# Skill Comply

> Visualize whether skills, rules, and agent definitions are actually followed — auto-generates scenarios at 3 prompt strictness levels, runs agents, classifies behavioral sequences, and reports compliance rates with full tool call timelines

- Skill: `shimo4228/skill-comply` (Agent Skill, multi-file: 32 files)
- Install (CLI): `npx skillmds@latest add shimo4228/skill-comply`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shimo4228/skill-comply/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: shimo4228 (https://skillmd.com/u/shimo4228)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shimo4228/skill-comply

---


# skill-comply: Automated Compliance Measurement

Measures whether coding agents actually follow skills, rules, or agent definitions by:
1. Auto-generating expected behavioral sequences (specs) from any .md file
2. Auto-generating scenarios with decreasing prompt strictness (supportive → neutral → competing)
3. Running `claude -p` and capturing tool call traces via stream-json
4. Classifying tool calls against spec steps using LLM (not regex)
5. Checking temporal ordering deterministically
6. Generating self-contained reports with spec, prompts, and timelines

## Supported Targets

- **Skills** (`skills/*/SKILL.md`): Workflow skills like search-first, TDD guides
- **Rules** (`rules/common/*.md`): Mandatory rules like testing.md, security.md, debugging.md
- **Agent definitions** (`agents/*.md`): Whether an agent gets invoked when expected (internal workflow verification not yet supported)

## When to Activate

- User runs `/skill-comply <path>`
- User asks "is this rule actually being followed?"
- After adding new rules/skills, to verify agent compliance
- Periodically as part of quality maintenance

## Usage

```bash
# 前提: scripts/ と pyproject.toml はこのスキルのディレクトリにあり、
# `python -m scripts.run` の解決は cwd 依存のため、まずスキルディレクトリへ cd する
# （`uv run --project` だけでは module 解決できないことを 2026-07-13 に実測確認）
cd ~/.claude/skills/skill-comply

# Full run
uv run python -m scripts.run ~/.claude/rules/common/testing.md

# Dry run (no cost, spec + scenarios only)
uv run python -m scripts.run --dry-run ~/.claude/skills/search-first/SKILL.md

# Custom models
uv run python -m scripts.run --gen-model haiku --model sonnet --classifier-model sonnet <path>

# 直列に戻す（レートリミットに当たったとき）
uv run python -m scripts.run --concurrency 1 <path>

# Bash を要する spec のみ (既定は off — 下の「信頼境界」を読んでから)
uv run python -m scripts.run --allow-bash <path>

# 保存済み spec を再利用して run 間比較 (LLM 再生成をスキップ)
uv run python -m scripts.run --spec results/<skill-name>.spec.yaml <path>
```

**spec の固定と run 間比較**: spec は「試験問題」。LLM 生成のたびに required steps 数も
順序制約も変わるため、生成された spec は自動で `results/<skill-name>.spec.yaml` に保存される
（gitignore 対象外 — version 管理できる）。同じ skill を再測定するときは `--spec` でこれを
読み込むと問題文が固定され、スコアが比較可能になる。次回の生成 run は同名ファイルを
上書きするので、比較対象として残したい spec は別名でコピーしておく。
なお scenario prompt は引き続き毎回 LLM 生成で変動する — run 間比較の固定は spec までで、
scenario の非決定性は現状スコープ外。

## 実行時間と進捗の見かた

3 つのシナリオは互いに独立（別プロンプト・別 sandbox・別プロセス）なので、
既定で 3 本同時に走る。待ち時間は 3 本の合計ではなく**いちばん遅い 1 本**になる。
分類（採点）もシナリオごとなので一緒に並ぶ。spec 生成 → シナリオ生成の 2 段は
前段の出力を次段が使うため直列のまま。

並列化してもスコアとレポートは変わらない。完了順は進捗表示にしか使わず、
レポートは必ず supportive → neutral → competing の順に組み立てる。
`--concurrency 1` で完全に直列へ戻せる。

**進捗は stderr、結果は stdout に出る。** これは事故ではなく分担で、
2026-08-01 の実測にもとづく（→ [ADR-0029](../../docs/adr/0029-skill-comply-parallel-scenarios-and-stderr-progress.md)）:

```bash
# 進捗が見える。stdout だけが tail に入り、stderr は端末へ直接届く
uv run python -m scripts.run <path> | tail -40

# 進捗もログに残したい
uv run python -m scripts.run <path> 2>&1 | tee run.log
```

**`2>&1 | tail -40` にすると何も見えなくなる。** `tail` は `-f` なしだと
「最後の N 行」を出す道具で、入力が終わるまで 1 行も出せない。
これは `tail` の性質なので、`python -u` でも flush でも直らない。
途中経過を見たいときは `tail` でなく `tee` を使う。

シナリオが 1 本でも失敗したとき（子プロセスの異常終了など）は、
そのシナリオを 0% として数えず「測定失敗」として stderr に出し、終了コード 1 を返す。
残りのシナリオのレポートは通常どおり保存される。

## 測定対象の種別と、測れるもの

対象がどこにあるかで、子から見えるかどうかが変わる。**見えないまま測ると、
skill 遵守ではなく「skill を持たないエージェントの素の挙動」を測ることになる。**
2026-08-02 まではそれが黙って起きていた（実例: project skill の run で
`Skill(...)` → `Unknown skill` のまま 75% / 50% / 25% が出ていた）。

| 対象 | 子から見えるか | 測定 |
|---|---|---|
| global skill（`~/.claude/skills/`） | 見える | そのまま測れる |
| project skill（repo の `.claude/skills/`） | **見えない** | sandbox に配置してから測る（下記の 2 層） |
| rule / agent 定義 / 素の .md | skill ではない | `Skill` 呼び出しは期待しない |

project skill は 2 層で測る。**どちらで測ったかはレポートの Summary に必ず出る** —
Tier 1 の 75% と Tier 2 の 75% は別物なので、混同できないようにする。

- **Tier 1（既定、flag 不要）** — frontmatter の `name` と `description` だけ本物で、
  本文は無害な stub を置く。**「skill に手を伸ばしたか」を測る。**
  発見と起動は `name` / `description` で決まり本文は関与しないので、これで足りる
- **Tier 2（`--load-target-skill`）** — 本物の本文を置く。**「手順に従うか」を測る。**
  監査対象の本文が無人の子への**指示**になるので opt-in。`SKILL.md` 1 ファイルのみを
  写し、ディレクトリは写さない（`references/` を持つ skill は Tier 2 で短く測れる —
  黙った穴より、見える制限を選ぶ）

**Tier 1 は測定の正しさのための層であって、封じ込めの層ではない。** 存在理由は
「project skill を発見可能にして、素の挙動でなく遵守を測れるようにする」こと。

`description` は発見に必要なので必ず子に届く。上限 500 文字は**分量の上限であって
能力の上限ではない** — 500 文字は指示文として十分すぎる長さで、Read / Write / Edit /
Glob / Grep を持つ子に有効な命令を書ける。Tier 1 と Tier 2 の差は
**「手順が届かない」**であって**「指示が届かない」**ではない。

`--load-target-skill` と `--allow-bash` の併用は、**untrusted な文書に payload と
インタープリタの両方を渡す**組み合わせなので警告が出る。

## 信頼境界 — 監査対象ファイルは untrusted

**このツールは、あなたが書いたとは限らない .md を読んで LLM にシナリオを書かせ、
それを別のエージェントに実行させる。** 監査対象がそのまま生成器への入力になるので、
対象ファイルの本文は untrusted data として扱う（2026-07-25 security scan F2/F3/F4/F18）。

- **`setup_commands` は実行されない。** `mkdir` / `touch` の 2 語彙だけを pathlib で
  解釈し、パスは sandbox 内に解決されることを検証する。それ以外は拒否して stderr に
  出す。以前は `shlex.split` + `subprocess` だったため、community 由来スキル 1 本で
  ホスト上の任意コマンド実行になっていた
- **パス検証は `..` を先に潰してから symlink を解決する。** 2026-08-01 まで、存在しない
  要素の後ろに置かれた `..`（`a/../../elsewhere/x`）が解決されずに残り、`Path.parents` が
  それをただのディレクトリ名として扱って sandbox 外への作成を通していた。
  sandbox は直前に消して作り直されるので最初の要素は必ず存在せず、抜け道は常に開いていた
- **子のツールは `permissions.deny` で外す。`--allowedTools` では外れない。**
  `claude --help` の言うとおり `--allowedTools` は「allow するツール名の列」＝自動承認
  リストであって、載せなかったツールは**消えない**。2026-08-02 に Claude Code 2.1.220 で
  実測: `--allowedTools "Read,Glob,Grep"` だけの子が Bash を呼び、`uname -sr` が
  ホスト上で実行された。`--permission-mode`（manual / dontAsk / acceptEdits）でも
  `--setting-sources project` でも変わらない。
  `--settings` の `permissions.deny` で `Bash` / `Agent` / `Workflow` /
  `ToolSearch` / `ScheduleWakeup` を外す（正本: `scripts/child_settings.py`）。
  Bash 以外も外すのは、`Agent` と `Workflow` が**このコードが制御しないツール集合を持つ
  サブエージェント**を生み、`ToolSearch` が user 設定から継承した MCP の面
  （メール・ドライブ・カレンダー・ブラウザ）を必要に応じて読み込むため。
  `--allow-bash` は deny から Bash を**外す**形で効く
- `cwd` と `--add-dir` はアクセスを**広げる**もので、閉じ込めない
- **生成器プロンプトでは対象文書を nonce 区切りで隔離**し、data であって指示ではないと
  明示している。固定区切り（`---`）は markdown frontmatter が再現できてしまう
- **sandbox は 1 シナリオ 1 個、かつ 1 実行 1 根**。ディレクトリ名は LLM が生成した
  scenario id に由来するので、重複していたら実行前に検出して別名にする。直列なら重複は
  無害だが、並列では片方の sandbox 作成（作る前に消す）が走行中のもう片方を消してしまう。
  この一意化は**プロセス内でしか効かない**ので、根を実行単位で分ける —
  `/tmp/skill-comply-sandbox/run-<pid>/<id>`。同じ id を生成した別の run が
  互いの走行中 sandbox を消さない（2026-08-17、`scripts/runner.py: sandbox_run_root`）。
  `SANDBOX_BASE` 自体が symlink（共有ホスト・CI の scratch 領域）のときは根の計算で
  1 回だけ解決して追従する — 封じ込め判定を毎回 link 越しにやり直さない
- **`<sandbox>/.claude/` と `<sandbox>/.git/` はツール専有。監査対象由来の指定は受け付けない。**
  sandbox は子にとっての**プロジェクトルート**で、そこでは一部のファイル名が
  「置いてあるだけ」ではなく読み込まれる。実測（2026-08-02）: 一度も信頼していない
  workspace でも `<sandbox>/.claude/settings.json` の `hooks.SessionStart` は
  **無言でホスト上のコマンドを実行した**（同じファイルの `permissions.allow` は
  「信頼されていない」と明示的に拒否されるのに）。`<sandbox>/CLAUDE.md` も読まれて従われる。
  子自身の Write は substrate が止めるが、**このツールの pathlib 書き込みは止まらない**ので
  ここで塞ぐ。`CLAUDE.md` / `AGENTS.md` / `.mcp.json` / `settings.json` /
  `settings.local.json` / `.gitignore` は深さを問わず拒否。
  **判定は case-fold する** — APFS は case-insensitive なので `.CLAUDE/Settings.json` は
  書けてしまえば `.claude/settings.json` として読まれる（2026-08-02 にすり抜けを実測して修正）。
  `.git/` を含めるのは、`_setup_sandbox` が `git init` を先に走らせるので書き込み可能で、
  `core.fsmonitor` / `core.pager` / `alias.*` が**実行ビット無しで git が実行する設定文字列**
  だから（実測: `files:` で `.git/config` を置き、`git status` でホスト上のコマンドが走った。
  Claude Code は workspace で git を実行するので、子の Bash を塞いでいても発火する）。
  `.gitignore` は実行ではなく**測定の破壊** — Grep は従い Glob は従わないので、文書が
  自分のフィクスチャを detector の期待するツールから隠せる。
  **閉じ込められていることと不活性であることは別の性質**で、`_contained` が答えるのは前者だけ

信頼できない .md を測るときは `--dry-run` で生成されたシナリオを先に読むこと。
`--dry-run` は spec の step に加えて、**攻撃者が制御しうる 3 つのフィールドを全文出す** —
無人の子に渡される `prompt`、ファイルシステムを触る `setup_commands` と `files:`。

## Models

| Stage | Default | Why |
|-------|---------|-----|
| `--gen-model` | `haiku` | Spec / scenario generation. Short prompts, fast. |
| `--model` | `sonnet` | Scenario execution (the agent under test). `haiku` / `sonnet` / `opus` / `fable` を指定可。 |
| `--classifier-model` | `sonnet` | Trace classification. Haiku times out on long traces (50+ events) and abstract specs (e.g. contemplative-axioms). Sonnet handles the load with a 300s timeout. |

## Key Concept: Prompt Independence

Measures whether a skill/rule is followed even when the prompt doesn't explicitly support it.

## Report Contents

Reports are self-contained and include:
1. Expected behavioral sequence (auto-generated spec)
2. Scenario prompts (what was asked at each strictness level)
3. Compliance scores per scenario
4. Tool call timelines with LLM classification labels

### Advanced (optional)

For users familiar with hooks, reports also include hook promotion recommendations for steps with low compliance. This is informational — the main value is the compliance visibility itself.

