ruvnet-brain 4.0.35 → 4.0.90-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/bin/install.mjs +283 -23
- package/data/model-catalog.json +104 -15
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/brain-console.md +72 -9
- package/plugin/commands/configure.md +67 -21
- package/plugin/commands/rvcb.md +72 -9
- package/plugin/hooks/codex-hooks.json +40 -33
- package/plugin/hooks/hook-contracts.json +14 -24
- package/plugin/hooks/hooks.json +7 -42
- package/plugin/mcp/server.mjs +23 -6
- package/plugin/scripts/adr-currency-gate.mjs +150 -0
- package/plugin/scripts/capability-registry.mjs +10 -1
- package/plugin/scripts/codex-hook-adapter.mjs +121 -19
- package/plugin/scripts/codex-hook-wrapper.mjs +61 -4
- package/plugin/scripts/continuation-gate.mjs +148 -8
- package/plugin/scripts/decision-gate.mjs +428 -0
- package/plugin/scripts/decision-outcomes.mjs +0 -0
- package/plugin/scripts/degradation-watch.mjs +271 -0
- package/plugin/scripts/ground-ruvnet.sh +51 -11
- package/plugin/scripts/hijack-ruvnet.sh +11 -3
- package/plugin/scripts/hook-registry.mjs +48 -3
- package/plugin/scripts/hook-shim.mjs +58 -3
- package/plugin/scripts/identifier-preflight.mjs +134 -0
- package/plugin/scripts/learn-capture.sh +50 -1
- package/plugin/scripts/learn-flush.mjs +5 -5
- package/plugin/scripts/lesson-bridge.mjs +343 -0
- package/plugin/scripts/lesson-hooks.sh +26 -0
- package/plugin/scripts/lesson-promote.mjs +50 -0
- package/plugin/scripts/lesson-store.mjs +6 -1
- package/plugin/scripts/mcp-readiness.mjs +107 -0
- package/plugin/scripts/protect-brain-state.sh +9 -0
- package/plugin/scripts/runtime-preferences.mjs +40 -0
- package/plugin/scripts/session-snapshot-hook.mjs +15 -6
- package/plugin/scripts/spend-guard.mjs +125 -0
- package/plugin/scripts/unprompted-runtime.mjs +12 -2
- package/plugin/scripts/update-apply.mjs +7 -2
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +20 -5
- package/plugin/skills/ruvnet-brain/SKILL.md +3 -3
- package/scripts/brain-score.mjs +252 -0
- package/scripts/brain-stamp.mjs +5 -1
- package/scripts/build-bundle.mjs +25 -1
- package/scripts/console-engine.mjs +1 -1
- package/scripts/health-repair.mjs +11 -2
- package/scripts/host-install-matrix.mjs +21 -0
- package/scripts/ingest-repo.mjs +66 -6
- package/scripts/learning-replay-cli.mjs +8 -3
- package/scripts/learning-replay-fixture.mjs +25 -6
- package/scripts/learning-replay-proof.mjs +38 -0
- package/scripts/nightly-wrapper.sh +13 -0
- package/scripts/onboarding-console.mjs +21 -1
- package/scripts/org-repo-count.mjs +119 -0
- package/scripts/publication-receipt.mjs +7 -3
- package/scripts/repo-count-detector.mjs +62 -0
- package/scripts/restore-local-ingests.mjs +116 -0
- package/scripts/selfcheck.mjs +9 -1
- package/scripts/stabilization-receipt.mjs +11 -1
- package/scripts/sync-census.mjs +0 -0
- package/scripts/sync-commands.mjs +117 -0
|
@@ -3,14 +3,77 @@ description: "Brain Console — RuvNet Brain Console (same console as /rvbc, /br
|
|
|
3
3
|
updated: 2026-07-20
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Launch the **RuvNet Brain Console** for the user. Same console as `/ruvnet-brain:configure`,
|
|
7
|
+
`/rvcb`, and `/brain-console` — every spelling lands here, so never tell the user they typed it wrong.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
2. Use `${RUVNET_BRAIN_KB:-$HOME/.cache/ruvnet-brain/kb}/.console-runtime/scripts/onboarding-console.mjs`.
|
|
11
|
-
A current-repository copy is allowed only for an explicit developer checkout.
|
|
12
|
-
3. Run `node <resolved-script> --serve --open` in the background.
|
|
13
|
-
4. Give the URL immediately. Never promise a duration; the page reports its own progress.
|
|
9
|
+
## The contract you are keeping
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
The page **opens immediately and scans itself while they watch.** The server never blocks: a cold or
|
|
12
|
+
new-project read is answered in milliseconds with "measuring…", the page keeps its skeletons and
|
|
13
|
+
narrates the wait, and each card fills in as its own measurement lands. A chip in the header says how
|
|
14
|
+
old the picture is and re-measures when clicked.
|
|
15
|
+
|
|
16
|
+
So there is nothing to wait for and nothing to warn them about. **Your entire job is: say one
|
|
17
|
+
sentence, start the server in the background, hand over the URL, and get out of the way.**
|
|
18
|
+
|
|
19
|
+
## 1. SPEAK FIRST — one short sentence, before any tool call
|
|
20
|
+
|
|
21
|
+
Your FIRST output — before any tool call, any file read, any search — is one warm line saying you are
|
|
22
|
+
opening it now and it will scan itself live in their browser. Something in the spirit of:
|
|
23
|
+
|
|
24
|
+
> "Opening your console now — it'll come up right away and scan your setup live while you watch."
|
|
25
|
+
|
|
26
|
+
Say it like a person, not a status bar. **Do not** promise 20 seconds, a minute, or any duration: the
|
|
27
|
+
page carries its own timing now, and a number invented here is a number the page will contradict.
|
|
28
|
+
|
|
29
|
+
## 2. Find the installed runtime
|
|
30
|
+
|
|
31
|
+
Use `${RUVNET_BRAIN_KB:-$HOME/.cache/ruvnet-brain/kb}/.console-runtime/scripts/onboarding-console.mjs`.
|
|
32
|
+
A current-repository copy is allowed only in an explicit developer checkout. Never guess a
|
|
33
|
+
`~/Code/ruvnet-brain` path: the installer persists this runtime for clean users.
|
|
34
|
+
|
|
35
|
+
## 3. Start it in the BACKGROUND and open the browser
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
node <repo>/scripts/onboarding-console.mjs --serve --open
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**`run_in_background: true`, ALWAYS — never in the foreground.** This is a server: it does not exit,
|
|
42
|
+
so a foreground run hangs the tool call until the 120-second timeout and turns an instant page into
|
|
43
|
+
two minutes of dead air. It binds `127.0.0.1` only and prints `http://127.0.0.1:7411/` within about a
|
|
44
|
+
third of a second.
|
|
45
|
+
|
|
46
|
+
**Then post the URL immediately.** Do not poll the log, do not wait for a scan, do not check whether
|
|
47
|
+
the data has landed — the page reports its own progress and none of it is visible from here.
|
|
48
|
+
|
|
49
|
+
**If it reports it is already running**, that is a success, not an error: open the URL yourself with
|
|
50
|
+
`open "http://127.0.0.1:<port>/"`. One honest caveat, worth half a sentence if it comes up — a
|
|
51
|
+
console that is already running is serving the project it was *launched* from, so if they want this
|
|
52
|
+
project's view they can stop that one (`^C` in its terminal) and run this again.
|
|
53
|
+
|
|
54
|
+
## 4. Hand it over and stop
|
|
55
|
+
|
|
56
|
+
Give them the URL as a clickable line, and one warm sentence: the page is **read-only until you
|
|
57
|
+
click**, every machine change is **explained first and reversible**, and Settings save at the user
|
|
58
|
+
level only. Worth naming once: the brain's on/off switch is the first card on the page, and the
|
|
59
|
+
header chip says how fresh the reading is and re-measures if they click it.
|
|
60
|
+
|
|
61
|
+
## 5. If it errors
|
|
62
|
+
|
|
63
|
+
Report it plainly — the real error, not a guess — and offer to fix it. Never leave them staring at
|
|
64
|
+
a dead tab wondering whether it is still loading.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Things NOT to do before the page is open
|
|
69
|
+
|
|
70
|
+
Each of these has cost a real user real minutes of silence, and not one of them is needed to open a
|
|
71
|
+
local web page:
|
|
72
|
+
|
|
73
|
+
- **No knowledge search, no memory recall, no source grounding, no repo exploration.** A cold
|
|
74
|
+
embedding-model load alone is allowed up to 180 seconds.
|
|
75
|
+
- **No model selection or routing work.**
|
|
76
|
+
- **No reading this repo to "check" anything first.** Find the script, run it.
|
|
77
|
+
|
|
78
|
+
Do **not** narrate the machine yourself afterwards; the page is the mirror. Your job is to open it,
|
|
79
|
+
say one sentence, and get out of the way.
|
|
@@ -1,33 +1,79 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Open the RuvNet Brain Onboarding Console — a local web page that mirrors YOUR machine's RuvNet setup, explains it in plain English, and lets you safely configure and fix it. Read-only until you click; every machine change is explained first and is reversible.
|
|
3
|
-
updated: 2026-07-
|
|
3
|
+
updated: 2026-07-20
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Launch the **RuvNet Brain
|
|
7
|
-
|
|
6
|
+
Launch the **RuvNet Brain Console** for the user. Same console as `/ruvnet-brain:configure`,
|
|
7
|
+
`/rvcb`, and `/brain-console` — every spelling lands here, so never tell the user they typed it wrong.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## The contract you are keeping
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
The page **opens immediately and scans itself while they watch.** The server never blocks: a cold or
|
|
12
|
+
new-project read is answered in milliseconds with "measuring…", the page keeps its skeletons and
|
|
13
|
+
narrates the wait, and each card fills in as its own measurement lands. A chip in the header says how
|
|
14
|
+
old the picture is and re-measures when clicked.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
So there is nothing to wait for and nothing to warn them about. **Your entire job is: say one
|
|
17
|
+
sentence, start the server in the background, hand over the URL, and get out of the way.**
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
node <repo>/scripts/onboarding-console.mjs --serve --open
|
|
20
|
-
```
|
|
19
|
+
## 1. SPEAK FIRST — one short sentence, before any tool call
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
Your FIRST output — before any tool call, any file read, any search — is one warm line saying you are
|
|
22
|
+
opening it now and it will scan itself live in their browser. Something in the spirit of:
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
the page is **read-only until you click**, everything it can change to your computer is **explained
|
|
27
|
-
in plain English and confirmed first**, and **Settings save at your user level**
|
|
28
|
-
(`~/.claude/ruvnet-brain/config.json`) without touching how anything else on your machine runs.
|
|
24
|
+
> "Opening your console now — it'll come up right away and scan your setup live while you watch."
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
Say it like a person, not a status bar. **Do not** promise 20 seconds, a minute, or any duration: the
|
|
27
|
+
page carries its own timing now, and a number invented here is a number the page will contradict.
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
## 2. Find the installed runtime
|
|
30
|
+
|
|
31
|
+
Use `${RUVNET_BRAIN_KB:-$HOME/.cache/ruvnet-brain/kb}/.console-runtime/scripts/onboarding-console.mjs`.
|
|
32
|
+
A current-repository copy is allowed only in an explicit developer checkout. Never guess a
|
|
33
|
+
`~/Code/ruvnet-brain` path: the installer persists this runtime for clean users.
|
|
34
|
+
|
|
35
|
+
## 3. Start it in the BACKGROUND and open the browser
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
node <repo>/scripts/onboarding-console.mjs --serve --open
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**`run_in_background: true`, ALWAYS — never in the foreground.** This is a server: it does not exit,
|
|
42
|
+
so a foreground run hangs the tool call until the 120-second timeout and turns an instant page into
|
|
43
|
+
two minutes of dead air. It binds `127.0.0.1` only and prints `http://127.0.0.1:7411/` within about a
|
|
44
|
+
third of a second.
|
|
45
|
+
|
|
46
|
+
**Then post the URL immediately.** Do not poll the log, do not wait for a scan, do not check whether
|
|
47
|
+
the data has landed — the page reports its own progress and none of it is visible from here.
|
|
48
|
+
|
|
49
|
+
**If it reports it is already running**, that is a success, not an error: open the URL yourself with
|
|
50
|
+
`open "http://127.0.0.1:<port>/"`. One honest caveat, worth half a sentence if it comes up — a
|
|
51
|
+
console that is already running is serving the project it was *launched* from, so if they want this
|
|
52
|
+
project's view they can stop that one (`^C` in its terminal) and run this again.
|
|
53
|
+
|
|
54
|
+
## 4. Hand it over and stop
|
|
55
|
+
|
|
56
|
+
Give them the URL as a clickable line, and one warm sentence: the page is **read-only until you
|
|
57
|
+
click**, every machine change is **explained first and reversible**, and Settings save at the user
|
|
58
|
+
level only. Worth naming once: the brain's on/off switch is the first card on the page, and the
|
|
59
|
+
header chip says how fresh the reading is and re-measures if they click it.
|
|
60
|
+
|
|
61
|
+
## 5. If it errors
|
|
62
|
+
|
|
63
|
+
Report it plainly — the real error, not a guess — and offer to fix it. Never leave them staring at
|
|
64
|
+
a dead tab wondering whether it is still loading.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Things NOT to do before the page is open
|
|
69
|
+
|
|
70
|
+
Each of these has cost a real user real minutes of silence, and not one of them is needed to open a
|
|
71
|
+
local web page:
|
|
72
|
+
|
|
73
|
+
- **No knowledge search, no memory recall, no source grounding, no repo exploration.** A cold
|
|
74
|
+
embedding-model load alone is allowed up to 180 seconds.
|
|
75
|
+
- **No model selection or routing work.**
|
|
76
|
+
- **No reading this repo to "check" anything first.** Find the script, run it.
|
|
77
|
+
|
|
78
|
+
Do **not** narrate the machine yourself afterwards; the page is the mirror. Your job is to open it,
|
|
79
|
+
say one sentence, and get out of the way.
|
package/plugin/commands/rvcb.md
CHANGED
|
@@ -3,14 +3,77 @@ description: "RvCB — RuvNet Brain Console (same console as /rvbc, /brain-conso
|
|
|
3
3
|
updated: 2026-07-20
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Launch the **RuvNet Brain Console** for the user. Same console as `/ruvnet-brain:configure`,
|
|
7
|
+
`/rvcb`, and `/brain-console` — every spelling lands here, so never tell the user they typed it wrong.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
2. Use `${RUVNET_BRAIN_KB:-$HOME/.cache/ruvnet-brain/kb}/.console-runtime/scripts/onboarding-console.mjs`.
|
|
11
|
-
A current-repository copy is allowed only for an explicit developer checkout.
|
|
12
|
-
3. Run `node <resolved-script> --serve --open` in the background.
|
|
13
|
-
4. Give the URL immediately. Never promise a duration; the page reports its own progress.
|
|
9
|
+
## The contract you are keeping
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
The page **opens immediately and scans itself while they watch.** The server never blocks: a cold or
|
|
12
|
+
new-project read is answered in milliseconds with "measuring…", the page keeps its skeletons and
|
|
13
|
+
narrates the wait, and each card fills in as its own measurement lands. A chip in the header says how
|
|
14
|
+
old the picture is and re-measures when clicked.
|
|
15
|
+
|
|
16
|
+
So there is nothing to wait for and nothing to warn them about. **Your entire job is: say one
|
|
17
|
+
sentence, start the server in the background, hand over the URL, and get out of the way.**
|
|
18
|
+
|
|
19
|
+
## 1. SPEAK FIRST — one short sentence, before any tool call
|
|
20
|
+
|
|
21
|
+
Your FIRST output — before any tool call, any file read, any search — is one warm line saying you are
|
|
22
|
+
opening it now and it will scan itself live in their browser. Something in the spirit of:
|
|
23
|
+
|
|
24
|
+
> "Opening your console now — it'll come up right away and scan your setup live while you watch."
|
|
25
|
+
|
|
26
|
+
Say it like a person, not a status bar. **Do not** promise 20 seconds, a minute, or any duration: the
|
|
27
|
+
page carries its own timing now, and a number invented here is a number the page will contradict.
|
|
28
|
+
|
|
29
|
+
## 2. Find the installed runtime
|
|
30
|
+
|
|
31
|
+
Use `${RUVNET_BRAIN_KB:-$HOME/.cache/ruvnet-brain/kb}/.console-runtime/scripts/onboarding-console.mjs`.
|
|
32
|
+
A current-repository copy is allowed only in an explicit developer checkout. Never guess a
|
|
33
|
+
`~/Code/ruvnet-brain` path: the installer persists this runtime for clean users.
|
|
34
|
+
|
|
35
|
+
## 3. Start it in the BACKGROUND and open the browser
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
node <repo>/scripts/onboarding-console.mjs --serve --open
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**`run_in_background: true`, ALWAYS — never in the foreground.** This is a server: it does not exit,
|
|
42
|
+
so a foreground run hangs the tool call until the 120-second timeout and turns an instant page into
|
|
43
|
+
two minutes of dead air. It binds `127.0.0.1` only and prints `http://127.0.0.1:7411/` within about a
|
|
44
|
+
third of a second.
|
|
45
|
+
|
|
46
|
+
**Then post the URL immediately.** Do not poll the log, do not wait for a scan, do not check whether
|
|
47
|
+
the data has landed — the page reports its own progress and none of it is visible from here.
|
|
48
|
+
|
|
49
|
+
**If it reports it is already running**, that is a success, not an error: open the URL yourself with
|
|
50
|
+
`open "http://127.0.0.1:<port>/"`. One honest caveat, worth half a sentence if it comes up — a
|
|
51
|
+
console that is already running is serving the project it was *launched* from, so if they want this
|
|
52
|
+
project's view they can stop that one (`^C` in its terminal) and run this again.
|
|
53
|
+
|
|
54
|
+
## 4. Hand it over and stop
|
|
55
|
+
|
|
56
|
+
Give them the URL as a clickable line, and one warm sentence: the page is **read-only until you
|
|
57
|
+
click**, every machine change is **explained first and reversible**, and Settings save at the user
|
|
58
|
+
level only. Worth naming once: the brain's on/off switch is the first card on the page, and the
|
|
59
|
+
header chip says how fresh the reading is and re-measures if they click it.
|
|
60
|
+
|
|
61
|
+
## 5. If it errors
|
|
62
|
+
|
|
63
|
+
Report it plainly — the real error, not a guess — and offer to fix it. Never leave them staring at
|
|
64
|
+
a dead tab wondering whether it is still loading.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Things NOT to do before the page is open
|
|
69
|
+
|
|
70
|
+
Each of these has cost a real user real minutes of silence, and not one of them is needed to open a
|
|
71
|
+
local web page:
|
|
72
|
+
|
|
73
|
+
- **No knowledge search, no memory recall, no source grounding, no repo exploration.** A cold
|
|
74
|
+
embedding-model load alone is allowed up to 180 seconds.
|
|
75
|
+
- **No model selection or routing work.**
|
|
76
|
+
- **No reading this repo to "check" anything first.** Find the script, run it.
|
|
77
|
+
|
|
78
|
+
Do **not** narrate the machine yourself afterwards; the page is the mirror. Your job is to open it,
|
|
79
|
+
say one sentence, and get out of the way.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "RuvNet Brain lifecycle hooks for Codex. Every command enters through a generation-independent wrapper and adapts the shared Brain hook bodies to Codex contracts.",
|
|
2
|
+
"description": "RuvNet Brain lifecycle hooks for Codex. Every command enters through a generation-independent wrapper and adapts the shared Brain hook bodies to Codex contracts.\n\nSCHEMA WARNING, MEASURED 2026-08-14 ON A LIVE CODEX 0.147.0 SESSION: this file accepts EXACTLY two top-level keys, `description` and `hooks`. Anything else and Codex logs `warning: failed to parse hooks config ...: unknown field `X`, expected `description` or `hooks`` and DROPS THE WHOLE FILE — all 16 hooks silently stop, on a warning line nobody reads. A structured `hostParity` object was written here first and did exactly that; the record below lives in this string because this string is the only place it can live.\n\nHOST PARITY (ADR-067). PreToolUse refusal routes through decision-gate.mjs on BOTH hosts, so decision-gate.mjs REFUSAL_POLICIES is the single list of policies and neither host can gain or lose one silently. This file deliberately does NOT restate that list: it used to name policies one by one, which is exactly why identifier-preflight, degradation-watch and adr-currency (added 2026-08-13) went live on Claude Code and were unreachable on Codex — a new policy had to be remembered twice, and nothing failed when it was not.\n\nDECLARED ABSENT — swarm-slot-recycler (Claude Code event TeammateIdle). Codex 0.147.0 has no TeammateIdle hook event. Its complete event set, read from the binary rather than assumed, is pre_tool_use, permission_request, post_tool_use, pre_compact, post_compact, session_start, session_end, user_prompt_submit, subagent_start, subagent_stop, stop. There is nothing to register against.\n\nOUTPUT CONTRACT. PreToolUse/PostToolUse/SessionStart/UserPromptSubmit stdout is PARSED by Codex (\"hook returned invalid post-tool-use JSON output\"); SessionEnd has no output schema at all and PreCompact carries no additionalContext. codex-hook-adapter.mjs enforces this per event.\n\nSESSIONEND IS HARD-CAPPED AT 3s BY THE HOST, measured on a live Codex 0.147.0 session which says so itself: `warning: clamping SessionEnd hook timeout to 3s`. A larger number here is not a longer budget, it is a lie the host silently corrects. learn-flush needs 18s (measured 18342/18347/18384 ms with a real queue and real ruflo), so codex-hook-wrapper.mjs DETACHES it: the cap governs how long the host WAITS, not how long work may take.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
]
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
|
+
"PreCompact": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": ".*",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 session-snapshot PreCompact",
|
|
23
|
+
"timeout": 5
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
16
28
|
"UserPromptSubmit": [
|
|
17
29
|
{
|
|
18
30
|
"hooks": [
|
|
@@ -31,41 +43,31 @@
|
|
|
31
43
|
],
|
|
32
44
|
"PreToolUse": [
|
|
33
45
|
{
|
|
34
|
-
"matcher": "^(
|
|
46
|
+
"matcher": "^(Edit|Write|MultiEdit|apply_patch)$",
|
|
35
47
|
"hooks": [
|
|
36
48
|
{
|
|
37
49
|
"type": "command",
|
|
38
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\"
|
|
39
|
-
"timeout":
|
|
50
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 6500 decision-gate write",
|
|
51
|
+
"timeout": 8
|
|
40
52
|
}
|
|
41
53
|
]
|
|
42
54
|
},
|
|
43
55
|
{
|
|
44
|
-
"matcher": "
|
|
56
|
+
"matcher": "^(Bash|exec_command|functions\\.exec_command|functions__exec_command)$",
|
|
45
57
|
"hooks": [
|
|
46
58
|
{
|
|
47
59
|
"type": "command",
|
|
48
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\"
|
|
49
|
-
"timeout":
|
|
60
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 6500 decision-gate bash",
|
|
61
|
+
"timeout": 8
|
|
50
62
|
}
|
|
51
63
|
]
|
|
52
64
|
},
|
|
53
65
|
{
|
|
54
|
-
"matcher": "^(
|
|
66
|
+
"matcher": "^(Agent|spawn_agent)$",
|
|
55
67
|
"hooks": [
|
|
56
68
|
{
|
|
57
69
|
"type": "command",
|
|
58
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500
|
|
59
|
-
"timeout": 5
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"type": "command",
|
|
63
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 protect-state",
|
|
64
|
-
"timeout": 5
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"type": "command",
|
|
68
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 unprompted-speech PreToolUse-write",
|
|
70
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 route-dispatch",
|
|
69
71
|
"timeout": 5
|
|
70
72
|
}
|
|
71
73
|
]
|
|
@@ -77,16 +79,6 @@
|
|
|
77
79
|
"type": "command",
|
|
78
80
|
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 verify-interface",
|
|
79
81
|
"timeout": 5
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"type": "command",
|
|
83
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 design-wall",
|
|
84
|
-
"timeout": 5
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"type": "command",
|
|
88
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 unprompted-speech PreToolUse-bash",
|
|
89
|
-
"timeout": 5
|
|
90
82
|
}
|
|
91
83
|
]
|
|
92
84
|
}
|
|
@@ -103,7 +95,7 @@
|
|
|
103
95
|
]
|
|
104
96
|
},
|
|
105
97
|
{
|
|
106
|
-
"matcher": "^(Bash|Edit|Write|exec_command|functions\\.exec_command|functions__exec_command|apply_patch)$",
|
|
98
|
+
"matcher": "^(Bash|Edit|Write|MultiEdit|exec_command|functions\\.exec_command|functions__exec_command|apply_patch)$",
|
|
107
99
|
"hooks": [
|
|
108
100
|
{
|
|
109
101
|
"type": "command",
|
|
@@ -113,7 +105,7 @@
|
|
|
113
105
|
]
|
|
114
106
|
},
|
|
115
107
|
{
|
|
116
|
-
"matcher": "^(Edit|Write|apply_patch)$",
|
|
108
|
+
"matcher": "^(Edit|Write|MultiEdit|apply_patch)$",
|
|
117
109
|
"hooks": [
|
|
118
110
|
{
|
|
119
111
|
"type": "command",
|
|
@@ -131,15 +123,30 @@
|
|
|
131
123
|
"timeout": 5
|
|
132
124
|
}
|
|
133
125
|
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"matcher": "^(Agent|spawn_agent)$",
|
|
129
|
+
"hooks": [
|
|
130
|
+
{
|
|
131
|
+
"type": "command",
|
|
132
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 routing-outcome",
|
|
133
|
+
"timeout": 5
|
|
134
|
+
}
|
|
135
|
+
]
|
|
134
136
|
}
|
|
135
137
|
],
|
|
136
138
|
"SessionEnd": [
|
|
137
139
|
{
|
|
138
|
-
"matcher": "
|
|
140
|
+
"matcher": ".*",
|
|
139
141
|
"hooks": [
|
|
140
142
|
{
|
|
141
143
|
"type": "command",
|
|
142
|
-
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\"
|
|
144
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 2800 session-snapshot SessionEnd",
|
|
145
|
+
"timeout": 3
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"type": "command",
|
|
149
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 2800 learn-flush",
|
|
143
150
|
"timeout": 3
|
|
144
151
|
}
|
|
145
152
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_note": "OUT-OF-SHIM HOOK CONTRACTS (ADR-055
|
|
2
|
+
"_note": "OUT-OF-SHIM HOOK CONTRACTS (ADR-055 \u00a76). The per-hook contract \u2014 event, matcher, layer, codeRoot, mode, offBehavior, failureBehavior, timeout, budgets, state, dependencies, reachesStrangers, owner \u2014 is carried NATIVELY by plugin/scripts/hook-shim.mjs's dispatch table for every shim-routed registration. Everything registered outside that table has nowhere to carry it, which is how the whole non-plugin two thirds of this machine's mesh ended up with no declared answer to 'what happens when the brain is off' (ADR-055 F14). This file is the other half: a checked-in contract for every registration this REPO owns but does not route through the shim. scripts/hook-registry.mjs reads both, and tests/unit/hook-registry-lint.test.mjs fails any repo-owned registration absent from both \u2014 so a new one cannot be added silently. SCOPE, deliberately: this file declares only registrations THIS repo owns (plugin/hooks/hooks.json and this project's .claude/settings.json). It does NOT declare contracts for the machine owner's ~/.claude/settings.json or for third-party plugins \u2014 those are not ours to specify, and inventing an offBehavior for someone else's hook would be exactly the fiction the lint exists to prevent. They stay visible as documented machine-local findings in the lint's expected-red block.",
|
|
3
3
|
"_version": 1,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
@@ -10,44 +10,34 @@
|
|
|
10
10
|
"commandIncludes": "version-bump-gate.sh",
|
|
11
11
|
"mode": "blocking",
|
|
12
12
|
"offBehavior": "run",
|
|
13
|
-
"offReason": "ADR-054's discriminator, applied literally: this is an HONESTY wall, not a retrieval feature. It refuses a commit that changes behaviour without bumping the version
|
|
14
|
-
"failureBehavior": "fail-open
|
|
13
|
+
"offReason": "ADR-054's discriminator, applied literally: this is an HONESTY wall, not a retrieval feature. It refuses a commit that changes behaviour without bumping the version \u2014 the signal every user's updater reads. Nothing about that becomes acceptable because the owner switched the retrieval brain off, and the gate reads git state only: no corpus, no network, no brain.",
|
|
14
|
+
"failureBehavior": "fail-open \u2014 a crash or unparseable payload permits the command and records degraded health; malfunction is never a decision (ADR-055 \u00a71.2)",
|
|
15
15
|
"timeoutSeconds": 5,
|
|
16
16
|
"warmP95BudgetMs": 50,
|
|
17
17
|
"coldBudgetMs": 500,
|
|
18
18
|
"stdoutCapBytes": 4096,
|
|
19
|
-
"stateRead": [
|
|
19
|
+
"stateRead": [
|
|
20
|
+
"git HEAD",
|
|
21
|
+
"plugin/.claude-plugin/plugin.json"
|
|
22
|
+
],
|
|
20
23
|
"stateWritten": [],
|
|
21
|
-
"dependencies": [
|
|
24
|
+
"dependencies": [
|
|
25
|
+
"bash",
|
|
26
|
+
"git"
|
|
27
|
+
],
|
|
22
28
|
"reachesStrangers": false,
|
|
23
|
-
"owner": "ruvnet-brain (this repo only
|
|
29
|
+
"owner": "ruvnet-brain (this repo only \u2014 ADR-053; it enforces a rule that is true here and nowhere else)",
|
|
24
30
|
"note": "Project-scoped on purpose: it lived in the GLOBAL settings until 2026-07-14, taxing every Bash call in 34+ projects with a rule true in one."
|
|
25
31
|
}
|
|
26
32
|
],
|
|
27
33
|
"matcherAllowlist": [
|
|
28
|
-
{
|
|
29
|
-
"layer": "plugin",
|
|
30
|
-
"event": "PreToolUse",
|
|
31
|
-
"matcher": "Write|Edit|Bash",
|
|
32
|
-
"handler": "hijack-ruvnet.sh",
|
|
33
|
-
"reason": "UNANCHORED, known (ADR-055 F4). Claude Code SEARCHES the tool name, so this also selects MultiEdit, NotebookEdit, TodoWrite and BashOutput — an advisory hook firing on four tools nobody chose. Advisory, so the blast radius is wasted latency rather than a wrong refusal.",
|
|
34
|
-
"retiredBy": "ADR-055 build item 3 (four plane dispatchers) — registration changes are SHELL changes and item 2 (battery v2) must land first, so this is recorded, not fixed out of order."
|
|
35
|
-
},
|
|
36
34
|
{
|
|
37
35
|
"layer": "plugin",
|
|
38
36
|
"event": "PreToolUse",
|
|
39
37
|
"matcher": "Bash",
|
|
40
38
|
"handler": "verify-interface.sh",
|
|
41
|
-
"reason": "UNANCHORED, known (ADR-055 F4/F12): also selects BashOutput. One of three blocking walls on the same event that each re-parse the same payload
|
|
42
|
-
"retiredBy": "ADR-055 build item 3 (execution-guard.mjs
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"layer": "plugin",
|
|
46
|
-
"event": "PreToolUse",
|
|
47
|
-
"matcher": "Bash",
|
|
48
|
-
"handler": "design-wall.sh",
|
|
49
|
-
"reason": "UNANCHORED, known (ADR-055 F4/F12) — same pair as verify-interface above, same fix.",
|
|
50
|
-
"retiredBy": "ADR-055 build item 3 (execution-guard.mjs)"
|
|
39
|
+
"reason": "UNANCHORED, known (ADR-055 F4/F12): also selects BashOutput. One of three blocking walls on the same event that each re-parse the same payload \u2014 the parse-once execution-guard is the real fix, not six anchor characters.",
|
|
40
|
+
"retiredBy": "ADR-055 build item 3 (execution-guard.mjs \u2014 ONE registration, ONE parse)"
|
|
51
41
|
},
|
|
52
42
|
{
|
|
53
43
|
"layer": "plugin",
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "POSIX Stable Spine (ADR-023): every hook routes through hook-shim.mjs, which resolves the active generation per invocation. Advisory hooks fail open
|
|
2
|
+
"description": "POSIX Stable Spine (ADR-023): every hook routes through hook-shim.mjs, which resolves the active generation per invocation. Advisory hooks fail open. ADR-067: exactly ONE hook may refuse a given tool call \u2014 decision-gate.mjs composes every policy into one decision. TIMEOUTS ARE A CONTRACT, NOT A GUESS: both decision-gate PreToolUse entries declare 5s, and the gate self-caps at DEFAULT_BUDGET_MS=2000 + MIN_HEADROOM_MS=3000, which is EXACTLY 5000ms \u2014 the relationship is the point, not the numbers. They ran at 10s for six days and that was earned at the time: an audit on 2026-08-13 timed 14 runs in unrelated projects and SIX exceeded 5s (up to 5700ms, including a plain `ls -la` at 5109ms), and because the host KILLS an over-timeout hook and renders it as a FAILED PreToolUse HOOK, the gate was producing the \"ton of hook errors\" this plugin was reported for while refusing nothing. RE-MEASURED 2026-08-19, after the adr-currency two-pass fix landed: the same full shim+gate path now runs 405-438ms in a stranger project and 412-438ms in this repo \u2014 about 10x faster \u2014 so the 10s ceiling was no longer earned by any measurement, and a ceiling nobody needs is charged to the USER, who waits behind it on every single Write and Bash. A budget is widened by evidence and must be narrowed again when the evidence goes away. The number is asserted, not asserted-to: decision-gate.mjs exports DEFAULT_BUDGET_MS + MIN_HEADROOM_MS and tests/unit/decision-gate.test.mjs reads the timeout out of THIS file and goes red if the two stop fitting. Comment keys (_note and friends) must NOT be added to this file: ADR-055 F17 records that Codex's plugin parser rejects them.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -59,36 +59,31 @@
|
|
|
59
59
|
],
|
|
60
60
|
"PreToolUse": [
|
|
61
61
|
{
|
|
62
|
-
"matcher": "Write|Edit|
|
|
62
|
+
"matcher": "^(Write|Edit|MultiEdit|NotebookEdit)$",
|
|
63
63
|
"hooks": [
|
|
64
64
|
{
|
|
65
65
|
"type": "command",
|
|
66
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\"
|
|
66
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" decision-gate write",
|
|
67
67
|
"timeout": 5
|
|
68
68
|
}
|
|
69
69
|
]
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
|
-
"matcher": "^
|
|
72
|
+
"matcher": "^Bash$",
|
|
73
73
|
"hooks": [
|
|
74
74
|
{
|
|
75
75
|
"type": "command",
|
|
76
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\"
|
|
76
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" decision-gate bash",
|
|
77
77
|
"timeout": 5
|
|
78
78
|
}
|
|
79
79
|
]
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
|
-
"matcher": "^(
|
|
82
|
+
"matcher": "^(Task|Agent)$",
|
|
83
83
|
"hooks": [
|
|
84
84
|
{
|
|
85
85
|
"type": "command",
|
|
86
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\"
|
|
87
|
-
"timeout": 5
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
"type": "command",
|
|
91
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" protect-state",
|
|
86
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" route-dispatch || true",
|
|
92
87
|
"timeout": 5
|
|
93
88
|
}
|
|
94
89
|
]
|
|
@@ -102,36 +97,6 @@
|
|
|
102
97
|
"timeout": 5
|
|
103
98
|
}
|
|
104
99
|
]
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"matcher": "Bash",
|
|
108
|
-
"hooks": [
|
|
109
|
-
{
|
|
110
|
-
"type": "command",
|
|
111
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" design-wall",
|
|
112
|
-
"timeout": 5
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"matcher": "^(Write|Edit|MultiEdit)$",
|
|
118
|
-
"hooks": [
|
|
119
|
-
{
|
|
120
|
-
"type": "command",
|
|
121
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" unprompted-speech PreToolUse-write",
|
|
122
|
-
"timeout": 5
|
|
123
|
-
}
|
|
124
|
-
]
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
"matcher": "^Bash$",
|
|
128
|
-
"hooks": [
|
|
129
|
-
{
|
|
130
|
-
"type": "command",
|
|
131
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" unprompted-speech PreToolUse-bash",
|
|
132
|
-
"timeout": 5
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
100
|
}
|
|
136
101
|
],
|
|
137
102
|
"PostToolUse": [
|