cursordoctrine 0.3.3 → 0.4.1

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/INSTALL.md CHANGED
@@ -26,7 +26,7 @@ Do not guess from the shell you happen to be running in — a Windows machine dr
26
26
 
27
27
  Check the prerequisites first:
28
28
 
29
- - Windows: PowerShell 7 (`pwsh`) on PATH, plus `git`. Python 3.9+ if you want the anti-slop scanner (the hooks work without it).
29
+ - Windows: **PowerShell 7 (`pwsh`) on PATH**, plus `git`. Python 3.9+ if you want the anti-slop scanner (the hooks work without it). Confirm with `pwsh -Version` — Windows PowerShell 5.1 (`powershell.exe`) is NOT supported; install PowerShell 7 via `winget install --id Microsoft.PowerShell --source winget` if missing.
30
30
  - Linux: `bash`, `git`, and either `jq` or `python3` (the hooks prefer `jq` and fall back to `python3`; install `jq` if neither is present). Python 3.9+ for the anti-slop scanner.
31
31
 
32
32
  ## 2. Copy the files
@@ -36,7 +36,7 @@ Windows (from the repo root, in pwsh):
36
36
  ```powershell
37
37
  New-Item -ItemType Directory -Force "$HOME\.agents\hooks", "$HOME\.cursor" | Out-Null
38
38
  Copy-Item windows\hooks\* "$HOME\.agents\hooks\" -Force
39
- Copy-Item windows\inject-doctrine.ps1, windows\doctrine.md, windows\USER-RULES.md "$HOME\.cursor\" -Force
39
+ Copy-Item windows\inject-doctrine.ps1, windows\doctrine.md, windows\USER-RULES.md, windows\declared-editing.md, windows\pre-compile.md "$HOME\.cursor\" -Force
40
40
  # hooks.json ships with ~/ placeholders; pwsh -File does NOT expand ~, so
41
41
  # substitute the real profile path (forward slashes) at install time:
42
42
  $h = $HOME -replace '\\', '/'
@@ -51,7 +51,7 @@ Linux (from the repo root, in bash):
51
51
  ```bash
52
52
  mkdir -p ~/.agents/hooks ~/.cursor
