cursordoctrine 0.4.0 → 0.4.2
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 +16 -6
- package/README.md +70 -17
- package/bin/cli.mjs +34 -12
- package/linux/hooks/anchor-set-nudge.sh +13 -13
- package/linux/hooks/final-review.sh +8 -3
- package/linux/hooks/subagent-stop-review.sh +7 -4
- package/linux/hooks.json +1 -1
- package/package.json +2 -2
- package/skills/anti-slop/SKILL.md +2 -2
- package/windows/hooks/anchor-set-nudge.ps1 +13 -13
- package/windows/hooks/final-review.ps1 +10 -3
- package/windows/hooks/subagent-stop-review.ps1 +7 -4
- package/windows/hooks.json +1 -1
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
|
|
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 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.
|
|
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>
|
|
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.
|
|
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. **
|
|
25
|
-
2. **
|
|
26
|
-
3. **
|
|
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.
|
|
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.
|
|
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.
|
|
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 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.
|
|
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
|
|
104
|
+
| 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. |
|
|
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,
|
|
67
|
-
|
|
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,
|
|
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
|
|
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` 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.
|
|
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
|
@@ -263,19 +263,40 @@ function verify() {
|
|
|
263
263
|
return true;
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
check('anchor-set nudge fires
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
266
|
+
check('anchor-set nudge fires once per turn, stop re-arms it', () => {
|
|
267
|
+
// anchor-set-nudge appends to feedback-<cid>.txt (the shared bus) rather
|
|
268
|
+
// than emitting JSON directly; drain it the same way post-tool-use does.
|
|
269
|
+
const drainedOf = (cidv) => runHook(hook('post-tool-use'), { conversation_id: cidv });
|
|
270
|
+
|
|
271
|
+
// --- Turn 1 -------------------------------------------------------------
|
|
272
|
+
// First edit -> nudge stashes into the feedback bus and arms the latch.
|
|
273
|
+
runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'x.py') });
|
|
274
|
+
if (!drainedOf('npxv3').includes('PRE-COMPILE NUDGE')) {
|
|
275
|
+
return { ok: false, detail: 'nudge did not reach the feedback bus on first edit' };
|
|
276
|
+
}
|
|
277
|
+
// Second edit same turn -> latch armed, nudge must stay silent.
|
|
278
|
+
runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'y.py') });
|
|
279
|
+
if (drainedOf('npxv3').includes('PRE-COMPILE NUDGE')) {
|
|
280
|
+
return { ok: false, detail: 'nudge re-fired on second edit (latch not gating)' };
|
|
281
|
+
}
|
|
282
|
+
// End of turn: final-review clears the latch unconditionally. Drive a
|
|
283
|
+
// review-less stop (no session-edits marker) so it hits the clear path and
|
|
284
|
+
// exits {}, same as a turn that produced no reviewable edits.
|
|
285
|
+
const stopOut = runHook(hook('final-review'), { conversation_id: 'npxv3', status: 'completed' });
|
|
286
|
+
if (stopOut !== '{}' && stopOut.replace(/\s/g, '') !== '{}') {
|
|
287
|
+
// A review fired (fine - the earlier edit left a session-edits marker via
|
|
288
|
+
// self-review-trigger if one ran). What matters is the latch got cleared;
|
|
289
|
+
// we verify that with the next-turn re-fire below.
|
|
290
|
+
}
|
|
291
|
+
// --- Turn 2 -------------------------------------------------------------
|
|
292
|
+
// First edit of the NEXT turn -> latch was cleared at the stop boundary, so
|
|
293
|
+
// the nudge MUST re-fire. This is the regression that 0.4.0 shipped broken:
|
|
294
|
+
// the latch only cleared on the fragile second-stop path, so it stranded
|
|
295
|
+
// and the nudge went permanently silent mid-session.
|
|
296
|
+
runHook(hook('anchor-set-nudge'), { conversation_id: 'npxv3', file_path: join(HOME, 'z.py') });
|
|
297
|
+
if (!drainedOf('npxv3').includes('PRE-COMPILE NUDGE')) {
|
|
298
|
+
return { ok: false, detail: 'nudge did NOT re-fire on the next turn (latch stranded at the stop boundary)' };
|
|
275
299
|
}
|
|
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
300
|
return true;
|
|
280
301
|
});
|
|
281
302
|
|
|
@@ -388,6 +409,7 @@ Examples
|
|
|
388
409
|
Kill switches (environment variables, all hooks fail open)
|
|
389
410
|
HOOKS_ENFORCE=0 everything advisory off
|
|
390
411
|
PERM_GATE_ENFORCE=0 permission gate off
|
|
412
|
+
ANCHOR_NUDGE_ENFORCE=0 pre-compile nudge off (first-edit Anchor Set reminder)
|
|
391
413
|
MINIMAL_EDITING_ENFORCE=0 over-edit advisory off (deprecated in 0.3.0)
|
|
392
414
|
SEMANTIC_DENSITY_ENFORCE=0 semantic-opacity advisory off
|
|
393
415
|
SCOPE_GATE_ENFORCE=0 declared-scope advisory off
|
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
# anchor-set-nudge.sh - afterFileEdit "pre-compile" nudge (Cursor, Linux).
|
|
3
3
|
#
|
|
4
4
|
# Proactive counterpart to the reactive audits. On the FIRST file edit of an
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# from the first edit.
|
|
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.
|
|
12
11
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# final-review.sh / subagent-stop-review.sh
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
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
19
|
#
|
|
20
20
|
# Advisory only: never blocks, never reads the diff, ALWAYS exits 0. Appends to
|
|
21
21
|
# the shared feedback-<cid>.txt bus; post-tool-use.sh delivers it next turn.
|
|
@@ -41,11 +41,16 @@ anchor_flag="$pending_dir/anchor-declared-$cid.flag"
|
|
|
41
41
|
# Sweep state from sessions that died before their stop hook ran.
|
|
42
42
|
find "$pending_dir" -maxdepth 1 -type f -mtime +7 -delete 2>/dev/null
|
|
43
43
|
|
|
44
|
+
# Unconditionally clear the pre-compile nudge's per-turn latch. Every stop is a
|
|
45
|
+
# turn boundary; clearing here (not only inside the reviewed-flag block below)
|
|
46
|
+
# guarantees the nudge re-fires on the first edit of the NEXT turn and can never
|
|
47
|
+
# get stranded silenced. Clearing it only on the SECOND stop left the nudge
|
|
48
|
+
# permanently off for any conversation that never cleanly hit that boundary.
|
|
49
|
+
rm -f "$anchor_flag" 2>/dev/null
|
|
50
|
+
|
|
44
51
|
# 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).
|
|
47
52
|
if [ -f "$flag" ]; then
|
|
48
|
-
rm -f "$flag" "$marker"
|
|
53
|
+
rm -f "$flag" "$marker" 2>/dev/null
|
|
49
54
|
emit_none
|
|
50
55
|
fi
|
|
51
56
|
|
|
@@ -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
|
|
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
|
|
@@ -46,11 +46,14 @@ marker="$pending_dir/session-edits-$cid.txt"
|
|
|
46
46
|
flag="$pending_dir/reviewed-$cid.flag"
|
|
47
47
|
anchor_flag="$pending_dir/anchor-declared-$cid.flag"
|
|
48
48
|
|
|
49
|
+
# Unconditionally clear the pre-compile nudge's per-turn latch so it re-fires
|
|
50
|
+
# on the first edit of the next subagent run. Clearing here (not only inside
|
|
51
|
+
# the reviewed-flag block below) can never strand the nudge silenced.
|
|
52
|
+
rm -f "$anchor_flag" 2>/dev/null
|
|
53
|
+
|
|
49
54
|
# 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).
|
|
52
55
|
if [ -f "$flag" ]; then
|
|
53
|
-
rm -f "$flag" "$marker"
|
|
56
|
+
rm -f "$flag" "$marker" 2>/dev/null
|
|
54
57
|
emit_none
|
|
55
58
|
fi
|
|
56
59
|
|
package/linux/hooks.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"command": "bash ~/.agents/hooks/anchor-set-nudge.sh",
|
|
44
44
|
"timeout": 5,
|
|
45
45
|
"matcher": "^(Write|StrReplace|EditNotebook)$",
|
|
46
|
-
"_comment": "5s: PROACTIVE pre-compile nudge. On the FIRST edit of each
|
|
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."
|
|
47
47
|
}
|
|
48
48
|
],
|
|
49
49
|
"postToolUse": [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursordoctrine",
|
|
3
|
-
"version": "0.4.
|
|
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.",
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Thin self-review hooks for Cursor — the model is the auditor. Pre-compile Anchor Set phase (proactive intent compilation, fires once per turn), 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
|
|
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.
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# anchor-set-nudge.ps1 - afterFileEdit "pre-compile" nudge (Cursor).
|
|
2
2
|
#
|
|
3
3
|
# Proactive counterpart to the reactive audits. On the FIRST file edit of an
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# from the first edit.
|
|
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.
|
|
11
10
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# final-review.ps1 / subagent-stop-review.ps1
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
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
18
|
#
|
|
19
19
|
# Advisory only: never blocks, never reads the diff, ALWAYS exits 0. Appends to
|
|
20
20
|
# the shared feedback-<cid>.txt bus; post-tool-use.ps1 delivers it next turn.
|
|
@@ -48,12 +48,19 @@ Get-ChildItem $pendingDir -File -ErrorAction SilentlyContinue |
|
|
|
48
48
|
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-7) } |
|
|
49
49
|
Remove-Item -Force -ErrorAction SilentlyContinue
|
|
50
50
|
|
|
51
|
+
# Unconditionally clear the pre-compile nudge's per-turn latch. Every stop is a
|
|
52
|
+
# turn boundary; clearing here (not only inside the reviewed-flag block below)
|
|
53
|
+
# guarantees the nudge re-fires on the first edit of the NEXT turn and can never
|
|
54
|
+
# get stranded silenced. The original design cleared anchor-declared-<cid>.flag
|
|
55
|
+
# only on the SECOND stop (the reviewed-flag path), so any conversation that
|
|
56
|
+
# never cleanly hit that boundary left the nudge permanently off - the agent
|
|
57
|
+
# then stopped being reminded to write .scope.json.
|
|
58
|
+
Remove-Item $anchorFlag -Force -ErrorAction SilentlyContinue
|
|
59
|
+
|
|
51
60
|
# One-shot brake: the previous stop for this conversation emitted the review.
|
|
52
61
|
# Clear the flag (and whatever the review pass itself edited) and end the loop.
|
|
53
|
-
# Also clear anchor-declared-<cid>.flag so the pre-compile nudge re-fires for
|
|
54
|
-
# the NEXT implementation (one nudge per body of work, not per session).
|
|
55
62
|
if (Test-Path $flag) {
|
|
56
|
-
Remove-Item $flag, $marker
|
|
63
|
+
Remove-Item $flag, $marker -Force -ErrorAction SilentlyContinue
|
|
57
64
|
Emit-None
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Counterpart of final-review.ps1 for delegated work. afterFileEdit DOES fire
|
|
4
4
|
# inside subagents (verified: a poteto subagent run left ~58 entries in
|
|
5
5
|
# session-edits-<subagent-cid>.txt), but subagents get no `stop` event, so
|
|
6
|
-
# that marker is never drained and the
|
|
6
|
+
# that marker is never drained and the six-axis review never fires for
|
|
7
7
|
# delegated implementations. This hook closes the loop: when a subagent
|
|
8
8
|
# finishes and ITS conversation has a session-edits marker, return ONE
|
|
9
9
|
# followup_message so the subagent audits its own implementation before the
|
|
@@ -45,11 +45,14 @@ $marker = Join-Path $pendingDir "session-edits-$cid.txt"
|
|
|
45
45
|
$flag = Join-Path $pendingDir "reviewed-$cid.flag"
|
|
46
46
|
$anchorFlag = Join-Path $pendingDir "anchor-declared-$cid.flag"
|
|
47
47
|
|
|
48
|
+
# Unconditionally clear the pre-compile nudge's per-turn latch so it re-fires
|
|
49
|
+
# on the first edit of the next subagent run. Clearing here (not only inside
|
|
50
|
+
# the reviewed-flag block below) can never strand the nudge silenced.
|
|
51
|
+
Remove-Item $anchorFlag -Force -ErrorAction SilentlyContinue
|
|
52
|
+
|
|
48
53
|
# One-shot brake: the previous subagentStop for this id emitted the review.
|
|
49
|
-
# Also clear anchor-declared-<cid>.flag so the pre-compile nudge re-fires for
|
|
50
|
-
# the next subagent implementation (one nudge per body of work).
|
|
51
54
|
if (Test-Path $flag) {
|
|
52
|
-
Remove-Item $flag, $marker
|
|
55
|
+
Remove-Item $flag, $marker -Force -ErrorAction SilentlyContinue
|
|
53
56
|
Emit-None
|
|
54
57
|
}
|
|
55
58
|
|
package/windows/hooks.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"command": "pwsh.exe -NoProfile -File ~/.agents/hooks/anchor-set-nudge.ps1",
|
|
44
44
|
"timeout": 5,
|
|
45
45
|
"matcher": "^(Write|StrReplace|EditNotebook)$",
|
|
46
|
-
"_comment": "5s: PROACTIVE pre-compile nudge. On the FIRST edit of each
|
|
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."
|
|
47
47
|
}
|
|
48
48
|
],
|
|
49
49
|
"postToolUse": [
|