cursordoctrine 0.4.6 → 0.5.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
@@ -76,13 +76,14 @@ 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)
84
- echo '{}' | bash ~/.cursor/inject-doctrine.sh # expect {"additional_context": ...}
85
- python3 ~/.cursor/skills/anti-slop/scripts/scan_slop.py --help # expect usage text (final review's scanner)
79
+ # intent-anchor: writes .scope.json from the current prompt and re-injects it.
80
+ # Needs a repo root in cwd (it will NOT write to $HOME - that's the guard).
81
+ echo '{"conversation_id":"t3","cwd":"/tmp","file_path":"/tmp/x.py"}' | bash ~/.agents/hooks/intent-anchor.sh
82
+ cat /tmp/.scope.json # expect a JSON scaffold with intent placeholder
83
+ echo '{"conversation_id":"t3"}' | bash ~/.agents/hooks/post-tool-use.sh # expect additional_context with the scaffold
84
+ echo '{}' | bash ~/.cursor/inject-doctrine.sh # expect {"additional_context": ...}
85
+ python3 ~/.cursor/skills/anti-slop/scripts/scan_slop.py --help # expect usage text (final review's scanner)
86
+ rm -f /tmp/.scope.json # cleanup the test scaffold
86
87
  ```
87
88
 
88
89
  If the scanner check fails, the final review still works — it falls back to the
@@ -96,11 +97,13 @@ Windows (same payloads, swap `bash ~/...sh` for `pwsh.exe -NoProfile -File $HOME
96
97
  echo '{"command":"git push --force"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\permission-gate.ps1
97
98
  echo '{"conversation_id":"t2","file_path":"C:\tmp\x.py"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\self-review-trigger.ps1
98
99
  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
100
+ # intent-anchor: writes .scope.json from the current prompt and re-injects it.
101
+ # Needs a repo root in cwd (it will NOT write to $HOME - that's the guard).
102
+ echo '{"conversation_id":"t3","cwd":"C:\tmp","file_path":"C:\tmp\x.py"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\intent-anchor.ps1
103
+ Get-Content C:\tmp\.scope.json # expect a JSON scaffold with intent placeholder
104
+ echo '{"conversation_id":"t3"}' | pwsh.exe -NoProfile -File $HOME\.agents\hooks\post-tool-use.ps1 # additional_context with the scaffold
103
105
  python $HOME\.cursor\skills\anti-slop\scripts\scan_slop.py --help
106
+ Remove-Item C:\tmp\.scope.json -Force # cleanup the test scaffold
104
107
  ```
105
108
 
106
109
  Also validate the config: `~/.cursor/hooks.json` must parse as JSON.
@@ -109,7 +112,7 @@ Also validate the config: `~/.cursor/hooks.json` must parse as JSON.
109
112
 
110
113
  1. Restart Cursor (hooks.json is read at startup).
111
114
  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 that turn) a `PRE-COMPILE NUDGE` reminder to write its Anchor Set to `.scope.json`. The nudge re-fires on the first edit of each new turn.
115
+ 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 `intent-anchor` should have written `.scope.json` to the repo root (intent from your prompt, `<TODO>` placeholders for files/acceptance). The scaffold regenerates when your prompt changes and is re-injected every turn.
113
116
  4. Ask the agent to run `git push --force` (in a throwaway repo). The permission gate must block it.
114
117
  5. Finish a small implementation and stop. A single `FINAL REVIEW` follow-up should fire — exactly once.
115
118
  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.)
@@ -120,4 +123,4 @@ Also validate the config: `~/.cursor/hooks.json` must parse as JSON.
120
123
 
121
124
  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.
122
125
 
123
- Kill switches if something misbehaves: `HOOKS_ENFORCE=0` (everything advisory off), `PERM_GATE_ENFORCE=0`, `ANCHOR_NUDGE_ENFORCE=0` (pre-compile nudge off), `INTENT_ANCHOR_ENFORCE=0` (thin-intent re-injection 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`.
126
+ Kill switches if something misbehaves: `HOOKS_ENFORCE=0` (everything advisory off), `PERM_GATE_ENFORCE=0`, `INTENT_ANCHOR_ENFORCE=0` (thin-intent `.scope.json` scaffold/re-injection off), `SEMANTIC_DENSITY_ENFORCE=0`, `SCOPE_GATE_ENFORCE=0`, `ANTI_SLOP_ENFORCE=0`, `FINAL_REVIEW_ENFORCE=0`, `SUBAGENT_REVIEW_ENFORCE=0`.
package/README.md CHANGED
@@ -21,9 +21,9 @@
21
21
 
22
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. **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 agent turn a `anchor-set-nudge` advisory fires to catch intent dilution at token ~50 instead of waiting for the stop-hook review at token ~5000.
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*. `intent-anchor` materializes that as `.scope.json` on the first tool of each turn (auto-created, regenerated when the prompt changes) and re-injects it into context every turn, so the contract stays in focus against Salience Dilution.
25
25
  2. **Inject the doctrine** at session start — every 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.
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, and anti-slop advisories when they trip). Next turn the model reads its diff, fixes real bugs, stays quiet otherwise.
27
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.
28
28
 
29
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.
@@ -90,13 +90,13 @@ Two machine-checkable consequences:
90
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
91
  - **final-review axis 0** (intent trace) traces every diff hunk back to `intent`. Anything untraceable is a hallucinated requirement.
92
92
 
93
- On the **first edit of each agent turn**, `anchor-set-nudge` drops a reminder into the feedback bus: *did you write your Anchor Set to `.scope.json`?* One nudge per turn the latch is armed on that first edit and cleared **unconditionally** by the stop hook on every turn boundary, so the next turn re-earns its nudge. A long chat with N turns gets up to N nudges, and the latch can never get stranded silenced mid-session.
93
+ On the **first tool of each agent turn**, `intent-anchor` materializes the Anchor Set: it writes `.scope.json` to the repo root (regenerating it when the prompt changed) and re-injects it into context. One scaffold per prompt, re-injection per turn. The latch is armed on first fire and cleared **unconditionally** by the stop hook on every turn boundary, so the next turn re-fires and can never get stranded silenced mid-session.
94
94
 
95
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
96
 
97
97
  ### Keeping the contract alive: `intent-anchor` (anti-Salience-Dilution)
98
98
 
99
- Writing `.scope.json` once is not enough. As a conversation fills with code, logs and errors, the token of the original request shrinks to a rounding error against the recent history — *Salience Dilution* — and the agent stops checking the contract it wrote at prompt 1. It forgets symmetry, colors, the acceptance bar. This is the failure mode the nudge alone can't fix (a reminder that the contract exists the contract being in context).
99
+ Writing `.scope.json` once is not enough. As a conversation fills with code, logs and errors, the token of the original request shrinks to a rounding error against the recent history — *Salience Dilution* — and the agent stops checking the contract it wrote at prompt 1. It forgets symmetry, colors, the acceptance bar. Re-injecting the contract every turn is what defeats this: the requirements are re-stated in front of the model before each edit, not just written once and hoped-for.
100
100
 
101
101
  `intent-anchor` (`postToolUse`, registered first so it runs before `post-tool-use` drains the bus) does two things on the **first tool boundary of every turn** (per-turn latch, cleared unconditionally at each stop):
102
102
 
