Not a promise. A hook.約束ではなく、hook が強制する。
A rule only tells Claude "don't push to main". A hook stops it — a small script that reads the tool call before it runs and exits 2 to block. Banto registers 46. Each skill declares its range in odd.yaml (ODD); this page maps which declaration lands on which hook.
rule は「main へ直接 push するな」と告げるだけ。止めるのは hook — tool call を実行前に読み、危険なら exit 2 で遮断する小さなスクリプト。Banto は 46 本。宣言側は各 skill の odd.yaml(ODD)が担い、どの宣言がどの hook に届くかまでこのページで突き合わせる。
one tool call, one deterministic verdict — no model in the gate1 回の tool call に 1 つの決定論的な判定 — 関所にモデルはいない
The whole picture全体像
Every hook is wired to one of Claude Code's lifecycle events. Banto uses all ten.すべての hook は Claude Code のライフサイクルイベントのいずれかに配線される。Banto は 10 種類すべてを使う。
Registered by the numbers登録数の実数
- 46 distinct hook scripts wired in
hooks.jsonhooks.jsonに配線された固有スクリプト 46 本 - 52 total command entries across 10 lifecycle events (some scripts run on more than one matcher)10 種類のライフサイクルイベント全体でのべ 52 エントリ(一部のスクリプトは複数 matcher に登録)
- events:
SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,Stop,SessionEnd,PreCompact,SubagentStart,SubagentStop,UserPromptExpansionイベント種別:SessionStart・UserPromptSubmit・PreToolUse・PostToolUse・Stop・SessionEnd・PreCompact・SubagentStart・SubagentStop・UserPromptExpansion PreToolUsecarries the heaviest load — 18 of the 52 entries — because that is where a dangerous action can still be stopped before it runs最も比重が重いのはPreToolUse(52 件中 18 件)。危険な操作を「実行前」に止められる、唯一の地点だから
Rules get skipped. Hooks don't.rule は読み飛ばされる。hook は読み飛ばせない。
CLAUDE.md, the ten behavioral rules, and each skill's own instructions are text Claude reads inside its context — and in a long session that text gets skipped, misremembered, or overridden by a more recent instruction. A hook is not text. It is a real process that starts the moment Claude calls a tool, reads that tool call's raw payload directly, and — when it blocks — exits 2 before the action runs. There is no context window for it to fall out of.CLAUDE.md も、10 の行動規範も、各 skill 自身の指示も、Claude がコンテキストの中で読むテキストにすぎない。長いセッションでは、このテキストは読み飛ばされ、記憶が薄れ、後から来た指示に上書きされる。hook はテキストではない。Claude が tool を呼んだ瞬間に走る実プロセスで、その tool call の生の payload を直接読み、block するときは実行前に exit 2 で終了する。コンテキストウィンドウから落ちるということが、そもそも起こらない。
jq is absent, every hook exits 0 silently — the plugin effectively disables itself rather than blocking work on a broken environment. The inverse holds for a positive detection: when a safety hook decides to block, it blocks with exit 2 regardless of what any other plugin's hooks would allow.判定材料が無ければ黙って通す(fail-open)。依存が欠けているだけでセッションを壊してはならない — jq が無ければ全 hook は黙って exit 0 とし、壊れた環境で作業を止めるのではなく、機能を自ら無効化する。逆に「危険を検知した」場合は fail-safe — 安全系 hook が block を決めたら、他プラグインの hook が何を許していようと exit 2 で止める。The seven that carry the weight実質を担う 7 つの hook
Most of the 46 inject a few lines of context or track telemetry silently. These seven actually block, gate, or auto-trigger something — read straight from each script's own header comment.46 本の大半は、数行のコンテキスト注入や無音のテレメトリ記録にとどまる。この 7 つだけが実際に block・gate・自動発火のいずれかを行う。各スクリプト冒頭のコメントから直接起こす。
odd-kill-switch PreToolUse
Blocks four irreversible git / filesystem actions outright: pushing straight to main/master, --no-verify, a hard --force push (--force-with-lease passes), and rm -rf on root or home. Each has its own named escape hatch (e.g. ODD_ALLOW_MAIN_PUSH=1) — the block is default-on, never silent-on.4 つの不可逆な git / ファイルシステム操作を全遮断する:main/master への直接 push、--no-verify、素の --force push(--force-with-lease は通過)、root や home への rm -rf。それぞれ専用の名前つきエスケープ(例:ODD_ALLOW_MAIN_PUSH=1)を持ち、既定は常に block。
egress-guard PreToolUse: Write/Edit
Checks a Write/Edit's content against a private, user-scope name registry (~/.claude/banto-name-registry, never committed) before it lands in a client deliverable — catching internal member names, other-project names, and PII before they leak across an NDA boundary. No-ops if the registry is absent or empty; escape via BANTO_ALLOW_NAMES=1.Write/Edit の内容を、非公開のユーザー単位の名前レジストリ(~/.claude/banto-name-registry、絶対にコミットしない)と照合し、客先成果物に書き込まれる前に止める — 内部メンバー名・他案件名・個人情報が NDA の境界を越える前に捕捉する。レジストリが無い/空なら no-op。エスケープは BANTO_ALLOW_NAMES=1。
verify-claim-guard Stop
Fires when the final response asserts completion ("done," "fixed," "tests pass") while recent evidence says otherwise, on two narrow, structural checks: B1 — the most recent full verify-run is recorded RED; B2 — the very last tool result in the transcript has is_error: true (one success call after a failure is treated as an already-resolved retry, not a false completion). One re-verify prompt per stop, guarded by stop_hook_active so it never loops.最終応答が完了を断定する(「完了しました」「修正しました」「テストが通った」等)一方で、直近の証跡がそれと矛盾するときに、2 つの狭い構造判定で発火する:B1 — 直近のフル verify-run が RED として記録されている。B2 — transcript 内で最後の tool result が is_error: true(失敗の後に成功呼び出しが 1 件あれば、それはリトライで解消済みとみなし、誤った完了断定とはしない)。1 stop につき再検証の促しは 1 回のみ・stop_hook_active で無限ループを防ぐ。
idle-checkpoint Stop / SessionEnd
On every Stop, arms a detached watcher process; if the transcript goes untouched for BANTO_IDLE_CHECKPOINT_MIN minutes (default 5 — the prompt-cache TTL), it fires a headless claude -p --resume --fork-session to auto-run /save-checkpoint. SessionEnd kills the watcher so a normally-finished session never triggers a wasted headless run. Motive: once the cache expires, resuming an idle session re-reads a huge context at cold prices — checkpointing first makes the eventual /clear + resume cheap.毎回の Stop で、切り離されたウォッチャープロセスをアーム。transcript が BANTO_IDLE_CHECKPOINT_MIN 分(既定 5 分 = プロンプトキャッシュの TTL)更新されなければ、ヘッドレスの claude -p --resume --fork-session を起動して /save-checkpoint を自動実行する。SessionEnd はウォッチャーを kill し、正常終了したセッションで無駄なヘッドレス実行を走らせない。動機:キャッシュが切れた後に放置セッションへ復帰すると、巨大なコンテキストをコールド価格で読み直すことになる。先にチェックポイントを切っておけば、その後の /clear + 再開が安く済む。
webfetch-deny PreToolUse: WebFetch
Denies WebFetch outright via the permissionDecision JSON contract — its rationale is the evidence-first rule: WebFetch returns a small-model summary of the page, so the main model can never verify the original text. URL reading is consolidated into webread (full-text extraction via trafilatura); URL discovery still uses WebSearch. Escape: BANTO_ALLOW_WEBFETCH=1.permissionDecision の JSON 契約で WebFetch を全遮断する — 根拠は evidence-first rule:WebFetch は小型モデルによるページ要約を返すため、本体モデルは原文を検証できない。URL の精読は webread(trafilatura による全文抽出)に一本化し、URL の発見には引き続き WebSearch を使う。エスケープ:BANTO_ALLOW_WEBFETCH=1。
odd-gate PreToolUse: Write/Edit
A circuit breaker for self-driving implementation loops: when a session-global counter (written by the test runner, reset on a pass) shows the project's own tests failing ODD_TEST_FAILURE_THRESHOLD times in a row (default 3), it blocks further edits and tells the model to hand the root cause to a debugger agent instead of churning. Fires regardless of which skill is active, because runaway edit→test-fail loops happen in the plain main loop too. Escape: ODD_ALLOW_TEST_FAILURES=1.自走実装ループのための回路遮断器:セッション横断のカウンタ(テストランナーが書き、成功でリセット)が、プロジェクト自身のテストの連続失敗を ODD_TEST_FAILURE_THRESHOLD 回(既定 3 回)検知すると、それ以上の編集を止め、闇雲な繰り返しではなく debugger エージェントに根本原因を委ねるよう促す。どの skill が動いていても発火する — 暴走した「編集 → テスト失敗」ループは、skill 不在の素の main loop でも起こるため。エスケープ:ODD_ALLOW_TEST_FAILURES=1。
prod-guard PreToolUse: Bash
Blocks production-environment operations by default — kubectl against a prod context, terraform apply/destroy, a --prod deploy, or ssh to a prod host. It only passes with a conversation-time approval escape (BANTO_ALLOW_PROD=1) or a standing per-repo grant (prod_ops: allow in {base}/meta/grants.json).本番環境の操作を既定で block する — prod context への kubectl、terraform apply/destroy、--prod デプロイ、prod ホストへの ssh。通すのは会話内承認によるエスケープ(BANTO_ALLOW_PROD=1)か、リポジトリ単位の常設許可({base}/meta/grants.json の prod_ops: allow)のみ。
verify-run / verify-detect helpers, not hooks
verify-detect.sh is pure file-based detection (never runs the project's own tools) shared by both the per-edit test runner and the full verify — one source of truth for what "build," "test," and "API smoke" mean in this project. verify-run.sh is the loop's own full verify: build → full test suite → API smoke (against staging, NODE_ENV=test, never production), writing the green/red state that verify-claim-guard's B1 check reads, and bumping the same failure counter that odd-gate watches. Neither is registered in hooks.json — they're invoked directly by the build-and-verify loop — but they're the ground truth both flagship hooks above depend on.verify-detect.sh は純粋なファイル判定(プロジェクト自身のツールは決して実行しない)で、1 回ごとのテスト実行とフル verify の両方が共有する — このプロジェクトでの「build」「test」「API smoke」の定義を一本化する単一の正本。verify-run.sh はループ自身のフル verify:build → フルテスト → API smoke(ステージング向け・NODE_ENV=test・本番には決して当たらない)を実行し、verify-claim-guard の B1 判定が読む green/red 状態を書き込み、odd-gate が監視するのと同じ失敗カウンタを更新する。どちらも hooks.json には未登録 — build-and-verify ループから直接起動される — が、上記 2 つの目玉 hook が依存する事実の根拠そのもの。
Design principles設計原則
Every hook in the directory follows the same compatibility contract, documented in hooks/CONTRACT.md — not a new mechanism, just the rules that keep 46 independently-written scripts from becoming unpredictable together.このディレクトリの全 hook は同じ互換性契約に従う(hooks/CONTRACT.md に記載)。新しい仕組みではなく、独立に書かれた 46 本のスクリプトが、まとまったときに予測不能にならないための規律。
Narrow over clever賢さより narrow
verify-claim-guard used to grep the last three tool outputs for words like "fatal:" — which also matched a hook script's own source code sitting in the transcript, a real false-positive source. The fix wasn't a smarter heuristic; it was reading the structured is_error flag on exactly the last tool_result. Across the hook set, the pattern repeats: replace a broad text match with a structural one the moment it misfires.verify-claim-guard は以前、直近 3 件の tool 出力を "fatal:" のような語で grep していた — これは transcript 内にたまたま存在した hook スクリプト自身のソースコードにも一致してしまう、実際の誤検知源だった。修正は「賢いヒューリスティック」ではなく、最後の tool_result 1 件だけの構造化された is_error フラグを読むことだった。hook 群全体でこのパターンが繰り返される:広い文字列一致が誤爆した瞬間、構造判定に置き換える。
One stop, one re-check1 stop に 1 回だけ
A Stop hook may request exactly one extra turn via exit 2 — verify-claim-guard and the checkpoint-arming logic both check stop_hook_active before firing, so a re-prompt can never chain into a second re-prompt. This is what keeps a safety valve from becoming an infinite loop the moment its own trigger condition re-appears after the retry.Stop hook が exit 2 で要求できる追加ターンはちょうど 1 回 — verify-claim-guard もチェックポイント起動ロジックも、発火前に必ず stop_hook_active を確認する。これにより、リトライ後に同じ発火条件が再び現れても、再プロンプトが二重に連鎖することはない。
BANTO_ALLOW_NAMES, ODD_ALLOW_MAIN_PUSH, ODD_ALLOW_TEST_FAILURES, BANTO_ALLOW_WEBFETCH — so a legitimate exception can be granted precisely, without disabling the guard's other three rules along with it.逃し方は個別に名前がつく。全体スイッチではない。決定論的な block はどれも、専用の環境変数エスケープを備える — BANTO_ALLOW_NAMES・ODD_ALLOW_MAIN_PUSH・ODD_ALLOW_TEST_FAILURES・BANTO_ALLOW_WEBFETCH。正当な例外は、そのガードの他 3 ルールまで一緒に無効化することなく、狙って許可できる。Autonomy by declaration自律性は宣言する
A one-click utility (kit) and a multi-file autonomous loop (dev-loop) can't share one autonomy rule — a kit-sized mistake costs a re-run, a dev-loop-sized mistake costs a branch. Each skill states its own operating range in plugins/banto/skills/<name>/odd.yaml, validated against a JSON Schema that only checks shape, not behavior.ワンショットの utility(kit)と、複数ファイルを跨ぐ自走ループ(dev-loop)を、同じ自律性ルールで扱うことはできない — kit の間違いはやり直しで済むが、dev-loop の間違いはブランチ 1 本分のコストになる。各 skill は plugins/banto/skills/<name>/odd.yaml に自分の運転可能な範囲を書き、形だけを見る JSON Schema で検証する(振る舞いの検証ではない)。
| Levelレベル | Meaning意味 | Adopted interpretations (spec-fidelity)採用解釈の扱い(spec-fidelity) |
|---|---|---|
| L0 | Manual — information display onlyManual — 情報表示のみ | May proceed, disclosed in the final report採用解釈で進行可・最終報告で開示 |
| L1 | Suggest — user approves each outputSuggest — 出力ごとにユーザーが承認 | May proceed, disclosed in the final report採用解釈で進行可・最終報告で開示 |
| L2 | Copilot — partial execution, per-step approvalCopilot — 部分実行・手順ごとに承認 | May proceed, disclosed in the final report採用解釈で進行可・最終報告で開示 |
| L3 | Autopilot — continuous execution, owner is asked only on exceptionsAutopilot — 継続実行・例外時のみ owner へ確認 | May proceed, but medium scale or larger MUST disclose interpretation / alternatives / verification進行可。ただし中規模以上は「採用解釈・代替案・検証内容」の開示が必須 |
| L4+ | Supervised-Auto / Self-improving — explicit stop conditions, little to no human loopSupervised-Auto / Self-improving — 明示停止条件・人間の関与がほぼ無い | Advance confirmation required — adopted interpretations not allowed. Out of Banto's scope.事前確認が必須・採用解釈は不可。Banto の対象外。 |
A skill with no odd.yaml at all is treated as provisional L0 until one is written. Banto ships L0–L3; L4/L5 belong to a separate, not-yet-public plugin (banto-autonomy).odd.yaml が無い skill は、書かれるまで暫定的に L0 として扱う。Banto が実装するのは L0〜L3 のみ。L4・L5 は別プラグイン(banto-autonomy・未公開)の領域。
A real odd.yaml, field by field実例で見る odd.yaml の中身
dev-loop is Banto's autonomous implementation loop (L3), so its odd.yaml is the most exercised one in the tree. Six fields carry the actual declaration.dev-loop は Banto の自走実装ループ(L3)で、ツリー内でも odd.yaml が最も実働している skill。宣言の本体は 6 つのフィールドが担う。
What the skill owns: task decomposition, the implement → build-and-verify → fix loop, tasks.md progress, parallel fan-out judgment, exception detection.skill が担う範囲:タスク分解、実装 → 検証 → 修正の周回、tasks.md の前進、並列 fan-out 判定、例外検知。
Spec generation and branch/worktree ops are delegated elsewhere; push / PR / merge to main stay a human gate.仕様生成やブランチ / worktree 操作は他 skill へ委譲。push・PR・main マージは人間ゲートのまま。
Commits stop at the branch; verification must reuse the existing verify-run.sh, never reimplement its detection logic; fan-out caps at 5 parallel workers.commit はブランチ止まり。検証は既存の verify-run.sh を正本とし検出ロジックを再実装しない。並列 fan-out は最大 5 worker。
4 conditions that stop the loop and escalate to the owner — detailed in Where the declaration meets a hook below.周回を止めて owner へ上げる 4 条件 — 詳細は下の「宣言が hook と接続する場所」節。
Before entering the loop, the decomposition plan (task list + parallel policy) is shown once for confirmation; an epic-scale target routes through ws epic first.自走に入る前、分解プラン(タスク列 + 並列方針)を 1 度提示して確認。epic 規模なら先に ws epic 提案を経由。
test_failure_threshold: 3, parallel_fanout_max: 5 — the numeric backing for the boundaries above, read by the schema's metrics object.test_failure_threshold: 3、parallel_fanout_max: 5 — 上記境界の数値的な裏付け。スキーマの metrics オブジェクトとして読む。
dev-loop's own autonomy_level line even names its enforcers inline: "explicit stop is a deterministic hook (odd-gate / verify-claim-guard); the human gate is the Phase-0 decomposition-plan confirmation plus push/PR/main." The declaration and its enforcement are cross-referenced from day one.dev-loop の autonomy_level 行そのものが強制側の名前まで書いている:「explicit stop は deterministic hook(odd-gate/verify-claim-guard)/ human gate は Phase0 分解プラン確認+push/PR/main」。宣言と強制は最初から相互参照している。
Where the declaration meets a hook宣言が hook と接続する場所
dev-loop's odd.yaml lists 4 kill-switch conditions. Three land on a deterministic script; the fourth — a goal fork — is enforced by judgment, and that gap is disclosed on purpose.dev-loop の odd.yaml は 4 つの kill switch 条件を挙げる。うち 3 つは決定論的スクリプトが受け、残る 1 つ(ゴール分岐)は判断で受ける — その隙間をあえて明示する。
3 of 4 conditions land on a script — the 4th is disclosed as judgment, on purpose4 条件中 3 つはスクリプトが受ける — 残る 1 つは「判断」だと、あえて明示する
- 3 consecutive test failuresテスト 3 連続失敗 → stops the loop, escalates to the owner. → 周回を停止し owner へ。
Enforced by:強制元: odd-gate.sh — a PreToolUse counter (default threshold 3,
ODD_TEST_FAILURE_THRESHOLD), escape hatchODD_ALLOW_TEST_FAILURES=1.— PreToolUse のカウンタ(既定閾値 3・ODD_TEST_FAILURE_THRESHOLD)。エスケープはODD_ALLOW_TEST_FAILURES=1。 - Claiming "done" while verify-last is redverify-last が red のまま「完了」を主張 → blocked before the response goes out. → 応答が出る前にブロック。
Enforced by:強制元: verify-claim-guard.sh — a Stop hook, fail-open (no jq / no transcript → exit 0), fires at most once per stop. It requires both a completion claim in the last assistant text AND unresolved evidence: the last full verify-run state is red (within 4 hours), or the last tool_result reports an error.— Stop hook。判定材料が無ければ黙って通す fail-open(jq 不在・transcript 不在 → exit 0)で、1 stop につき最大 1 回だけ発火。最終応答に「完了」断定があり、かつ未解決の根拠(4 時間以内の verify-run 状態が red、または直近 tool_result が error)が両方揃ったときのみブロックする。
- A goal fork with no clear interpretationゴール分岐で採用解釈が立たない → stop, ask the owner. → 停止し owner へ確認。
Enforced by: judgment, not a hook強制元:hook ではなく判断による — the spec-fidelity rule's stop triggers. A goal fork is not a pattern a deterministic script can reliably detect.spec-fidelity ルールの stop trigger による。ゴール分岐は決定論的スクリプトが確実に検知できるパターンではない。
- Irreversible / outward operations (push, PR, delete, external post)不可逆・外向き操作(push・PR・削除・外部投稿) → stop, ask the owner. → 停止し owner へ確認。
Enforced by:強制元: odd-kill-switch.sh — 4 blocking rules, none dev-loop-specific: direct push to main/master,
--no-verify,--forcepush, andrm -rfon root/home. Each has its own named escape variable, none default-on.— 4 つのブロックルール(dev-loop 固有ではない):main/master への直 push・--no-verify・--forcepush・root/home へのrm -rf。各々に個別のエスケープ変数があり、既定では無効。
Not every declared condition has a matching hook — and that gap is disclosed here on purpose. A kill switch that can't be mechanically detected still belongs in odd.yaml, as a human-readable boundary; it just needs a different kind of enforcement (a rule, a judgment call) than the ones a script can catch.宣言した条件のすべてに対応する hook があるわけではない — その隙間をここであえて明示する。機械的に検知できない kill switch も、人が読める境界として odd.yaml に書く価値はある。ただしスクリプトが拾えるものとは違う種類の強制(ルール・判断)を要る。