53
53
  cp linux/hooks/* ~/.agents/hooks/
54
- cp linux/inject-doctrine.sh linux/doctrine.md linux/USER-RULES.md ~/.cursor/
54
+ cp linux/inject-doctrine.sh linux/doctrine.md linux/USER-RULES.md linux/declared-editing.md linux/pre-compile.md ~/.cursor/
55
55
  cp linux/hooks.json ~/.cursor/hooks.json
56
56
  chmod +x ~/.agents/hooks/*.sh ~/.cursor/inject-doctrine.sh
57
57
  # anti-slop skill (SKILL.md + the scanner the final-review hook runs):
@@ -76,6 +76,11 @@ echo '{"conversation_id":"t1","status":"completed"}' | bash ~/.agents/hooks/fina
76
76
  echo '{"conversation_id":"t2","file_path":"/tmp/x.py"}' | bash ~/.agents/hooks/self-review-trigger.sh
77
77
  echo '{"conversation_id":"t2","status":"completed"}' | bash ~/.agents/hooks/subagent-stop-review.sh # expect {"followup_message": "SUBAGENT FINAL REVIEW ..."} once, then {}
78
78
  echo '{"conversation_id":"t2"}' | bash ~/.agents/hooks/post-tool-use.sh # drain t2's leftover feedback file
79
+ # anchor-set nudge: fires PRE-COMPILE NUDGE on the first edit, silent on the second
80
+ echo '{"conversation_id":"t3","file_path":"/tmp/x.py"}' | bash ~/.agents/hooks/anchor-set-nudge.sh
81
+ echo '{"conversation_id":"t3"}' | bash ~/.agents/hooks/post-tool-use.sh # expect additional_context containing "PRE-COMPILE NUDGE"
82
+ echo '{"conversation_id":"t3","file_path":"/tmp/y.py"}' | bash ~/.agents/hooks/anchor-set-nudge.sh # second edit -> flag armed -> silent
83
+ echo '{"conversation_id":"t3"}' | bash ~/.agents/hooks/post-tool-use.sh # expect {} (nothing new stashed)
79
84
  echo '{}' | bash ~/.cursor/inject-doctrine.sh # expect {"additional_context": ...}
80
85
  python3 ~/.cursor/skills/anti-slop/scripts/scan_slop.py --help # expect usage text (final review's scanner)
81
86
  ```
@@ -91,6 +96,10 @@ Windows (same payloads, swap `bash ~/...sh` for `pwsh.exe -NoProfile -File $HOME
91
96
  echo '{"command":"git push --force"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\permission-gate.ps1
92
97
  echo '{"conversation_id":"t2","file_path":"C:\tmp\x.py"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\self-review-trigger.ps1
93
98
  echo '{"conversation_id":"t2","status":"completed"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\subagent-stop-review.ps1 # SUBAGENT FINAL REVIEW once, then {}
99
+ # anchor-set nudge: fires PRE-COMPILE NUDGE on the first edit, silent on the second
100
+ echo '{"conversation_id":"t3","file_path":"C:\tmp\x.py"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\anchor-set-nudge.ps1
101
+ echo '{"conversation_id":"t3"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\post-tool-use.ps1 # additional_context with "PRE-COMPILE NUDGE"
102
+ echo '{"conversation_id":"t3","file_path":"C:\tmp\y.py"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\anchor-set-nudge.ps1 # second edit -> silent
94
103
  python $HOME\.cursor\skills\anti-slop\scripts\scan_slop.py --help
95
104
  ```
96
105
 
@@ -99,15 +108,16 @@ Also validate the config: `~/.cursor/hooks.json` must parse as JSON.
99
108
  ## 4. Verify inside Cursor
100
109
 
101
110
  1. Restart Cursor (hooks.json is read at startup).
102
- 2. Open any project and start a new agent chat. The doctrine should be in context — ask the agent "what does your doctrine say about diffs?" and it should answer from §2.
103
- 3. Have the agent make a small edit to a tracked file. On the next turn it should receive a `SELF-REVIEW TRIGGER` message.
111
+ 2. Open any project and start a new agent chat. The doctrine should be in context — ask the agent "what does your doctrine say about diffs?" and it should answer from §2; ask "what is the Anchor Set?" and it should answer from `pre-compile.md` (Objective / Constraints / Scope / Deterministic success).
112
+ 3. Have the agent make a small edit to a tracked file. On the next turn it should receive a `SELF-REVIEW TRIGGER` message, and (if it's the first edit of the implementation) a `PRE-COMPILE NUDGE` reminder to write its Anchor Set to `.scope.json`.
104
113
  4. Ask the agent to run `git push --force` (in a throwaway repo). The permission gate must block it.
105
114
  5. Finish a small implementation and stop. A single `FINAL REVIEW` follow-up should fire — exactly once.
106
115
  6. Delegate a small edit to a subagent (e.g. ask the agent to "use a generalPurpose subagent to add a comment to <file>"). The subagent should receive one `SUBAGENT FINAL REVIEW` follow-up before returning, and the parent should see `SUBAGENT WORK DETECTED` at its next tool boundary. (`subagentStop` is only read at startup — if nothing fires, restart Cursor again.)
107
116
  7. Type `/anti-slop` in a chat (or say "remove the AI slop") — the anti-slop skill should load and run the scanner as its first step.
117
+ 8. (Optional, opt-in scope gate) Have the agent write a `.scope.json` in the repo root (`intent` + `files[]` + `acceptance`), then edit a file outside `files[]`. The next turn should carry a `[SCOPE VIOLATION]` advisory quoting the declared `intent` and `acceptance`. Delete the file to disable the gate.
108
118
 
109
119
  ## 5. Report
110
120
 
111
121
  Tell the user what was installed, which checks passed, and anything that failed with the exact error. Do not silently work around a failing check.
112
122
 
113
- Kill switches if something misbehaves: `HOOKS_ENFORCE=0` (everything advisory off), `PERM_GATE_ENFORCE=0`, `MINIMAL_EDITING_ENFORCE=0` (deprecated in 0.3.0), `SEMANTIC_DENSITY_ENFORCE=0`, `SCOPE_GATE_ENFORCE=0`, `ANTI_SLOP_ENFORCE=0`, `FINAL_REVIEW_ENFORCE=0`, `SUBAGENT_REVIEW_ENFORCE=0`.
123
+ Kill switches if something misbehaves: `HOOKS_ENFORCE=0` (everything advisory off), `PERM_GATE_ENFORCE=0`, `ANCHOR_NUDGE_ENFORCE=0` (pre-compile nudge off), `MINIMAL_EDITING_ENFORCE=0` (deprecated in 0.3.0), `SEMANTIC_DENSITY_ENFORCE=0`, `SCOPE_GATE_ENFORCE=0`, `ANTI_SLOP_ENFORCE=0`, `FINAL_REVIEW_ENFORCE=0`, `SUBAGENT_REVIEW_ENFORCE=0`.
package/README.md CHANGED
@@ -9,8 +9,8 @@
9
9
 
10
10
  <div align="center">
11
11
  <h1>cursordoctrine</h1>
12
- <p><strong>Thin self-review hooks for Cursor.</strong></p>
13
- <p>Five hook events, one message bus.<br />The model audits its own work. Cursor carries context and gates blast radius.</p>
12
+ <p><strong>Self-review hooks for Cursor — proactive and reactive.</strong></p>
13
+ <p>Five hook events, one message bus.<br />The model compiles its intent, audits its own work, and stays on the rails. Cursor carries context and gates blast radius.</p>
14
14
  </div>
15
15
 
16
16
  <br />
@@ -19,18 +19,35 @@
19
19
 
20
20
  ## What this is
21
21
 
22
- Cursor hooks that make the agent review its own edits without bolting a static-analysis pipeline onto every keystroke. No regex army, no scoring engine. Three jobs:
22
+ Cursor hooks that make the agent review its own edits without bolting a static-analysis pipeline onto every keystroke. No regex army, no scoring engine. Four jobs:
23
23
 
24
- 1. **Inject the doctrine** at session start every chat starts with the same short governing text (`doctrine.md`, `USER-RULES.md`, and `declared-editing.md`, the YAGNI ultra ladder that stops over-building before a line gets written).
25
- 2. **Hand the model its own edits back** after each agent edit, a self-review prompt goes into a pending file (plus minimal-edit, semantic-density, and anti-slop advisories when they trip). Next turn the model reads its diff, fixes real bugs, stays quiet otherwise.
26
- 3. **Gate blast radius** — one permission gate denies a short explicit list of dangerous commands (`rm -rf /`, `curl | sh`, force-push, `npm publish`, ...). Everything else passes.
24
+ 1. **Compile intent before coding** (proactive) — at session start the agent gets the doctrine plus the **Anchor Set** discipline (`pre-compile.md`): before writing code it must emit *Objective / Constraints / Scope / Deterministic success*, and write that contract to `.scope.json`. On the first edit of each implementation a `anchor-set-nudge` advisory fires to catch intent dilution at token ~50 instead of waiting for the stop-hook review at token ~5000.
25
+ 2. **Inject the doctrine** at session startevery chat starts with the same short governing text (`doctrine.md`, `USER-RULES.md`, `declared-editing.md` the YAGNI ultra ladder, and `pre-compile.md` the thin intent-compilation phase).
26
+ 3. **Hand the model its own edits back** (reactive) after each agent edit, a self-review prompt goes into a pending file (plus semantic-density, scope-gate, anti-slop, and the pre-compile nudge advisories when they trip). Next turn the model reads its diff, fixes real bugs, stays quiet otherwise.
27
+ 4. **Gate blast radius** — one permission gate denies a short explicit list of dangerous commands (`rm -rf /`, `curl | sh`, force-push, `npm publish`, ...). Everything else passes.
27
28
 
28
- When an implementation finishes, the stop hook runs one final review over everything that changed, then stops. Five axes. The first is **intent trace**: the hook pulls your last user message from the transcript and prepends it to the review so the model has to tie every diff hunk to a concrete request. Anything it can't trace is a hallucinated requirement and gets reverted. That's the only check that catches "clean code, wrong feature" — linters and later axes miss it.
29
+ When an implementation finishes, the stop hook runs one final review over everything that changed, then stops. Six axes. The first is **intent trace**: the hook pulls your last user message from the transcript and prepends it to the review so the model has to tie every diff hunk to a concrete request. Anything it can't trace is a hallucinated requirement and gets reverted. That's the only check that catches "clean code, wrong feature" — linters and later axes miss it.
29
30
 
30
31
  Subagents get the same treatment. If a delegated run edited files, it reviews its own work before the result goes back to the parent. Those edits fold into the parent's final review. Every bound is enforced twice: in the script and in `hooks.json`.
31
32
 
32
33
  Cursor only. Installs into `~/.cursor` and `~/.agents/hooks`. Doesn't touch your projects.
33
34
 
35
+ ## Prerequisites
36
+
37
+ > **PowerShell 7 (`pwsh`) is required on Windows.** The hooks run via `pwsh.exe -NoProfile -File ...`. Windows PowerShell 5.1 (`powershell.exe`) is not supported — install PowerShell 7 separately.
38
+
39
+ | Platform | Required | Optional (recommended) |
40
+ |---|---|---|
41
+ | **Windows** | `git`, **PowerShell 7 (`pwsh`) on PATH** | Python 3.9+ (powers the anti-slop scanner; hooks work without it) |
42
+ | **Linux / SSH remotes** | `bash`, `git`, and `jq` **or** `python3` | Python 3.9+ (anti-slop scanner) |
43
+
44
+ Install PowerShell 7:
45
+
46
+ - **Windows**: `winget install --id Microsoft.PowerShell --source winget` (or grab the MSI from the [PowerShell GitHub releases](https://github.com/PowerShell/PowerShell/releases)). Confirm with `pwsh -Version`.
47
+ - **Linux**: follow the [official package instructions](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux) — only needed if you run the `windows/` pack on a Linux box (unusual); normal Linux installs use the `linux/` bash pack.
48
+
49
+ `npx cursordoctrine install` warns at the end if `pwsh` (Windows) or `bash`+`jq`/`python3` (Linux) or Python are missing, so you'll know up front.
50
+
34
51
  ## Install
35
52
 
36
53
  Node 18+:
@@ -44,38 +61,72 @@ Restart Cursor after install — `hooks.json` is read at startup. `install` is i
44
61
 
45
62
  No Node? Open `INSTALL.md`, paste it into a Cursor agent chat on the target machine, and let the agent copy files and run the checklist. Copy commands are in the same file if you prefer doing it by hand.
46
63
 
47
- Prerequisites: `git` everywhere; `pwsh` on Windows; `bash` plus `jq` or `python3` on Linux.
48
-
49
64
  The anti-slop skill (`skills/anti-slop/` — SKILL.md and the duplication scanner) installs to `~/.cursor/skills/anti-slop/`. The hook checklist (`~/.agents/hooks/anti-slop.md`, 13 items) is the canonical slop detector for per-edit advisories and final-review axis 4. Final review runs the scanner from the skill path first when it's there.
50
65
 
66
+ ## The proactive phase: the Anchor Set
67
+
68
+ The reactive stack (self-review, anti-slop, final-review) only fires *after* code exists. If the agent drifted from the request on its first token, a clean final review of the wrong feature is still the wrong feature. The pre-compile phase puts the right feature on the rails first.
69
+
70
+ `pre-compile.md` (injected at session start alongside the doctrine) asks the agent to emit, terse, before any code:
71
+
72
+ 1. **OBJECTIVE** — one operational sentence. Not "improve X" but "make X return Y when Z".
73
+ 2. **CONSTRAINTS** — local negations: what it will NOT do (no schema migration, no new dep, no refactor of the surrounding function).
74
+ 3. **SCOPE** — files to touch (exact, derived from the objective) and files untouchable.
75
+ 4. **DETERMINISTIC SUCCESS** — the one command, test, or observable check that decides done.
76
+
77
+ It then writes that contract to `.scope.json` in the repo root:
78
+
79
+ ```json
80
+ {
81
+ "intent": "make /api/login return 401 instead of 500 on a bad password",
82
+ "files": ["src/api/login.ts", "tests/login.test.ts"],
83
+ "acceptance": "tests/login.test.ts passes; curl /api/login with wrong password returns 401",
84
+ "allow_growth": false
85
+ }
86
+ ```
87
+
88
+ Two machine-checkable consequences:
89
+
90
+ - **`scope-gate-audit`** (afterFileEdit, opt-in via `.scope.json` existing) audits every edit against `files[]` and quotes `intent` + `acceptance` back on a violation. Editing outside the declared set is the textbook scope-creep signal.
91
+ - **final-review axis 0** (intent trace) traces every diff hunk back to `intent`. Anything untraceable is a hallucinated requirement.
92
+
93
+ On the **first edit of each implementation**, `anchor-set-nudge` drops a reminder into the feedback bus: *did you write your Anchor Set to `.scope.json`?* One nudge per body of work — the flag is cleared at the same per-implementation boundary where the final-review one-shot brake fires, so a long chat with N implementations gets N nudges.
94
+
95
+ The Anchor Set is skipped for trivial one-liners (typo, literal) — the `declared-editing.md` ladder's rung 1 governs when it's overkill.
96
+
51
97
  ## The five flows
52
98
 
53
99
  | Flow | Event | What happens |
54
100
  |---|---|---|
55
- | Session | `sessionStart` | `inject-doctrine` reads doctrine + user rules + declared-editing and emits them as `additional_context`. |
101
+ | Session | `sessionStart` | `inject-doctrine` reads doctrine + user rules + declared-editing + **pre-compile** and emits them as `additional_context`. |
56
102
  | Every turn | `postToolUse` | Folds completed subagents' edit markers into this conversation's marker, then drains the conversation's pending feedback file into `additional_context`. One-shot, keyed by conversation id. |
57
103
  | Shell | `beforeShellExecution` | `permission-gate` checks the command against a deny list. Allow by default, deny by list, fail open. |
58
- | Edit | `afterFileEdit` + `stop` | `self-review-trigger` stashes the review prompt per edit; `minimal-edit-audit` (deprecated in 0.3.0), `semantic-density-audit`, and `anti-slop-audit` append advisories when thresholds trip (new deps / premature abstraction / redundant comments / **semantic opacity**: low-density identifiers like `DataManager`, `process()`, `utils.ts` / Tier 3 operational slop: retry-without-backoff, await-in-loop, telemetry spam); `final-review` fires one end-of-implementation pass. |
104
+ | Edit | `afterFileEdit` + `stop` | **Proactive:** `anchor-set-nudge` fires once per implementation to push the Anchor Set. **Reactive:** `self-review-trigger` stashes the review prompt per edit; `minimal-edit-audit` (deprecated), `semantic-density-audit`, `scope-gate-audit` (opt-in, audits `.scope.json`), and `anti-slop-audit` append advisories when they trip; `final-review` fires one end-of-implementation six-axis pass. |
59
105
  | Subagent | `subagentStop` | `subagent-stop-review` fires one in-subagent final review when a delegated run edited files, before the result returns to the parent. Marker-gated and flag-braked like `final-review`. |
60
106
 
61
107
  ## Layout
62
108
 
63
109
  ```
64
- windows/ PowerShell hooks (pwsh) — install on Windows machines
110
+ windows/ PowerShell 7 hooks (pwsh) — install on Windows machines
65
111
  hooks.json hook wiring for ~/.cursor/hooks.json
66
- inject-doctrine.ps1, doctrine.md, USER-RULES.md, declared-editing.md
67
- hooks/ the eight scripts + the three prompt files
112
+ inject-doctrine.ps1, doctrine.md, USER-RULES.md,
113
+ declared-editing.md, pre-compile.md
114
+ hooks/ the ten hook scripts + hook-common.ps1 (shared) + 3 prompt files
115
+ (anti-slop.md, self-review.md, final-review.md)
68
116
  linux/ bash hooks — install on Linux machines and SSH remotes
69
- hooks.json, inject-doctrine.sh, doctrine.md, USER-RULES.md, declared-editing.md
117
+ hooks.json, inject-doctrine.sh, doctrine.md, USER-RULES.md,
118
+ declared-editing.md, pre-compile.md
70
119
  hooks/ same hooks, ported to bash (jq preferred, python3 fallback)
71
120
  skills/ Cursor agent skills shipped with the package
72
121
  anti-slop/ SKILL.md + the duplication scanner (final review runs it)
122
+ scripts/scope_match.py — the .scope.json matcher shared by
123
+ scope-gate-audit and final-review (returns intent + acceptance)
73
124
  bin/ the npm CLI (npx cursordoctrine install / verify / uninstall)
74
125
  INSTALL.md ready-to-paste prompt that tells a Cursor agent to
75
126
  install the right folder and verify every hook
76
127
  ```
77
128
 
78
- Both folders do the same thing. Windows runs everything through `pwsh.exe`. Linux runs bash, which is what you want on a remote over SSH (check your `~/.ssh/config` host — hooks live on the remote's `$HOME`, not your laptop).
129
+ Both folders do the same thing. Windows runs everything through `pwsh.exe` (PowerShell 7 — Windows PowerShell 5.1 is not supported). Linux runs bash, which is what you want on a remote over SSH (check your `~/.ssh/config` host — hooks live on the remote's `$HOME`, not your laptop).
79
130
 
80
131
  ## Tuning and kill switches
81
132
 
@@ -85,6 +136,7 @@ All hooks fail open and always exit 0. Nothing here can block your session.
85
136
  |---|---|---|
86
137
  | `HOOKS_ENFORCE=0` | on | turns off all advisory hooks at once |
87
138
  | `PERM_GATE_ENFORCE=0` | on | disables the permission gate |
139
+ | `ANCHOR_NUDGE_ENFORCE=0` | on | disables the pre-compile nudge (first-edit Anchor Set reminder) |
88
140
  | `MINIMAL_EDITING_ENFORCE=0` | on | disables the over-edit advisory (deprecated in 0.3.0) |
89
141
  | `SCOPE_GATE_ENFORCE=0` | on | disables the declared-scope advisory (opt-in: only fires when `.scope.json` exists) |
90
142
  | `SEMANTIC_DENSITY_ENFORCE=0` | on | disables the semantic-opacity advisory |
@@ -98,7 +150,8 @@ All hooks fail open and always exit 0. Nothing here can block your session.
98
150
 
99
151
  - **State lives under `$HOME`**, in `~/.cursor/.hooks-pending/`, keyed by conversation id. No repo litter. Concurrent sessions can't drain each other's prompts. Stale state older than 7 days gets swept on every stop.
100
152
  - **`afterFileEdit` output isn't consumed by Cursor**, so edit hooks write to a pending file and `post-tool-use` re-emits it at the next tool boundary. That's the whole message bus.
101
- - **One review per implementation.** The stop hook arms a per-conversation flag before emitting its follow-up, so a crash can't re-fire it and a long chat still gets a review after each implementation.
153
+ - **One review per implementation.** The stop hook arms a per-conversation flag before emitting its follow-up, so a crash can't re-fire it and a long chat still gets a review after each implementation. The `anchor-set-nudge` flag and the review flag clear together at that boundary — one nudge + one review per body of work.
154
+ - **The `.scope.json` contract is opt-in.** No `.scope.json` in the repo root → `scope-gate-audit` stays silent and the system falls back to the `declared-editing` ladder plus the final-review footprint check. Writing the file is how the agent opts into a machine-checked scope.
102
155
  - **Subagents are first-class.** `afterFileEdit` fires inside subagents keyed by the subagent's conversation id. The harness normalizes agent edits (incl. `StrReplace`) to tool type `Write`, and `postToolUse` never fires for the `Task` tool — verified by payload capture. Matchers cover `Write|StrReplace|EditNotebook` defensively. `subagentStop` reviews the subagent in its own context. The parent folds orphaned subagent markers (from the `subagents/` transcript directory) into its own at every tool boundary and at stop.
103
156
 
104
157
  Self-contained. No build. Open `hooks.json` and read it — that's the whole system in one file.
package/bin/cli.mjs CHANGED
@@ -40,7 +40,7 @@ const pendingDir = join(cursorDst, '.hooks-pending');
40
40
  const hooksJsonDst = join(cursorDst, 'hooks.json');
41
41
 
42
42
  const injectName = platform === 'windows' ? 'inject-doctrine.ps1' : 'inject-doctrine.sh';
43
- const doctrineFiles = [injectName, 'doctrine.md', 'USER-RULES.md', 'declared-editing.md'];
43
+ const doctrineFiles = [injectName, 'doctrine.md', 'USER-RULES.md', 'declared-editing.md', 'pre-compile.md'];
44
44
 
45
45
  function payloadHookFiles() {
46
46
  return readdirSync(join(payload, 'hooks'));
@@ -263,6 +263,22 @@ function verify() {
263
263
  return true;
264
264
  });
265
265
 
266
+ check('anchor-set nudge fires on first edit, then goes quiet', () => {
267
+ // First edit of the implementation -> the pre-compile nudge stashes its
268
+ // advisory in the pending feedback bus and arms the one-shot flag.
269
+ const first = runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'x.py') });
270
+ if (!first.includes('additional_context') || !first.includes('PRE-COMPILE NUDGE')) {
271
+ // anchor-set-nudge appends to feedback-<cid>.txt (the shared bus) rather
272
+ // than emitting JSON directly; drain it the same way post-tool-use does.
273
+ const drained = runHook(hook('post-tool-use'), { conversation_id: 'npxv3' });
274
+ if (!drained.includes('PRE-COMPILE NUDGE')) return { ok: false, detail: 'nudge did not reach the feedback bus on first edit' };
275
+ }
276
+ // Second edit -> flag is armed, nudge must stay silent.
277
+ const second = runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'y.py') });
278
+ if (second.includes('PRE-COMPILE NUDGE')) return { ok: false, detail: 'nudge re-fired on second edit (flag not gating)' };
279
+ return true;
280
+ });
281
+
266
282
  check('doctrine injection emits additional_context', () =>
267
283
  runHook(join(cursorDst, injectName), {}).includes('additional_context'));
268
284
 
@@ -372,6 +388,7 @@ Examples
372
388
  Kill switches (environment variables, all hooks fail open)
373
389
  HOOKS_ENFORCE=0 everything advisory off
374
390
  PERM_GATE_ENFORCE=0 permission gate off
391
+ ANCHOR_NUDGE_ENFORCE=0 pre-compile nudge off (first-edit Anchor Set reminder)
375
392
  MINIMAL_EDITING_ENFORCE=0 over-edit advisory off (deprecated in 0.3.0)
376
393
  SEMANTIC_DENSITY_ENFORCE=0 semantic-opacity advisory off
377
394
  SCOPE_GATE_ENFORCE=0 declared-scope advisory off
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+ # anchor-set-nudge.sh - afterFileEdit "pre-compile" nudge (Cursor, Linux).
3
+ #
4
+ # Proactive counterpart to the reactive audits. On the FIRST file edit of an
5
+ # implementation (per conversation), remind the agent to compile its Anchor Set
6
+ # (pre-compile.md) and write .scope.json BEFORE piling on more code. The
7
+ # reactive stack (self-review, anti-slop, final-review) only fires AFTER code
8
+ # exists; this nudge catches intent dilution at token ~50, not at the ~5000 of
9
+ # the stop-hook axis 0. A clean final review of the wrong feature is still the
10
+ # wrong feature - the Anchor Set exists so the right feature is on the rails
11
+ # from the first edit.
12
+ #
13
+ # One-shot PER IMPLEMENTATION, not per session: gated by an
14
+ # anchor-declared-<cid>.flag in the pending dir. That flag is cleared by
15
+ # final-review.sh / subagent-stop-review.sh at the SAME per-implementation
16
+ # boundary where they clear session-edits-<cid>.txt and reviewed-<cid>.flag
17
+ # (the stop after a review pass). So a long conversation with N implementations
18
+ # gets N nudges, not one - every new body of work re-earns the reminder.
19
+ #
20
+ # Advisory only: never blocks, never reads the diff, ALWAYS exits 0. Appends to
21
+ # the shared feedback-<cid>.txt bus; post-tool-use.sh delivers it next turn.
22
+ # Disable: HOOKS_ENFORCE=0 or ANCHOR_NUDGE_ENFORCE=0
23
+
24
+ set +e
25
+ . "$(dirname "$0")/hook-common.sh"
26
+
27
+ [ "${HOOKS_ENFORCE:-}" = "0" ] && exit 0
28
+ [ "${ANCHOR_NUDGE_ENFORCE:-}" = "0" ] && exit 0
29
+
30
+ input="$(read_hook_stdin)"
31
+ [ -n "$input" ] || exit 0
32
+
33
+ file_path=""
34
+ for k in file_path path filePath; do
35
+ file_path="$(json_get "$input" "$k")"
36
+ [ -n "$file_path" ] && break
37
+ done
38
+ [ -n "$file_path" ] || exit 0
39
+ is_cursor_config_path "$file_path" && exit 0
40
+
41
+ cid="$(safe_conversation_id "$input")"
42
+ pending_dir="$(hooks_pending_dir)"
43
+ flag="$pending_dir/anchor-declared-$cid.flag"
44
+
45
+ # Already nudged this implementation -> stay quiet. The flag is cleared at the
46
+ # per-implementation boundary in final-review.sh / subagent-stop-review.sh.
47
+ [ -f "$flag" ] && exit 0
48
+
49
+ msg="PRE-COMPILE NUDGE - first edit of this implementation: $file_path
50
+
51
+ Before you keep going, did you compile your Anchor Set (pre-compile.md)?
52
+ 1. OBJECTIVE - one operational sentence. What is strictly necessary.
53
+ 2. CONSTRAINTS - local negations (what you will NOT do).
54
+ 3. SCOPE - files to touch, files untouchable.
55
+ 4. SUCCESS - the one deterministic check that decides done.
56
+
57
+ If you have not already, write it to .scope.json now (intent / files /
58
+ acceptance / allow_growth). The scope-gate audits every edit against files[],
59
+ and final-review axis 0 traces every diff hunk back to intent. An Anchor Set
60
+ that lives only in your head is not an Anchor Set - the gate cannot audit it.
61
+
62
+ Skip this for trivial one-liners (typo, literal). Otherwise: compile, then code."
63
+
64
+ # Append to the shared pending file (same bus as the other advisories).
65
+ pending="$pending_dir/feedback-$cid.txt"
66
+ mkdir -p "$pending_dir" 2>/dev/null
67
+ if [ -s "$pending" ]; then
68
+ printf '\n\n---\n\n%s' "$msg" >> "$pending" 2>/dev/null
69
+ else
70
+ printf '%s' "$msg" >> "$pending" 2>/dev/null
71
+ fi
72
+
73
+ # Arm the one-shot brake BEFORE returning, so a crash after the append can't
74
+ # re-nudge on the next edit. Mirrors the arming order in final-review.sh.
75
+ touch "$flag" 2>/dev/null
76
+
77
+ exit 0
@@ -36,13 +36,16 @@ cid="$(safe_conversation_id "$input")"
36
36
  pending_dir="$(hooks_pending_dir)"
37
37
  marker="$pending_dir/session-edits-$cid.txt"
38
38
  flag="$pending_dir/reviewed-$cid.flag"
39
+ anchor_flag="$pending_dir/anchor-declared-$cid.flag"
39
40
 
40
41
  # Sweep state from sessions that died before their stop hook ran.
41
42
  find "$pending_dir" -maxdepth 1 -type f -mtime +7 -delete 2>/dev/null
42
43
 
43
44
  # One-shot brake: the previous stop for this conversation emitted the review.
45
+ # Also clear anchor-declared-<cid>.flag so the pre-compile nudge re-fires for
46
+ # the NEXT implementation (one nudge per body of work, not per session).
44
47
  if [ -f "$flag" ]; then
45
- rm -f "$flag" "$marker" 2>/dev/null
48
+ rm -f "$flag" "$marker" "$anchor_flag" 2>/dev/null
46
49
  emit_none
47
50
  fi
48
51
 
@@ -91,6 +94,14 @@ Fix now, re-run the scan + tests, then stop. If an axis is clean, say so in one
91
94
  fi
92
95
  body="$(expand_agent_paths "$body")"
93
96
 
97
+ # Regla R1 (re-entry): if this review pass is a re-audit after a failed gate or
98
+ # axis, suppress History Propagation - the model must NOT build on its own prior
99
+ # wrong diff. Reset its prior to the Anchor Set, not to its previous attempt.
100
+ reentry_line="
101
+
102
+ RE-ENTRY RULE (Regla R1): if a gate or axis failed, forget the approach that produced it. Re-read your ORIGINAL REQUEST above and your Anchor Set (.scope.json, if you wrote one). Fix ONLY what is failing. Do not refactor in this pass - that is History Propagation, the exact failure mode the Anchor Set exists to prevent.
103
+ "
104
+
94
105
  file_list=""
95
106
  while IFS= read -r p; do
96
107
  [ -n "$p" ] || continue
@@ -127,7 +138,7 @@ msg="FINAL REVIEW (end of implementation) - intent, correctness, reliability, co
127
138
  ${surface_block}${intent_block}Files you changed this session:
128
139
  $file_list
129
140
 
130
- $body"
141
+ ${body}${reentry_line}"
131
142
 
132
143
  # Arm the one-shot brake BEFORE emitting, so a crash after emit can't re-fire.
133
144
  touch "$flag" 2>/dev/null
@@ -78,8 +78,10 @@ if p.get("in_scope"):
78
78
  sys.exit(3) # in scope -> clean
79
79
  allow_growth = "1" if p.get("allow_growth") else "0"
80
80
  intent = p.get("intent", "")
81
+ acceptance = p.get("acceptance", "")
81
82
  print(f"__AG__{allow_growth}")
82
83
  print(f"__INTENT__{intent}")
84
+ print(f"__ACCEPT__{acceptance}")
83
85
  sys.exit(0)
84
86
  PYEOF
85
87
  }
@@ -90,6 +92,7 @@ rc=$?
90
92
 
91
93
  allow_growth="$(printf '%s\n' "$parsed" | grep '__AG__' | sed 's/__AG__//')"
92
94
  intent="$(printf '%s\n' "$parsed" | grep '__INTENT__' | sed 's/__INTENT__//')"
95
+ acceptance="$(printf '%s\n' "$parsed" | grep '__ACCEPT__' | sed 's/__ACCEPT__//')"
93
96
 
94
97
  # Read declared files for the message (best-effort)
95
98
  declared_files="$(printf '%s' "$scope_file" | "$py" -c "
@@ -102,16 +105,29 @@ except Exception:
102
105
  " "$scope_file" 2>/dev/null)"
103
106
 
104
107
  # --- compose advisory ------------------------------------------------------
108
+ # acceptance line: only quote it when the agent declared one. A blank acceptance
109
+ # means the Anchor Set was incomplete - surface that gap, since the whole point
110
+ # of the pre-compile phase is to name the deterministic success check.
111
+ if [ -n "$acceptance" ]; then
112
+ acceptance_line="$acceptance"
113
+ else
114
+ acceptance_line="(not declared - your Anchor Set is missing the EXITO/acceptance field)"
115
+ fi
116
+
105
117
  if [ "$allow_growth" = "1" ]; then
106
118
  summary="Scope note - $rel is new vs your declared scope (growth allowed)"
107
119
  body=" You touched a file outside your initial declared set. Since allow_growth is
108
120
  true, this is not a violation, but justify it: add $rel to .scope.json or
109
- explain why the scope grew."
121
+ explain why the scope grew.
122
+
123
+ Your success contract (acceptance): $acceptance_line
124
+ Does growing into $rel still serve that?"
110
125
  else
111
126
  summary="[SCOPE VIOLATION] $rel is NOT in your declared scope"
112
127
  body=" Your contract (.scope.json):
113
128
  intent: $intent
114
129
  files: $declared_files
130
+ acceptance: $acceptance_line
115
131
 
116
132
  You declared these files and touched one outside the set. Either:
117
133
  1. Add $rel to .scope.json with a one-line justification, OR
@@ -4,7 +4,7 @@
4
4
  # Counterpart of final-review.sh for delegated work. afterFileEdit DOES fire
5
5
  # inside subagents (verified: a subagent run left its edits in
6
6
  # session-edits-<subagent-cid>.txt), but subagents get no `stop` event, so
7
- # that marker is never drained and the five-axis review never fires for
7
+ # that marker is never drained and the six-axis review never fires for
8
8
  # delegated implementations. This hook closes the loop: when a subagent
9
9
  # finishes and ITS conversation has a session-edits marker, return ONE
10
10
  # followup_message so the subagent audits its own implementation before the
@@ -44,10 +44,13 @@ cid="$(safe_conversation_id "$input")"
44
44
  pending_dir="$(hooks_pending_dir)"
45
45
  marker="$pending_dir/session-edits-$cid.txt"
46
46
  flag="$pending_dir/reviewed-$cid.flag"
47
+ anchor_flag="$pending_dir/anchor-declared-$cid.flag"
47
48
 
48
49
  # One-shot brake: the previous subagentStop for this id emitted the review.
50
+ # Also clear anchor-declared-<cid>.flag so the pre-compile nudge re-fires for
51
+ # the next subagent implementation (one nudge per body of work).
49
52
  if [ -f "$flag" ]; then
50
- rm -f "$flag" "$marker" 2>/dev/null
53
+ rm -f "$flag" "$marker" "$anchor_flag" 2>/dev/null
51
54
  emit_none
52
55
  fi
53
56
 
@@ -80,6 +83,14 @@ If an axis is clean, say so in one line. Then stop.'
80
83
  fi
81
84
  body="$(expand_agent_paths "$body")"
82
85
 
86
+ # Regla R1 (re-entry): same suppression as final-review.sh. A subagent that
87
+ # failed an axis must not build on its own prior wrong diff - reset its prior
88
+ # to the Anchor Set, not to its previous attempt.
89
+ reentry_line="
90
+
91
+ RE-ENTRY RULE (Regla R1): if an axis failed, forget the approach that produced it. Re-read your original task and your Anchor Set (.scope.json, if you wrote one). Fix ONLY what is failing. Do not refactor in this pass.
92
+ "
93
+
83
94
  file_list=""
84
95
  while IFS= read -r p; do
85
96
  [ -n "$p" ] || continue
@@ -94,7 +105,7 @@ msg="SUBAGENT FINAL REVIEW - you just finished delegated implementation work. Be
94
105
  Files you changed this run:
95
106
  $file_list
96
107
 
97
- $body"
108
+ ${body}${reentry_line}"
98
109
 
99
110
  # Arm the one-shot brake BEFORE emitting, so a crash after emit can't re-fire.
100
111
  touch "$flag" 2>/dev/null
package/linux/hooks.json CHANGED
@@ -38,6 +38,12 @@
38
38
  "timeout": 15,
39
39
  "matcher": "^(Write|StrReplace|EditNotebook)$",
40
40
  "_comment": "15s: AI-slop advisory, companion to minimal-edit-audit. git diff flags new deps / premature abstractions (Factory/Repository/Mediator/CQRS/DDD) / redundant comments, and injects the anti-slop.md self-review checklist on substantial edits (>= ANTI_SLOP_CHECKLIST_LINES, default 40). Appends to the conversation's pending file; never blocks. Disable: HOOKS_ENFORCE=0 or ANTI_SLOP_ENFORCE=0."
41
+ },
42
+ {
43
+ "command": "bash ~/.agents/hooks/anchor-set-nudge.sh",
44
+ "timeout": 5,
45
+ "matcher": "^(Write|StrReplace|EditNotebook)$",
46
+ "_comment": "5s: PROACTIVE pre-compile nudge. On the FIRST edit of each implementation (per conversation), remind the agent to compile its Anchor Set (pre-compile.md) into .scope.json BEFORE piling on more code. The reactive audits (self-review / anti-slop / final-review axis 0) only fire after code exists; this catches intent dilution at token ~50 instead of ~5000. One-shot per implementation: gated by anchor-declared-<cid>.flag, which final-review.sh / subagent-stop-review.sh clear at the same per-implementation boundary as reviewed-<cid>.flag. Advisory only; never blocks. Disable: HOOKS_ENFORCE=0 or ANCHOR_NUDGE_ENFORCE=0."
41
47
  }
42
48
  ],
43
49
  "postToolUse": [
@@ -16,7 +16,7 @@ set +e
16
16
  cat >/dev/null
17
17
 
18
18
  context=""
19
- for p in "$HOME/.cursor/doctrine.md" "$HOME/.cursor/USER-RULES.md" "$HOME/.cursor/declared-editing.md"; do
19
+ for p in "$HOME/.cursor/doctrine.md" "$HOME/.cursor/USER-RULES.md" "$HOME/.cursor/declared-editing.md" "$HOME/.cursor/pre-compile.md"; do
20
20
  if [ -f "$p" ]; then
21
21
  part="$(cat "$p")"
22
22
  if [ -n "$context" ]; then context="$context
@@ -0,0 +1,72 @@
1
+ # Pre-compile — Thin Intent Compilation
2
+
3
+ ACTIVE EVERY IMPLEMENTATION TURN. Before writing or modifying a single line of
4
+ code, emit your Anchor Set. Compiling intent first is what stops the dilution
5
+ that no later axis can fully undo — a clean final review of the wrong feature
6
+ is still the wrong feature.
7
+
8
+ This is the proactive phase. The anti-slop checklist, the self-review trigger
9
+ and the final review are reactive — they audit after the fact. You compile the
10
+ intent BEFORE the first token of code so they have the right thing to audit.
11
+
12
+ ## The Anchor Set
13
+
14
+ Answer these four, terse, in your first response. One phrase each, not prose:
15
+
16
+ 1. OBJECTIVE — one operational sentence. What is *strictly* necessary. Not
17
+ "improve X" — "make X return Y when Z".
18
+ 2. CONSTRAINTS (local negations) — what you will NOT do. "NO schema migration.
19
+ NO new dependency. NO refactor of the surrounding function." Negations bind
20
+ harder than the objective: a constraint that the task contradicts is a bug
21
+ in your reading of the task, and you ask before you override it.
22
+ 3. SCOPE —
23
+ - FILES TO TOUCH: exact list, derived from the objective, nothing speculative.
24
+ - FILES UNTOUCHABLE: anything the system marked off-limits (.cursor state,
25
+ lockfiles you weren't asked to touch, files outside the request's blast
26
+ radius).
27
+ 4. DETERMINISTIC SUCCESS — the one command, test, or observable check that
28
+ will decide whether this is done. "Tests pass" is not deterministic; the
29
+ specific failing test going green is. If you cannot name one, you do not
30
+ yet understand the task — ask.
31
+
32
+ ## Materialize it: .scope.json
33
+
34
+ Write the Anchor Set to `.scope.json` in the repo root before editing source.
35
+ This is the machine-checkable form — the scope-gate hook audits every edit
36
+ against `files[]`, and the final-review axis 0 traces every diff hunk back to
37
+ `intent`. An Anchor Set that lives only in your head is not an Anchor Set.
38
+
39
+ ```json
40
+ {
41
+ "intent": "<OBJECTIVE>",
42
+ "files": ["<FILES TO TOUCH, repo-relative, glob-friendly>"],
43
+ "acceptance": "<DETERMINISTIC SUCCESS>",
44
+ "allow_growth": false
45
+ }
46
+ ```
47
+
48
+ `allow_growth: false` is the default — the gate fires on any edit outside
49
+ `files[]`. Set it true only if you expect the work to discover new files
50
+ (a refactor, a migration) and you will justify each one as it appears.
51
+
52
+ No need to write `.scope.json` for trivial one-liners (a typo, a literal).
53
+ The declared-editing ladder's rung 1 ("does this need to exist?") governs when
54
+ the Anchor Set itself is overkill. When in doubt, write it.
55
+
56
+ ## Regla R3 — Authority
57
+
58
+ If, during execution, you read logs or code that contradict these anchors,
59
+ **the anchors win.** Prior history in this session is auditor material, not
60
+ authority. An earlier wrong assumption of yours does not override the Anchor
61
+ Set you declared at the start.
62
+
63
+ ## Regla R1 — On re-entry (when the loop hands you back a failure)
64
+
65
+ If the harness returns a gate failure or a failed axis: forget the approach
66
+ that produced it. Re-read your OBJECTIVE and your Anchor Set, not your prior
67
+ diff. Fix ONLY what is failing. Do not refactor in the same pass — that is
68
+ History Propagation, the failure mode the Anchor Set exists to prevent.
69
+
70
+ ---
71
+
72
+ End of pre-compile. Now emit the Anchor Set, then do the work.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cursordoctrine",
3
- "version": "0.3.3",
4
- "description": "Thin self-review hooks for Cursor — the model is the auditor. Intent-trace final review (Tier 0), unified 13-item anti-slop checklist, operational slop detection.",
3
+ "version": "0.4.1",
4
+ "description": "Thin self-review hooks for Cursor — the model is the auditor. Pre-compile Anchor Set phase (proactive intent compilation), intent-trace final review (Tier 0), unified 13-item anti-slop checklist, operational slop detection.",
5
5
  "bin": {
6
6
  "cursordoctrine": "bin/cli.mjs"
7
7
  },
@@ -228,8 +228,8 @@ The `stop` hook (`~/.agents/hooks/final-review.ps1` on Windows,
228
228
  `~/.agents/hooks/final-review.sh` on Linux) fires after the agent finishes an
229
229
  implementation that edited files. It extracts the last `<user_query>` from the
230
230
  session transcript (Tier 0 intent trace), reports session footprint (Tier 5),
231
- and auto-submits a `followup_message` so the model audits five axes: intent,
232
- correctness, reliability, coverage, anti-slop. Axis 4 delegates to this skill's
231
+ and auto-submits a `followup_message` so the model audits six axes: intent,
232
+ correctness, reliability, coverage, anti-slop, wiring completeness. Axis 4 delegates to this skill's
233
233
  scanner (`scan_slop.py --all`) and the canonical checklist at
234
234
  `~/.agents/hooks/anti-slop.md` (13 items, including semantic contracts,
235
235
  operational slop, and change surface). One bounded pass per implementation.