@@ -114,7 +114,7 @@ Crucially, `intent-anchor` carries the **semantic** contract (`intent`/`acceptan
114
114
  | Session | `sessionStart` | `inject-doctrine` reads doctrine + user rules + declared-editing + **pre-compile** and emits them as `additional_context`. |
115
115
  | Every turn | `postToolUse` | **`intent-anchor`** (registered first) re-injects `.scope.json` into `additional_context` at the first tool boundary of each turn — the anti-Salience-Dilution move that keeps `intent` + `acceptance` in the model's attentional focus before edits pile up. If the prompt changed since last turn, it demands the contract be updated. Then `post-tool-use` folds subagent markers and drains the feedback file. |
116
116
  | Shell | `beforeShellExecution` | `permission-gate` checks the command against a deny list. Allow by default, deny by list, fail open. |
117
- | Edit | `afterFileEdit` + `stop` | **Proactive:** `anchor-set-nudge` fires once per turn (on its first edit) 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. |
117
+ | Edit | `afterFileEdit` + `stop` | **Proactive:** `intent-anchor` (`postToolUse`) scaffolds `.scope.json` per prompt and re-injects it each turn. **Reactive:** `self-review-trigger` stashes the review prompt per edit; `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. |
118
118
  | 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`. |
119
119
 
120
120
  ## Layout
@@ -149,9 +149,7 @@ All hooks fail open and always exit 0. Nothing here can block your session.
149
149
  |---|---|---|
150
150
  | `HOOKS_ENFORCE=0` | on | turns off all advisory hooks at once |
151
151
  | `PERM_GATE_ENFORCE=0` | on | disables the permission gate |
152
- | `ANCHOR_NUDGE_ENFORCE=0` | on | disables the pre-compile nudge (first-edit Anchor Set reminder) |
153
- | `INTENT_ANCHOR_ENFORCE=0` | on | disables the thin-intent re-injection (per-turn `.scope.json` echo into `additional_context`) |
154
- | `MINIMAL_EDITING_ENFORCE=0` | on | disables the over-edit advisory (deprecated in 0.3.0) |
152
+ | `INTENT_ANCHOR_ENFORCE=0` | on | disables the thin-intent `.scope.json` scaffold + re-injection |
155
153
  | `SCOPE_GATE_ENFORCE=0` | on | disables the declared-scope advisory (opt-in: only fires when `.scope.json` exists) |
156
154
  | `SEMANTIC_DENSITY_ENFORCE=0` | on | disables the semantic-opacity advisory |
157
155
  | `ANTI_SLOP_ENFORCE=0` | on | disables the slop advisory |
@@ -164,7 +162,7 @@ All hooks fail open and always exit 0. Nothing here can block your session.
164
162
 
165
163
  - **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.
166
164
  - **`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.
167
- - **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` latch is separate and simpler: it's cleared **unconditionally** on every stop, so the nudge re-fires on the first edit of each new turn and can never get stranded silenced mid-session.
165
+ - **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 `intent-anchor` per-turn latch is separate and simpler: it's cleared **unconditionally** on every stop, so the scaffold/re-inject re-fires on the first tool of each new turn and can never get stranded silenced mid-session.
168
166
  - **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.
169
167
  - **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.
170
168
 
package/bin/cli.mjs CHANGED
@@ -284,43 +284,6 @@ function verify() {
284
284
  return true;
285
285
  });
286
286
 
287
- check('anchor-set nudge fires once per turn, stop re-arms it', () => {
288
- // anchor-set-nudge appends to feedback-<cid>.txt (the shared bus) rather
289
- // than emitting JSON directly; drain it the same way post-tool-use does.
290
- const drainedOf = (cidv) => runHook(hook('post-tool-use'), { conversation_id: cidv });
291
-
292
- // --- Turn 1 -------------------------------------------------------------
293
- // First edit -> nudge stashes into the feedback bus and arms the latch.
294
- runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'x.py') });
295
- if (!drainedOf('npxv3').includes('PRE-COMPILE NUDGE')) {
296
- return { ok: false, detail: 'nudge did not reach the feedback bus on first edit' };
297
- }
298
- // Second edit same turn -> latch armed, nudge must stay silent.
299
- runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'y.py') });
300
- if (drainedOf('npxv3').includes('PRE-COMPILE NUDGE')) {
301
- return { ok: false, detail: 'nudge re-fired on second edit (latch not gating)' };
302
- }
303
- // End of turn: final-review clears the latch unconditionally. Drive a
304
- // review-less stop (no session-edits marker) so it hits the clear path and
305
- // exits {}, same as a turn that produced no reviewable edits.
306
- const stopOut = runHook(hook('final-review'), { conversation_id: 'npxv3', status: 'completed' });
307
- if (stopOut !== '{}' && stopOut.replace(/\s/g, '') !== '{}') {
308
- // A review fired (fine - the earlier edit left a session-edits marker via
309
- // self-review-trigger if one ran). What matters is the latch got cleared;
310
- // we verify that with the next-turn re-fire below.
311
- }
312
- // --- Turn 2 -------------------------------------------------------------
313
- // First edit of the NEXT turn -> latch was cleared at the stop boundary, so
314
- // the nudge MUST re-fire. This is the regression that 0.4.0 shipped broken:
315
- // the latch only cleared on the fragile second-stop path, so it stranded
316
- // and the nudge went permanently silent mid-session.
317
- runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'z.py') });
318
- if (!drainedOf('npxv3').includes('PRE-COMPILE NUDGE')) {
319
- return { ok: false, detail: 'nudge did NOT re-fire on the next turn (latch stranded at the stop boundary)' };
320
- }
321
- return true;
322
- });
323
-
324
287
  check('intent-anchor scaffolds .scope.json per-prompt, never to $HOME', () => {
325
288
  // The user-requested behavior: every NEW prompt -> a fresh .scope.json
326
289
  // in the repo root, intent locked from the query. Same prompt -> re-inject
@@ -513,9 +476,7 @@ Examples
513
476
  Kill switches (environment variables, all hooks fail open)
514
477
  HOOKS_ENFORCE=0 everything advisory off
515
478
  PERM_GATE_ENFORCE=0 permission gate off
516
- ANCHOR_NUDGE_ENFORCE=0 pre-compile nudge off (first-edit Anchor Set reminder)
517
- INTENT_ANCHOR_ENFORCE=0 thin-intent re-injection off (per-turn .scope.json echo)
518
- MINIMAL_EDITING_ENFORCE=0 over-edit advisory off (deprecated in 0.3.0)
479
+ INTENT_ANCHOR_ENFORCE=0 thin-intent scaffold/re-injection off
519
480
  SEMANTIC_DENSITY_ENFORCE=0 semantic-opacity advisory off
520
481
  SCOPE_GATE_ENFORCE=0 declared-scope advisory off
521
482
  ANTI_SLOP_ENFORCE=0 slop advisory off
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # anti-slop-audit.sh - afterFileEdit "AI slop" advisory (Cursor, Linux).
3
3
  #
4
- # Companion to minimal-edit-audit.sh. That hook guards ONE slop axis -
5
- # over-editing. This hook guards the rest of the taxonomy: the parts static
6
- # analysis can cheaply and precisely flag, plus a self-review checklist for
7
- # the parts it cannot.
4
+ # Guards the parts of the slop taxonomy static analysis can cheaply and
5
+ # precisely flag, plus a self-review checklist for the parts it cannot.
8
6
  #
9
7
  # Statically flagged (high-precision, deliberately low false-positive):
10
8
  # * new dependency added to a manifest
@@ -174,6 +172,9 @@ msg="Anti-slop audit - $rel
174
172
  ${flag_block}${checklist}
175
173
 
176
174
  (Advisory; the bug pass is the self-review trigger. Disable: ANTI_SLOP_ENFORCE=0)"
175
+ # Expand ~ so the model gets literal absolute paths (followups should be
176
+ # copy-pasteable; bash expands ~ but the agent may not be in a bash context).
177
+ msg="$(expand_agent_paths "$msg")"
177
178
 
178
179
  # --- append to the shared pending file --------------------------------------
179
180
  cid="$(safe_conversation_id "$input")"
@@ -36,21 +36,18 @@ 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"
40
39
  intent_latch="$pending_dir/intent-injected-$cid.flag"
41
40
 
42
41
  # Sweep state from sessions that died before their stop hook ran.
43
42
  find "$pending_dir" -maxdepth 1 -type f -mtime +7 -delete 2>/dev/null
44
43
 
45
- # Unconditionally clear the per-turn latches so the next turn re-fires. Every
46
- # stop is a turn boundary; clearing here (not only inside the reviewed-flag
47
- # block below) guarantees these re-fire on the first edit/tool of the NEXT
48
- # turn and can never get stranded silenced mid-session:
49
- # - anchor-declared-<cid>.flag (anchor-set-nudge, first-edit reminder)
50
- # - intent-injected-<cid>.flag (intent-anchor, first-tool re-injection)
44
+ # Unconditionally clear the intent-anchor per-turn latch so the next turn
45
+ # re-fires. Every stop is a turn boundary; clearing here (not only inside the
46
+ # reviewed-flag block below) guarantees it re-fires on the first tool of the
47
+ # NEXT turn and can never get stranded silenced mid-session.
51
48
  # last-query-<cid>.hash is NOT cleared here - it persists turn-to-turn so
52
49
  # intent-anchor can detect prompt changes; the 7-day sweep above reaps it.
53
- rm -f "$anchor_flag" "$intent_latch" 2>/dev/null
50
+ rm -f "$intent_latch" 2>/dev/null
54
51
 
55
52
  # One-shot brake: the previous stop for this conversation emitted the review.
56
53
  if [ -f "$flag" ]; then
@@ -83,16 +80,17 @@ body=""
83
80
  if [ -z "$body" ]; then
84
81
  body='FINAL REVIEW - audit everything you changed this session and FIX what fails
85
82
  (do NOT revert the behaviour the user asked for):
83
+ 0. Intent trace - tie every diff hunk back to the ORIGINAL REQUEST above.
84
+ Anything untraceable is a hallucinated requirement: revert it. Runs FIRST.
86
85
  1. Correctness - logic, edge cases (null/empty/zero/boundary), language traps, security.
87
86
  2. Reliability - error paths handled (no empty catch), timeouts/retries, resources
88
87
  released on every path, no races, input validated at the boundary.
89
88
  3. Coverage - behaviour-bearing changes have real tests; RUN the suite if present;
90
89
  no tautological tests.
91
- 4. Anti-slop - read ~/.agents/hooks/anti-slop.md and apply all 13 items to
92
- the session diff. If ~/.cursor/skills/anti-slop/scripts/scan_slop.py exists,
93
- run `python ~/.cursor/skills/anti-slop/scripts/scan_slop.py --all` first.
94
- Consolidate clones; drop premature abstraction, unneeded deps, operational
95
- slop (retries, await-in-loop, log spam), unjustified files.
90
+ 4. Anti-slop - if the anti-slop scanner exists, run `python <scanner> --all` first;
91
+ then read ~/.agents/hooks/anti-slop.md (the single source of truth) and apply all
92
+ 13 items to the session diff. Consolidate clones; drop premature abstraction,
93
+ unneeded deps, operational slop, unjustified files. Do NOT re-list the items here.
96
94
  5. Wiring completeness - for every user-visible behavior you added/changed
97
95
  (button, submit, API call, route, state transition), trace its execution
98
96
  path to a REAL EFFECT (persist, mutate, call, render). A dead end is slop:
@@ -101,6 +99,9 @@ if [ -z "$body" ]; then
101
99
  now or remove the dead half; mark later-stubs with TODO(wire):.
102
100
  Fix now, re-run the scan + tests, then stop. If an axis is clean, say so in one line.'
103
101
  fi
102
+ # Expand ~ in the body AND the fallback, so the model gets literal absolute
103
+ # paths it can paste at the shell (bash expands ~, but followups should emit
104
+ # literals agents can copy-paste without re-interpreting).
104
105
  body="$(expand_agent_paths "$body")"
105
106
 
106
107
  # Regla R1 (re-entry): if this review pass is a re-audit after a failed gate or
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env bash
2
2
  # semantic-density-audit.sh - afterFileEdit "semantic opacity" advisory (Cursor, Linux).
3
3
  #
4
- # Guards the naming layer the other audit hooks do not see. minimal-edit-audit
5
- # watches diff SIZE; anti-slop-audit watches generated-code PATTERNS; this hook
6
- # watches whether the identifiers the agent JUST introduced actually communicate
7
- # intent. DataManager, process(), utils.ts, CoreEngine - names that exist but
8
- # say nothing.
4
+ # Guards the naming layer the other audit hooks do not see. anti-slop-audit
5
+ # watches generated-code PATTERNS; this hook watches whether the identifiers
6
+ # the agent JUST introduced actually communicate intent. DataManager,
7
+ # process(), utils.ts, CoreEngine - names that exist but say nothing.
9
8
  #
10
9
  # Mechanism: extract ADDED lines from `git diff HEAD -- <rel>` (with the
11
10
  # untracked-file fallback anti-slop-audit uses), pipe them to density_scan.py
@@ -44,13 +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"
48
47
  intent_latch="$pending_dir/intent-injected-$cid.flag"
49
48
 
50
- # Unconditionally clear the per-turn latches so the next subagent run re-fires.
51
- # Clearing here (not only inside the reviewed-flag block below) can never strand
52
- # them silenced. last-query-<cid>.hash is kept (cross-turn prompt-change detect).
53
- rm -f "$anchor_flag" "$intent_latch" 2>/dev/null
49
+ # Unconditionally clear the intent-anchor per-turn latch so the next subagent
50
+ # run re-fires. Clearing here (not only inside the reviewed-flag block below)
51
+ # can never strand it silenced. last-query-<cid>.hash is kept (cross-turn
52
+ # prompt-change detect).
53
+ rm -f "$intent_latch" 2>/dev/null
54
54
 
55
55
  # One-shot brake: the previous subagentStop for this id emitted the review.
56
56
  if [ -f "$flag" ]; then
@@ -76,13 +76,15 @@ body=""
76
76
  [ -f "$prompt_file" ] && body="$(cat "$prompt_file")"
77
77
  if [ -z "$body" ]; then
78
78
  body='Audit everything you changed in this run and FIX what fails (do NOT revert the
79
- behaviour the task asked for):
79
+ behaviour the task asked for). Six axes, in order:
80
+ 0. Intent trace - tie every diff hunk back to your original task. Untraceable = hallucinated.
80
81
  1. Correctness - logic, edge cases (null/empty/zero/boundary), language traps, security.
81
82
  2. Reliability - error paths handled, no swallowed errors, resources released.
82
83
  3. Coverage - behaviour-bearing changes have real tests; RUN the suite if present.
83
- 4. Anti-slop - if ~/.cursor/skills/anti-slop/scripts/scan_slop.py exists, run
84
- `python ~/.cursor/skills/anti-slop/scripts/scan_slop.py --all`; otherwise
85
- apply ~/.agents/hooks/anti-slop.md to the session diff.
84
+ 4. Anti-slop - if the scanner exists, run it --all first; otherwise read
85
+ ~/.agents/hooks/anti-slop.md (the single source of truth) and apply all 13 items.
86
+ 5. Wiring completeness - trace every added behavior to a REAL EFFECT (persist/mutate/call/render).
87
+ A dead end (handleSubmit that doesn'"'"'t persist, an endpoint no caller invokes) is slop.
86
88
  If an axis is clean, say so in one line. Then stop.'
87
89
  fi
88
90
  body="$(expand_agent_paths "$body")"
package/linux/hooks.json CHANGED
@@ -15,12 +15,6 @@
15
15
  "matcher": "^(Write|StrReplace|EditNotebook)$",
16
16
  "_comment": "5s: record the edit in ~/.cursor/.hooks-pending/session-edits-<conversation_id>.txt and stash the self-review prompt in feedback-<conversation_id>.txt. The harness normalizes agent file edits (incl. StrReplace) to tool type 'Write' in this event - verified via payload capture - so ^Write$ matches them all; the alternation is defensive against future harness versions reporting raw tool names. Anchored so TabWrite (every user tab-completion) stays excluded. The model is the auditor. NOTE: fires inside subagent contexts too, keyed by the SUBAGENT's conversation_id - see subagentStop + the marker fold in post-tool-use.sh/final-review.sh."
17
17
  },
18
- {
19
- "command": "bash ~/.agents/hooks/minimal-edit-audit.sh",
20
- "timeout": 15,
21
- "matcher": "^(Write|StrReplace|EditNotebook)$",
22
- "_comment": "15s (DEPRECATED in 0.3.0, superseded by semantic-density-audit + declared-editing; retained for compat, removal slated for 0.4.0): minimal-editing advisory on the edited file (git --numstat line-count). Size-based gate; the intent-declared gate supersedes it. Appends findings to the conversation's pending file; never blocks. Disable: HOOKS_ENFORCE=0 or MINIMAL_EDITING_ENFORCE=0."
23
- },
24
18
  {
25
19
  "command": "bash ~/.agents/hooks/semantic-density-audit.sh",
26
20
  "timeout": 15,
@@ -37,13 +31,7 @@
37
31
  "command": "bash ~/.agents/hooks/anti-slop-audit.sh",
38
32
  "timeout": 15,
39
33
  "matcher": "^(Write|StrReplace|EditNotebook)$",
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 agent turn (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 turn: gated by anchor-declared-<cid>.flag, armed here on first edit and cleared UNCONDITIONALLY by final-review.sh / subagent-stop-review.sh on every stop - so it re-fires on the first edit of the next turn and can never get stranded silenced. Advisory only; never blocks. Disable: HOOKS_ENFORCE=0 or ANCHOR_NUDGE_ENFORCE=0."
34
+ "_comment": "15s: AI-slop advisory. 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."
47
35
  }
48
36
  ],
49
37
  "postToolUse": [
@@ -78,7 +66,7 @@
78
66
  "stop": [
79
67
  {
80
68
  "command": "bash ~/.agents/hooks/final-review.sh",
81
- "timeout": 5,
69
+ "timeout": 10,
82
70
  "loop_limit": 5,
83
71
  "_comment": "5s: ONE comprehensive end-of-implementation review across correctness + reliability + coverage + anti-slop. If the agent edited files this loop (session-edits-<conversation_id> marker), returns {followup_message} so Cursor auto-submits ONE review pass. Bounded by the script's per-conversation reviewed-flag (one review per implementation); loop_limit is the harness-side runaway cap. Disable: HOOKS_ENFORCE=0 or FINAL_REVIEW_ENFORCE=0."
84
72
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cursordoctrine",
3
- "version": "0.4.6",
4
- "description": "Thin self-review hooks for Cursor — the model is the auditor. Proactive intent compilation (pre-compile Anchor Set + auto-scaffolded .scope.json that regenerates per prompt and re-injects every turn against Salience Dilution), intent-trace final review (Tier 0), unified 13-item anti-slop checklist, operational slop detection.",
3
+ "version": "0.5.1",
4
+ "description": "Thin self-review hooks for Cursor — the model is the auditor. Pruned + deduplicated: intent-anchor (auto-scaffolded .scope.json per prompt + per-turn re-injection against Salience Dilution), intent-trace final review, unified anti-slop checklist as single source of truth.",
5
5
  "bin": {
6
6
  "cursordoctrine": "bin/cli.mjs"
7
7
  },
@@ -20,7 +20,7 @@ description: >-
20
20
  duplicate utilities.
21
21
  metadata:
22
22
  layer: active-cleanup
23
- pairs-with: declared-editing (supersedes minimal-editing), semantic-density-audit
23
+ pairs-with: declared-editing, semantic-density-audit
24
24
  ---
25
25
 
26
26
  # Anti-Slop
@@ -275,8 +275,7 @@ low-density identifiers per edit — shares `low_density.py` with this scanner's
275
275
  `semantic_density` bucket), the **scope-gate-audit hook**
276
276
  (`scope-gate-audit.ps1` / `.sh`, Compuerta 1 — opt-in declared-scope gate
277
277
  that flags edits outside `.scope.json`; shares `scope_match.py` with the
278
- final-review Step D closing gate), the **stop hook** (`final-review.ps1` / `.sh`,
278
+ **stop hook** (`final-review.ps1` / `.sh`,
279
279
  six-axis session review incl. intent trace and wiring completeness), and
280
- **declared-editing** (YAGNI ultra ladder injected at session start;
281
- supersedes the deprecated `minimal-editing` size gate). This skill is the
282
- active "delete it now" layer those only nudge toward.
280
+ **declared-editing** (YAGNI ultra ladder injected at session start).
281
+ This skill is the active "delete it now" layer those only nudge toward.
@@ -13,27 +13,25 @@ new rule, you do not add it here — you do the work.
13
13
 
14
14
  ## 1. You are the auditor
15
15
 
16
- The harness gives you one piece of safety net: a **permission-gate** that
17
- denies a small list of dangerous commands (rm -rf on absolute paths,
18
- curl|sh, force-push, npm publish, etc.). It does **not** audit your code.
19
- It does **not** run linters. It does **not** score your diffs. **You**
20
- are the auditor. After every `Edit`, the harness hands you your own diff
21
- back via `additional_context` and asks you to review it for bugs. You
22
- must review, fix what is broken, and stay silent otherwise. The exact
23
- self-review prompt is in `self-review.md` next to the hook scripts
24
- (installed under `~/.agents/hooks/` — if you cannot find it, run
25
- `find ~/.agents ~/.cursor ~/.config -name self-review.md 2>/dev/null`
26
- on first session).
16
+ The harness ships a set of **advisory hooks** small scripts that flag
17
+ patterns after you edit (semantic density, declared-scope drift, AI slop) and
18
+ hand them back to you as `additional_context`. They never block, never revert,
19
+ never decide anything. They surface signals; **you** decide. A permission-gate
20
+ denies a short list of dangerous shell commands (`rm -rf` on absolute paths,
21
+ `curl|sh`, force-push, `npm publish`, ...). That is the only hard block. After
22
+ every `Edit`, a self-review prompt is handed back to you so you re-read your
23
+ own diff and fix bugs.
27
24
 
28
25
  This means:
29
26
 
30
- - You do not need a regex engine. You do not need an AST pass. You do
31
- not need 8 audit layers. You need to **read the file you just
32
- changed, see if it is wrong, and fix it if so.**
33
- - A heuristic audit that runs on every keystroke and never blocks
34
- would still be wasted work. A self-review that you do yourself, in
35
- your own context, is **free** you have the file, the diff, the
36
- user's intent, and the ability to fix. Use that.
27
+ - You do not need a deterministic gate to catch bugs. The hooks seed signals
28
+ (regex hits, scope violations, low-density names); you do the actual audit
29
+ — **read the file you just changed, see if it is wrong, fix it if so.**
30
+ A self-review you do yourself, in your own context, is **free** you have
31
+ the file, the diff, the user's intent, and the ability to fix.
32
+ - The hooks are advisory, not authoritative. If a hook flags something and you
33
+ disagree, say so in one line and proceed. You outrank the hooks. They exist
34
+ to remind you, not to overrule you.
37
35
  - "Bugs" means things that would fail a careful code review at
38
36
  Anthropic / Stripe / Vercel. Style, naming, formatting, missing
39
37
  type hints, "you could write this more idiomatically" — these are
@@ -140,20 +138,18 @@ When you call Bash:
140
138
 
141
139
  ## 7. What you do not do
142
140
 
143
- - You do not run 8 audit engines on every file. The harness does not
144
- have them. You are the auditor.
141
+ - You do not treat the advisory hooks as a gate to satisfy. They surface
142
+ signals; the hooks do not block, and you are the auditor. Optimize for
143
+ "the change is correct, small, and the user can review it in 30 seconds,"
144
+ not for "no hook fired."
145
145
  - You do not write to `.stuck-files/`, `.audit-baselines/`, or any
146
146
  other hook state directory. Those are not your concern.
147
- - You do not maintain a "strict quality gate" config. There isn't
148
- one. The rules above *are* the gate.
149
147
  - You do not set kill-switch env vars (`HOOKS_ENFORCE=0`,
150
- `MYTHOS_ENFORCE=0`, etc.). They don't exist anymore.
148
+ `INTENT_ANCHOR_ENFORCE=0`, etc.) to silence the hooks. They exist; work
149
+ with them, or tell the user if one is wrong.
151
150
  - You do not re-read the doctrine on every turn. You read it once,
152
151
  at session start, and internalize it. The self-review trigger
153
152
  will re-prompt you for the specific edit you're auditing.
154
- - You do not optimize for "the gate passed." You optimize for
155
- "the change is correct, small, and the user can review it in
156
- 30 seconds."
157
153
 
158
154
  ---
159
155
 
@@ -1,8 +1,6 @@
1
1
  # anti-slop-audit.ps1 - afterFileEdit "AI slop" advisory (Cursor).
2
2
  #
3
- # Companion to minimal-edit-audit.ps1. That hook guards ONE slop axis -
4
- # over-editing (diff size / token-Levenshtein / added complexity). This hook
5
- # guards the rest of the taxonomy: the parts static analysis can cheaply and
3
+ # Guards the parts of the slop taxonomy static analysis can cheaply and
6
4
  # precisely flag, plus a self-review checklist for the parts it cannot.
7
5
  #
8
6
  # Statically flagged (high-precision, deliberately low false-positive):
@@ -209,9 +207,11 @@ Fix guilty items now. Never revert what the user asked for.
209
207
  $flagBlock = if ($flags.Count -gt 0) { "Static signals on this edit:`n" + ($flags -join "`n") + "`n`n" } else { '' }
210
208
 
211
209
  # Concatenate (do NOT interpolate $checklist - its content must stay literal).
210
+ # Expand ~ on the final message so the model gets literal absolute paths
211
+ # (Windows pwsh does NOT expand ~; the agent may paste paths into a shell).
212
212
  $header = "Anti-slop audit - $rel`n`n"
213
213
  $footer = "`n`n(Advisory; the bug pass is the self-review trigger. Disable: ANTI_SLOP_ENFORCE=0)"
214
- $msg = $header + $flagBlock + $checklist + $footer
214
+ $msg = Expand-AgentPaths ($header + $flagBlock + $checklist + $footer)
215
215
 
216
216
  # --- append to the shared pending file ------------------------------------
217
217
  $cid = Get-SafeConversationId $obj
@@ -56,26 +56,18 @@ Step A — mechanical scan (if available):
56
56
  Step B — canonical checklist (always):
57
57
  Read `~/.agents/hooks/anti-slop.md` and apply ALL 13 items to every hunk you
58
58
  changed this session. That file is the single source of truth for slop
59
- detection — items 1–10 are structural/code, 11 is semantic contracts, 12 is
60
- operational slop (retries, await-in-loop, telemetry spam), 13 is change
61
- surface. Fix every hit; consolidate clones to one source of truth.
59
+ detection — it is NOT repeated here. Fix every hit; consolidate clones to one
60
+ source of truth.
62
61
 
63
62
  Step C — session footprint (also in the header above):
64
63
  If "Session footprint" shows >5 files or the request was simple, justify each
65
64
  file or trim. Unjustified files are slop.
66
65
 
67
- Step D — declared scope (closing gate for Compuerta 1):
68
- If `.scope.json` exists in the repo root, run the session's full diff against
69
- the declared contract. In your shell:
70
- for f in $(git diff --name-only HEAD); do
71
- python ~/.cursor/skills/anti-slop/scripts/scope_match.py --path "$f" --patterns-file .scope.json
72
- done
73
- Any file reporting `"in_scope": false` is a scope violation you must justify
74
- (add to .scope.json with a one-line reason) or revert. If `.scope.json` does
75
- not exist, this step is skipped — the declared-editing ladder and the
76
- per-edit scope-gate-audit hook are the opt-in discipline.
77
-
78
66
  Fix with edits now; re-run the scan (if Step A ran) and the tests; then stop.
67
+ (The per-edit `scope-gate-audit` hook already checks `.scope.json` files[] on
68
+ every edit — Step D of older versions ran that loop again here. Removed: it
69
+ duplicated the live hook and burned tokens. If `.scope.json` exists, trust the
70
+ per-edit gate; the intent trace in axis 0 is the whole-session backstop.)
79
71
 
80
72
  ## 5. Wiring completeness
81
73
  For every user-visible behavior you added or changed (button, form submit, API
@@ -40,7 +40,6 @@ $cid = Get-SafeConversationId $obj
40
40
  $pendingDir = Get-HooksPendingDir
41
41
  $marker = Join-Path $pendingDir "session-edits-$cid.txt"
42
42
  $flag = Join-Path $pendingDir "reviewed-$cid.flag"
43
- $anchorFlag = Join-Path $pendingDir "anchor-declared-$cid.flag"
44
43
  $intentLatch = Join-Path $pendingDir "intent-injected-$cid.flag"
45
44
 
46
45
  # Sweep state from sessions that died before their stop hook ran. Cheap (one
@@ -49,16 +48,13 @@ Get-ChildItem $pendingDir -File -ErrorAction SilentlyContinue |
49
48
  Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-7) } |
50
49
  Remove-Item -Force -ErrorAction SilentlyContinue
51
50
 
52
- # Unconditionally clear the per-turn latches so the next turn re-fires. Every
53
- # stop is a turn boundary; clearing here (not only inside the reviewed-flag
54
- # block below) guarantees these re-fire on the first edit/tool of the NEXT
55
- # turn and can never get stranded silenced mid-session:
56
- # - anchor-declared-<cid>.flag (anchor-set-nudge, first-edit reminder)
57
- # - intent-injected-<cid>.flag (intent-anchor, first-tool re-injection)
58
- # last-query-<cid>.hash is NOT cleared here - it must persist turn-to-turn so
59
- # intent-anchor can detect prompt changes; the 7-day sweep above reaps it when
60
- # a conversation truly dies.
61
- Remove-Item $anchorFlag, $intentLatch -Force -ErrorAction SilentlyContinue
51
+ # Unconditionally clear the intent-anchor per-turn latch so the next turn
52
+ # re-fires. Every stop is a turn boundary; clearing here (not only inside the
53
+ # reviewed-flag block below) guarantees it re-fires on the first tool of the
54
+ # NEXT turn and can never get stranded silenced mid-session.
55
+ # last-query-<cid>.hash is NOT cleared here - it persists turn-to-turn so
56
+ # intent-anchor can detect prompt changes; the 7-day sweep above reaps it.
57
+ Remove-Item $intentLatch -Force -ErrorAction SilentlyContinue
62
58
 
63
59
  # One-shot brake: the previous stop for this conversation emitted the review.
64
60
  # Clear the flag (and whatever the review pass itself edited) and end the loop.
@@ -94,16 +90,17 @@ if (-not $body) {
94
90
  $body = @'
95
91
  FINAL REVIEW - audit everything you changed this session and FIX what fails
96
92
  (do NOT revert the behaviour the user asked for):
93
+ 0. Intent trace - tie every diff hunk back to the ORIGINAL REQUEST above.
94
+ Anything untraceable is a hallucinated requirement: revert it. Runs FIRST.
97
95
  1. Correctness - logic, edge cases (null/empty/zero/boundary), language traps, security.
98
96
  2. Reliability - error paths handled (no empty catch), timeouts/retries, resources
99
97
  released on every path, no races, input validated at the boundary.
100
98
  3. Coverage - behaviour-bearing changes have real tests; RUN the suite if present;
101
99
  no tautological tests.
102
- 4. Anti-slop - read ~/.agents/hooks/anti-slop.md and apply all 13 items to
103
- the session diff. If ~/.cursor/skills/anti-slop/scripts/scan_slop.py exists,
104
- run `python ~/.cursor/skills/anti-slop/scripts/scan_slop.py --all` first.
105
- Consolidate clones; drop premature abstraction, unneeded deps, operational
106
- slop (retries, await-in-loop, log spam), unjustified files.
100
+ 4. Anti-slop - if the anti-slop scanner exists, run `python <scanner> --all` first;
101
+ then read ~/.agents/hooks/anti-slop.md (the single source of truth) and apply all
102
+ 13 items to the session diff. Consolidate clones; drop premature abstraction,
103
+ unneeded deps, operational slop, unjustified files. Do NOT re-list the items here.
107
104
  5. Wiring completeness - for every user-visible behavior you added/changed
108
105
  (button, submit, API call, route, state transition), trace its execution
109
106
  path to a REAL EFFECT (persist, mutate, call, render). A dead end is slop:
@@ -113,6 +110,9 @@ FINAL REVIEW - audit everything you changed this session and FIX what fails
113
110
  Fix now, re-run the scan + tests, then stop. If an axis is clean, say so in one line.
114
111
  '@
115
112
  }
113
+ # Expand ~ in the body AND in the fallback above, so the model gets literal
114
+ # absolute paths it can paste into pwsh on Windows (where ~ does NOT expand).
115
+ # Same treatment applied to the scanner path so the anti-slop Step A command runs.
116
116
  $body = Expand-AgentPaths $body
117
117
 
118
118
  # Regla R1 (re-entry): if this review pass is a re-audit after a failed gate or
@@ -6,31 +6,25 @@ file. Your job, on this turn, is to:
6
6
  3. Decide: does this edit introduce any of the following?
7
7
  - **Security**: hardcoded secret (AWS key, private key, API token,
8
8
  password in source), `eval(`, `exec(`, `pickle.loads`, `verify=False`,
9
- `child_process` with user input, `dangerouslySetInnerHTML` with
10
- untrusted data, SQL string concat.
9
+ SQL string concat. (Full taxonomy lives in anti-slop.md item 9 +
10
+ the scanner; this is the high-confidence shortlist to act on now.)
11
11
  - **Correctness**: assignment-in-condition (`if (x = 5)`), `==`/`!=`
12
12
  with `null`/`None`/`NaN` in a comparison, `forEach` with `await`,
13
13
  async `useEffect` with side-effects missing cleanup, `==` instead
14
14
  of `===` in JS, mutable default args in Python, shadowed imports,
15
15
  dead relative imports.
16
- - **Safety**: `rm -rf /`, `curl ... | sh`, force-push, `git reset --hard`
17
- without a backup, `npm publish` without version bump, secret
18
- committed to a public file.
19
16
  - **Logic bugs that the user would actually care about**: a function
20
17
  that returns the wrong thing, an off-by-one, a missing `return`, a
21
18
  wrong import path.
22
- - **Semantic contracts**: did any existing function's BEHAVIOR change
23
- without its name, signature, or docstring changing? Names are
24
- contracts. `deleteUser()` that now soft-deletes, a getter that now
25
- writes, a function that used to throw on bad input and now silently
26
- returns null — these are silent contract breaks that callers will
27
- rely on and break against. If behavior changed, the name, signature,
28
- or docstring must reflect it.
29
19
  4. If you find a real bug, **fix it with `Edit`**, then say nothing.
30
20
  Do not report it. Do not explain it. The user will see the fix
31
21
  in the next message; the bug is gone.
32
22
  5. If the edit is clean, respond with the single word: `clean`.
33
23
 
24
+ Scope: this pass is the **fast bug hunter**. It is deliberately narrower than
25
+ anti-slop — no duplication/architecture/abstraction/conventions audit here.
26
+ Those run on substantial edits (the `anti-slop-audit` hook) and at the end of
27
+ the implementation (`final-review` axis 4). Do not duplicate them per-edit.
34
28
  Hard constraints:
35
29
 
36
30
  - **Never revert or re-do work the user asked for.** The user's intent
@@ -51,5 +45,4 @@ Hard constraints:
51
45
  thrashing.
52
46
 
53
47
  This is the entire self-review prompt. It is the same prompt, every
54
- edit, forever. The model is the auditor. There is no regex, no AST
55
- parse, no Python — the model itself does the work.
48
+ edit, forever. The model is the auditor.
@@ -1,10 +1,9 @@
1
1
  # semantic-density-audit.ps1 - afterFileEdit "semantic opacity" advisory (Cursor).
2
2
  #
3
- # Guards the naming layer the other audit hooks do not see. minimal-edit-audit
4
- # watches diff SIZE; anti-slop-audit watches generated-code PATTERNS; this hook
5
- # watches whether the identifiers the agent JUST introduced actually communicate
6
- # intent. DataManager, process(), utils.ts, CoreEngine - names that exist but
7
- # say nothing.
3
+ # Guards the naming layer the other audit hooks do not see. anti-slop-audit
4
+ # watches generated-code PATTERNS; this hook watches whether the identifiers
5
+ # the agent JUST introduced actually communicate intent. DataManager,
6
+ # process(), utils.ts, CoreEngine - names that exist but say nothing.
8
7
  #
9
8
  # Mechanism: extract ADDED lines from `git diff HEAD -- <rel>` (with the
10
9
  # untracked-file fallback anti-slop-audit uses), pipe them to density_scan.py
@@ -43,13 +43,13 @@ $cid = Get-SafeConversationId $obj
43
43
  $pendingDir = Get-HooksPendingDir
44
44
  $marker = Join-Path $pendingDir "session-edits-$cid.txt"
45
45
  $flag = Join-Path $pendingDir "reviewed-$cid.flag"
46
- $anchorFlag = Join-Path $pendingDir "anchor-declared-$cid.flag"
47
46
  $intentLatch = Join-Path $pendingDir "intent-injected-$cid.flag"
48
47
 
49
- # Unconditionally clear the per-turn latches so the next subagent run re-fires.
50
- # Clearing here (not only inside the reviewed-flag block below) can never strand
51
- # them silenced. last-query-<cid>.hash is kept (cross-turn prompt-change detect).
52
- Remove-Item $anchorFlag, $intentLatch -Force -ErrorAction SilentlyContinue
48
+ # Unconditionally clear the intent-anchor per-turn latch so the next subagent
49
+ # run re-fires. Clearing here (not only inside the reviewed-flag block below)
50
+ # can never strand it silenced. last-query-<cid>.hash is kept (cross-turn
51
+ # prompt-change detect).
52
+ Remove-Item $intentLatch -Force -ErrorAction SilentlyContinue
53
53
 
54
54
  # One-shot brake: the previous subagentStop for this id emitted the review.
55
55
  if (Test-Path $flag) {
@@ -76,13 +76,15 @@ if (Test-Path $promptFile) { $body = Get-Content -Raw $promptFile }
76
76
  if (-not $body) {
77
77
  $body = @'
78
78
  Audit everything you changed in this run and FIX what fails (do NOT revert the
79
- behaviour the task asked for):
79
+ behaviour the task asked for). Six axes, in order:
80
+ 0. Intent trace - tie every diff hunk back to your original task. Untraceable = hallucinated.
80
81
  1. Correctness - logic, edge cases (null/empty/zero/boundary), language traps, security.
81
82
  2. Reliability - error paths handled, no swallowed errors, resources released.
82
83
  3. Coverage - behaviour-bearing changes have real tests; RUN the suite if present.
83
- 4. Anti-slop - if ~/.cursor/skills/anti-slop/scripts/scan_slop.py exists, run
84
- `python ~/.cursor/skills/anti-slop/scripts/scan_slop.py --all`; otherwise
85
- apply ~/.agents/hooks/anti-slop.md to the session diff.
84
+ 4. Anti-slop - if the scanner exists, run it --all first; otherwise read
85
+ ~/.agents/hooks/anti-slop.md (the single source of truth) and apply all 13 items.
86
+ 5. Wiring completeness - trace every added behavior to a REAL EFFECT (persist/mutate/call/render).
87
+ A dead end (handleSubmit that doesn't persist, an endpoint no caller invokes) is slop.
86
88
  If an axis is clean, say so in one line. Then stop.
87
89
  '@
88
90
  }
@@ -15,12 +15,6 @@
15
15
  "matcher": "^(Write|StrReplace|EditNotebook)$",
16
16
  "_comment": "5s: record the edit in ~/.cursor/.hooks-pending/session-edits-<conversation_id>.txt and stash the self-review prompt in feedback-<conversation_id>.txt. The harness normalizes agent file edits (incl. StrReplace) to tool type 'Write' in this event - verified via payload capture - so ^Write$ matches them all; the alternation is defensive against future harness versions reporting raw tool names. Anchored so TabWrite (every user tab-completion) stays excluded. The model is the auditor. NOTE: fires inside subagent contexts too, keyed by the SUBAGENT's conversation_id - see subagentStop + the marker fold in post-tool-use.ps1/final-review.ps1."
17
17
  },
18
- {
19
- "command": "pwsh.exe -NoProfile -File ~/.agents/hooks/minimal-edit-audit.ps1",
20
- "timeout": 15,
21
- "matcher": "^(Write|StrReplace|EditNotebook)$",
22
- "_comment": "15s (DEPRECATED in 0.3.0, superseded by semantic-density-audit + declared-editing; retained for compat, removal slated for 0.4.0): minimal-editing advisory on the edited file (git --numstat line-count). Size-based gate; the intent-declared gate supersedes it. Appends findings to the conversation's pending file; never blocks. Disable: HOOKS_ENFORCE=0 or MINIMAL_EDITING_ENFORCE=0."
23
- },
24
18
  {
25
19
  "command": "pwsh.exe -NoProfile -File ~/.agents/hooks/semantic-density-audit.ps1",
26
20
  "timeout": 15,
@@ -37,13 +31,7 @@
37
31
  "command": "pwsh.exe -NoProfile -File ~/.agents/hooks/anti-slop-audit.ps1",
38
32
  "timeout": 15,
39
33
  "matcher": "^(Write|StrReplace|EditNotebook)$",
40
- "_comment": "15s: AI-slop advisory, companion to minimal-edit-audit. Native 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": "pwsh.exe -NoProfile -File ~/.agents/hooks/anchor-set-nudge.ps1",
44
- "timeout": 5,
45
- "matcher": "^(Write|StrReplace|EditNotebook)$",
46
- "_comment": "5s: PROACTIVE pre-compile nudge. On the FIRST edit of each agent turn (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 turn: gated by anchor-declared-<cid>.flag, armed here on first edit and cleared UNCONDITIONALLY by final-review.ps1 / subagent-stop-review.ps1 on every stop - so it re-fires on the first edit of the next turn and can never get stranded silenced. Advisory only; never blocks. Disable: HOOKS_ENFORCE=0 or ANCHOR_NUDGE_ENFORCE=0."
34
+ "_comment": "15s: AI-slop advisory. Native 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."
47
35
  }
48
36
  ],
49
37
  "postToolUse": [
@@ -78,7 +66,7 @@
78
66
  "stop": [
79
67
  {
80
68
  "command": "pwsh.exe -NoProfile -File ~/.agents/hooks/final-review.ps1",
81
- "timeout": 5,
69
+ "timeout": 10,
82
70
  "loop_limit": 5,
83
71
  "_comment": "5s: ONE comprehensive end-of-implementation review across correctness + reliability + coverage + anti-slop. If the agent edited files this loop (session-edits-<conversation_id> marker), returns {followup_message} so Cursor auto-submits ONE review pass. Bounded by the script's per-conversation reviewed-flag (one review per implementation); loop_limit counts follow-ups per CONVERSATION (docs), so 5 = harness-side runaway cap, not the per-implementation bound (2 would silently kill reviews after the 2nd implementation in a long chat). Disable: HOOKS_ENFORCE=0 or FINAL_REVIEW_ENFORCE=0."
84
72
  }
@@ -1,77 +0,0 @@
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
- # agent turn, remind the agent to compile its Anchor Set (pre-compile.md) and
6
- # write .scope.json BEFORE piling on more code. The reactive stack (self-review,
7
- # anti-slop, final-review) only fires AFTER code exists; this nudge catches
8
- # intent dilution at token ~50, not at the ~5000 of the stop-hook axis 0. A
9
- # clean final review of the wrong feature is still the wrong feature - the
10
- # Anchor Set exists so the right feature is on the rails from the first edit.
11
- #
12
- # Fires ONCE PER TURN (per conversation): gated by an anchor-declared-<cid>.flag
13
- # in the pending dir, armed here on first edit and cleared UNCONDITIONALLY by
14
- # final-review.sh / subagent-stop-review.sh on every stop. So a long
15
- # conversation with N turns gets up to N nudges - every new turn re-earns the
16
- # reminder on its first edit. The clear is unconditional (not gated on the
17
- # reviewed-flag path) so the latch can never get stranded silenced mid-session,
18
- # which would silently stop reminding the agent to write .scope.json.
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
@@ -1,120 +0,0 @@
1
- #!/usr/bin/env bash
2
- # minimal-edit-audit.sh - afterFileEdit minimal-editing advisory (Cursor, Linux).
3
- #
4
- # DEPRECATED in 0.3.0 (superseded by semantic-density-audit.sh + the
5
- # declared-editing discipline; removal slated for 0.4.0). The line-count
6
- # heuristic here is the size-based gate the cursordoctrine audit identified
7
- # as the antipattern: it penalizes legitimate large declared changes and
8
- # misses small quiet drifts. Retained for compatibility with existing installs;
9
- # new installs register semantic-density-audit.sh alongside it. To opt out of
10
- # the deprecated hook without uninstalling: MINIMAL_EDITING_ENFORCE=0.
11
- #
12
- # Audits the just-edited file for over-editing:
13
- # * line-count - git diff --numstat thresholds (any language).
14
- # * token metrics - audit-metrics.py (token-Levenshtein + cognitive
15
- # complexity), Python files only, if the script exists.
16
- # On WARN/FAIL it APPENDS a short advisory to the shared pending-feedback file;
17
- # post-tool-use.sh delivers it as additional_context on the next tool turn.
18
- #
19
- # Advisory only: never blocks, never writes persistent state. afterFileEdit
20
- # output isn't consumed and a non-zero exit shows as "hook failed", so we
21
- # ALWAYS exit 0.
22
- #
23
- # Thresholds (env-overridable): MINIMAL_EDIT_FAIL_LINES (400), MINIMAL_EDIT_WARN_LINES (100).
24
- # Disable: HOOKS_ENFORCE=0 or MINIMAL_EDITING_ENFORCE=0
25
-
26
- set +e
27
- . "$(dirname "$0")/hook-common.sh"
28
-
29
- [ "${HOOKS_ENFORCE:-}" = "0" ] && exit 0
30
- [ "${MINIMAL_EDITING_ENFORCE:-}" = "0" ] && exit 0
31
-
32
- input="$(read_hook_stdin)"
33
- [ -n "$input" ] || exit 0
34
-
35
- # audit root = project from JSON (cwd, then workspace_roots), else CURSOR_PROJECT_DIR / HOME
36
- root=""
37
- while IFS= read -r cand; do
38
- [ -n "$cand" ] && [ -d "$cand" ] && { root="${cand%/}"; break; }
39
- done <<EOF
40
- $(json_get "$input" cwd)
41
- $(json_get_array "$input" workspace_roots)
42
- EOF
43
- [ -n "$root" ] || root="${CURSOR_PROJECT_DIR:-$HOME}"
44
- root="${root%/}"
45
-
46
- # edited file -> repo-relative path
47
- fp=""
48
- for k in file_path path filename absolute_path abs_path; do
49
- fp="$(json_get "$input" "$k")"
50
- [ -n "$fp" ] && break
51
- done
52
- [ -n "$fp" ] || exit 0
53
- rel="$fp"
54
- case "$rel" in "$root"/*) rel="${rel#"$root"/}" ;; esac
55
- if is_cursor_config_path "$fp" || is_cursor_config_path "$rel"; then exit 0; fi
56
-
57
- # git repo?
58
- git -C "$root" rev-parse --git-dir >/dev/null 2>&1 || exit 0
59
-
60
- # --- line-count audit (any language) --------------------------------------
61
- fail_lines="${MINIMAL_EDIT_FAIL_LINES:-400}"
62
- warn_lines="${MINIMAL_EDIT_WARN_LINES:-100}"
63
- changed="$(git -C "$root" diff HEAD --numstat -- "$rel" 2>/dev/null |
64
- awk '$1 != "-" && $2 != "-" { n += $1 + $2 } END { print n + 0 }')"
65
-
66
- grade="OK"; hint=""
67
- if [ "$changed" -gt "$fail_lines" ]; then
68
- grade="FAIL"; hint="$changed lines changed (limit $fail_lines) - likely over-editing; trim or split"
69
- elif [ "$changed" -gt "$warn_lines" ]; then
70
- grade="WARN"; hint="$changed lines changed - justify each hunk or split the task"
71
- fi
72
-
73
- # --- token metrics (.py only) ---------------------------------------------
74
- audit_metrics="$HOME/.cursor/skills/minimal-editing/scripts/audit-metrics.py"
75
- if [ -f "$audit_metrics" ] && have_py; then
76
- case "$rel" in
77
- *.py)
78
- mgrade="$(python3 "$audit_metrics" --root "$root" --format json --path "$rel" 2>/dev/null |
79
- { if have_jq; then jq -r '.grade // empty'; else python3 -c 'import json,sys
80
- try: print(json.load(sys.stdin).get("grade",""))
81
- except Exception: pass'; fi; })"
82
- if [ "$mgrade" = "FAIL" ]; then grade="FAIL"
83
- elif [ "$mgrade" = "WARN" ] && [ "$grade" = "OK" ]; then grade="WARN"; fi
84
- ;;
85
- esac
86
- fi
87
-
88
- [ "$grade" = "OK" ] && exit 0
89
-
90
- # --- compose advisory + append to the shared pending file ------------------
91
- hint_txt=""
92
- [ -n "$hint" ] && hint_txt=" - $hint"
93
- if [ "$grade" = "FAIL" ]; then
94
- actions=" - Trim every hunk that isn't required by the task.
95
- - Prefer narrow, targeted edits over rewriting blocks.
96
- - If the change is genuinely large, split it into smaller logical commits."
97
- else
98
- actions=" Advisory only - trim unrelated hunks if any; otherwise proceed."
99
- fi
100
-
101
- msg="Minimal-edit audit $grade - $rel
102
-
103
- IMPORTANT: Try to preserve the original code and the logic of the original code as much as possible.
104
-
105
- grade: $grade$hint_txt
106
-
107
- $actions
108
-
109
- (Disable for this session: HOOKS_ENFORCE=0)"
110
-
111
- cid="$(safe_conversation_id "$input")"
112
- pending="$(hooks_pending_dir)/feedback-$cid.txt"
113
- mkdir -p "$(dirname "$pending")" 2>/dev/null
114
- if [ -s "$pending" ]; then
115
- printf '\n\n---\n\n%s' "$msg" >> "$pending" 2>/dev/null
116
- else
117
- printf '%s' "$msg" >> "$pending" 2>/dev/null
118
- fi
119
-
120
- exit 0
@@ -1,76 +0,0 @@
1
- # anchor-set-nudge.ps1 - afterFileEdit "pre-compile" nudge (Cursor).
2
- #
3
- # Proactive counterpart to the reactive audits. On the FIRST file edit of an
4
- # agent turn, remind the agent to compile its Anchor Set (pre-compile.md) and
5
- # write .scope.json BEFORE piling on more code. The reactive stack (self-review,
6
- # anti-slop, final-review) only fires AFTER code exists; this nudge catches
7
- # intent dilution at token ~50, not at the ~5000 of the stop-hook axis 0. A
8
- # clean final review of the wrong feature is still the wrong feature - the
9
- # Anchor Set exists so the right feature is on the rails from the first edit.
10
- #
11
- # Fires ONCE PER TURN (per conversation): gated by an anchor-declared-<cid>.flag
12
- # in the pending dir, armed here on first edit and cleared UNCONDITIONALLY by
13
- # final-review.ps1 / subagent-stop-review.ps1 on every stop. So a long
14
- # conversation with N turns gets up to N nudges - every new turn re-earns the
15
- # reminder on its first edit. The clear is unconditional (not gated on the
16
- # reviewed-flag path) so the latch can never get stranded silenced mid-session,
17
- # which would silently stop reminding the agent to write .scope.json.
18
- #
19
- # Advisory only: never blocks, never reads the diff, ALWAYS exits 0. Appends to
20
- # the shared feedback-<cid>.txt bus; post-tool-use.ps1 delivers it next turn.
21
- # Disable: HOOKS_ENFORCE=0 or ANCHOR_NUDGE_ENFORCE=0
22
-
23
- $ErrorActionPreference = 'SilentlyContinue'
24
- . "$PSScriptRoot\hook-common.ps1"
25
-
26
- if ($env:HOOKS_ENFORCE -eq '0' -or $env:ANCHOR_NUDGE_ENFORCE -eq '0') { exit 0 }
27
-
28
- $obj = Read-HookStdinJson
29
- if (-not $obj) { exit 0 }
30
-
31
- $filePath = ''
32
- if ($obj.PSObject.Properties['file_path']) { $filePath = [string]$obj.file_path }
33
- elseif ($obj.PSObject.Properties['path']) { $filePath = [string]$obj.path }
34
- elseif ($obj.PSObject.Properties['filePath']) { $filePath = [string]$obj.filePath }
35
- if (-not $filePath) { exit 0 }
36
- if (Test-IsCursorConfigPath $filePath) { exit 0 }
37
-
38
- $cid = Get-SafeConversationId $obj
39
- $pendingDir = Get-HooksPendingDir
40
- $flag = Join-Path $pendingDir "anchor-declared-$cid.flag"
41
-
42
- # Already nudged this implementation -> stay quiet. The flag is cleared at the
43
- # per-implementation boundary in final-review.ps1 / subagent-stop-review.ps1.
44
- if (Test-Path $flag) { exit 0 }
45
-
46
- $msg = @"
47
- PRE-COMPILE NUDGE - first edit of this implementation: $filePath
48
-
49
- Before you keep going, did you compile your Anchor Set (pre-compile.md)?
50
- 1. OBJECTIVE - one operational sentence. What is strictly necessary.
51
- 2. CONSTRAINTS - local negations (what you will NOT do).
52
- 3. SCOPE - files to touch, files untouchable.
53
- 4. SUCCESS - the one deterministic check that decides done.
54
-
55
- If you have not already, write it to .scope.json now (intent / files /
56
- acceptance / allow_growth). The scope-gate audits every edit against files[],
57
- and final-review axis 0 traces every diff hunk back to intent. An Anchor Set
58
- that lives only in your head is not an Anchor Set - the gate cannot audit it.
59
-
60
- Skip this for trivial one-liners (typo, literal). Otherwise: compile, then code.
61
- "@
62
-
63
- # Append to the shared pending file (same bus as the other advisories).
64
- $pending = Join-Path $pendingDir "feedback-$cid.txt"
65
- try {
66
- New-Item -ItemType Directory -Path $pendingDir -Force | Out-Null
67
- $prefix = ''
68
- if ((Test-Path $pending) -and ((Get-Item $pending).Length -gt 0)) { $prefix = "`n`n---`n`n" }
69
- Add-Content -Path $pending -Value ($prefix + $msg) -NoNewline
70
- } catch { }
71
-
72
- # Arm the one-shot brake BEFORE returning, so a crash after the append can't
73
- # re-nudge on the next edit. Mirrors the arming order in final-review.ps1.
74
- New-Item -ItemType File -Path $flag -Force -ErrorAction SilentlyContinue | Out-Null
75
-
76
- exit 0
@@ -1,124 +0,0 @@
1
- # minimal-edit-audit.ps1 - afterFileEdit minimal-editing advisory (Cursor).
2
- #
3
- # DEPRECATED in 0.3.0 (superseded by semantic-density-audit.ps1 + the
4
- # declared-editing discipline; removal slated for 0.4.0). The line-count
5
- # heuristic here is the size-based gate the cursordoctrine audit identified
6
- # as the antipattern: it penalizes legitimate large declared changes and
7
- # misses small quiet drifts. Retained for compatibility with existing installs;
8
- # new installs register semantic-density-audit.ps1 alongside it. To opt out of
9
- # the deprecated hook without uninstalling: MINIMAL_EDITING_ENFORCE=0.
10
- #
11
- # Audits the just-edited file for over-editing:
12
- # * line-count - git diff --numstat thresholds (any language). Native git,
13
- # no bash (Git's MSYS bash mangles Windows paths / lacks PATH
14
- # when spawned from pwsh, so we compute this directly).
15
- # * token metrics - audit-metrics.py (token-Levenshtein + cognitive
16
- # complexity), Python files only, via a resolved interpreter.
17
- # On WARN/FAIL it APPENDS a short advisory to the shared pending-feedback file;
18
- # post-tool-use.ps1 delivers it as additional_context on the next tool turn.
19
- #
20
- # Advisory only: never blocks, never writes persistent state. afterFileEdit
21
- # output isn't consumed and a non-zero exit shows as "hook failed", so we
22
- # ALWAYS exit 0. Self-contained.
23
- #
24
- # Thresholds (env-overridable): MINIMAL_EDIT_FAIL_LINES (400), MINIMAL_EDIT_WARN_LINES (100).
25
- # Disable: HOOKS_ENFORCE=0 or MINIMAL_EDITING_ENFORCE=0
26
-
27
- $ErrorActionPreference = 'SilentlyContinue'
28
- . "$PSScriptRoot\hook-common.ps1"
29
-
30
- if ($env:HOOKS_ENFORCE -eq '0' -or $env:MINIMAL_EDITING_ENFORCE -eq '0') { exit 0 }
31
-
32
- $obj = Read-HookStdinJson
33
- if (-not $obj) { exit 0 }
34
-
35
- # audit root = project from JSON (cwd, then workspace_roots), else CURSOR_PROJECT_DIR / HOME
36
- $root = ''
37
- $cands = @()
38
- if ($obj.PSObject.Properties['cwd'] -and $obj.cwd) { $cands += [string]$obj.cwd }
39
- if ($obj.PSObject.Properties['workspace_roots']) { foreach ($w in $obj.workspace_roots) { $cands += [string]$w } }
40
- foreach ($c in $cands) { $f = ConvertTo-FwdPath $c; if ($f -and (Test-Path -LiteralPath $f)) { $root = $f.TrimEnd('/'); break } }
41
- if (-not $root) { $root = (& { if ($env:CURSOR_PROJECT_DIR) { $env:CURSOR_PROJECT_DIR } else { $HOME } }).Replace('\', '/').TrimEnd('/') }
42
-
43
- # edited file -> repo-relative forward-slash path
44
- $fp = ''
45
- foreach ($k in 'file_path', 'path', 'filename', 'absolute_path', 'abs_path') {
46
- if ($obj.PSObject.Properties[$k] -and $obj.$k) { $fp = [string]$obj.$k; break }
47
- }
48
- if (-not $fp) { exit 0 }
49
- $rel = ConvertTo-FwdPath $fp
50
- if ($rel.StartsWith($root + '/', [System.StringComparison]::OrdinalIgnoreCase)) { $rel = $rel.Substring($root.Length + 1) }
51
- if (Test-IsCursorConfigPath $fp) { exit 0 }
52
- if (Test-IsCursorConfigPath $rel) { exit 0 }
53
-
54
- # git repo?
55
- & git -C $root rev-parse --git-dir 2>$null | Out-Null
56
- if ($LASTEXITCODE -ne 0) { exit 0 }
57
-
58
- # --- line-count audit (any language) via native git ----------------------
59
- $failLines = if ($env:MINIMAL_EDIT_FAIL_LINES) { [int]$env:MINIMAL_EDIT_FAIL_LINES } else { 400 }
60
- $warnLines = if ($env:MINIMAL_EDIT_WARN_LINES) { [int]$env:MINIMAL_EDIT_WARN_LINES } else { 100 }
61
- $ins = 0; $del = 0
62
- foreach ($line in (& git -C $root diff HEAD --numstat -- $rel 2>$null)) {
63
- $parts = $line -split "`t"
64
- if ($parts.Count -lt 3 -or $parts[0] -eq '-') { continue } # skip header/binary
65
- $ins += [int]$parts[0]; $del += [int]$parts[1]
66
- }
67
- $changed = $ins + $del
68
-
69
- $grade = 'OK'; $hint = ''
70
- if ($changed -gt $failLines) { $grade = 'FAIL'; $hint = "$changed lines changed (limit $failLines) - likely over-editing; trim or split" }
71
- elseif ($changed -gt $warnLines) { $grade = 'WARN'; $hint = "$changed lines changed - justify each hunk or split the task" }
72
-
73
- # --- token metrics (.py only) via a resolved interpreter -----------------
74
- $auditMetrics = Join-Path $HOME '.cursor\skills\minimal-editing\scripts\audit-metrics.py'
75
- if ((Test-Path $auditMetrics) -and ($rel -match '\.py$')) {
76
- # Windows often ships the interpreter as `py` or `python3`, not `python`.
77
- # Without this resolution the call errored silently and .py metrics never ran.
78
- $py = Get-Command python, python3, py -ErrorAction SilentlyContinue | Select-Object -First 1
79
- if ($py) {
80
- $mout = & $py.Source $auditMetrics --root $root --format json --path $rel 2>$null
81
- $mo = $null; try { $mo = ($mout -join "`n") | ConvertFrom-Json } catch { }
82
- if ($mo -and $mo.grade) {
83
- if ($mo.grade -eq 'FAIL') { $grade = 'FAIL' }
84
- elseif ($mo.grade -eq 'WARN' -and $grade -eq 'OK') { $grade = 'WARN' }
85
- }
86
- }
87
- }
88
-
89
- if ($grade -eq 'OK') { exit 0 }
90
-
91
- # --- compose advisory + append to the shared pending file ----------------
92
- $hintTxt = if ($hint) { " - $hint" } else { '' }
93
- if ($grade -eq 'FAIL') {
94
- $actions = @"
95
- - Trim every hunk that isn't required by the task.
96
- - Prefer narrow, targeted edits over rewriting blocks.
97
- - If the change is genuinely large, split it into smaller logical commits.
98
- "@
99
- } else {
100
- $actions = ' Advisory only - trim unrelated hunks if any; otherwise proceed.'
101
- }
102
-
103
- $msg = @"
104
- Minimal-edit audit $grade - $rel
105
-
106
- IMPORTANT: Try to preserve the original code and the logic of the original code as much as possible.
107
-
108
- grade: $grade$hintTxt
109
-
110
- $actions
111
-
112
- (Disable for this session: HOOKS_ENFORCE=0)
113
- "@
114
-
115
- $cid = Get-SafeConversationId $obj
116
- $pending = Join-Path (Get-HooksPendingDir) "feedback-$cid.txt"
117
- try {
118
- New-Item -ItemType Directory -Path (Split-Path $pending) -Force | Out-Null
119
- $prefix = ''
120
- if ((Test-Path $pending) -and ((Get-Item $pending).Length -gt 0)) { $prefix = "`n`n---`n`n" }
121
- Add-Content -Path $pending -Value ($prefix + $msg) -NoNewline
122
- } catch { }
123
-
124
- exit 0