cursordoctrine 0.6.2 → 0.6.4
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/linux/doctrine.md +4 -0
- package/linux/hooks/hook-common.sh +43 -12
- package/linux/hooks/intent-anchor.sh +7 -5
- package/linux/pre-compile.md +44 -14
- package/package.json +1 -1
- package/windows/doctrine.md +4 -0
- package/windows/hooks/hook-common.ps1 +42 -4
- package/windows/hooks/intent-anchor.ps1 +5 -3
- package/windows/pre-compile.md +44 -14
package/linux/doctrine.md
CHANGED
|
@@ -69,6 +69,10 @@ what the task requires**.
|
|
|
69
69
|
|
|
70
70
|
The natural order is:
|
|
71
71
|
|
|
72
|
+
0. **Restate** the request in one normalized line (`Understood as: …`) before
|
|
73
|
+
you touch a tool — grammar fixed, ambiguity resolved, meaning preserved.
|
|
74
|
+
It is the user's catch-point for a misread, and it becomes the contract's
|
|
75
|
+
`intent`. Full spec in pre-compile.
|
|
72
76
|
1. **Read** the relevant file(s) to understand context.
|
|
73
77
|
2. **Edit** to make the change.
|
|
74
78
|
3. **Read** the file again (the self-review trigger will remind you).
|
|
@@ -105,7 +105,7 @@ resolve_agent_path() {
|
|
|
105
105
|
esac
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
# extract_last_user_query <json> -> text of the last <user_query> in this
|
|
108
|
+
# extract_last_user_query <json> -> text of the last *human* <user_query> in this
|
|
109
109
|
# conversation's transcript, or '' if there is none. Capped at 2000 chars.
|
|
110
110
|
#
|
|
111
111
|
# This is the Tier 0 intent-trace primitive: the final-review hook prepends the
|
|
@@ -113,7 +113,14 @@ resolve_agent_path() {
|
|
|
113
113
|
# to it. Anything untraceable is a hallucinated requirement.
|
|
114
114
|
#
|
|
115
115
|
# Walks the JSONL backward via tac (preferred) or a portable awk fallback; finds
|
|
116
|
-
# the first (last) user record whose content carries a <user_query> tag
|
|
116
|
+
# the first (last) user record whose content carries a <user_query> tag - SKIPPING
|
|
117
|
+
# hook-generated turns. final-review.sh / subagent-stop-review.sh emit a
|
|
118
|
+
# {followup_message} that Cursor replays as a user turn (and self-review /
|
|
119
|
+
# intent-anchor inject into additional_context); returning one of those would lock
|
|
120
|
+
# the review boilerplate into .scope.json as the intent (the contamination loop).
|
|
121
|
+
# Hook turns are detected by their fixed headers; if the transcript has been
|
|
122
|
+
# trimmed to just the hook turn, recover the real request from the embedded
|
|
123
|
+
# "ORIGINAL REQUEST ... --- <request> ---" block.
|
|
117
124
|
extract_last_user_query() {
|
|
118
125
|
local json="$1"
|
|
119
126
|
local tp
|
|
@@ -134,6 +141,14 @@ extract_last_user_query() {
|
|
|
134
141
|
if have_py; then
|
|
135
142
|
printf '%s' "$reversed" | python3 -c '
|
|
136
143
|
import json, re, sys
|
|
144
|
+
HOOK_HDR = re.compile(r"^\s*(FINAL REVIEW \(end of implementation\)|SUBAGENT FINAL REVIEW|SELF-REVIEW|INTENT ANCHOR)", re.M)
|
|
145
|
+
EMBEDDED = re.compile(r"ORIGINAL REQUEST[^\r\n]*\r?\n-{3,}\r?\n(.+?)\r?\n-{3,}", re.S)
|
|
146
|
+
def redact(q):
|
|
147
|
+
q = re.sub(r"\bnpm_[A-Za-z0-9]{10,}\b", "[REDACTED_NPM_TOKEN]", q)
|
|
148
|
+
q = re.sub(r"\b(sk-[A-Za-z0-9]{10,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,})\b", "[REDACTED_TOKEN]", q)
|
|
149
|
+
q = re.sub(r"(?i)(api[_-]?key|token|secret|password)\s*[:=]\s*\S+", r"\1=[REDACTED]", q)
|
|
150
|
+
return q
|
|
151
|
+
embedded_fallback = ""
|
|
137
152
|
try:
|
|
138
153
|
for line in sys.stdin:
|
|
139
154
|
line = line.strip()
|
|
@@ -155,15 +170,26 @@ try:
|
|
|
155
170
|
if isinstance(p, dict) and p.get("type") == "text" and p.get("text"):
|
|
156
171
|
text += p["text"]
|
|
157
172
|
m = re.search(r"<user_query>\s*(.+?)\s*</user_query>", text, re.S)
|
|
158
|
-
if m:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
if not m:
|
|
174
|
+
continue
|
|
175
|
+
q = m.group(1).strip()
|
|
176
|
+
# Hook-generated turn -> not the human words. Remember the embedded
|
|
177
|
+
# ORIGINAL REQUEST (latest such turn) and keep walking back.
|
|
178
|
+
if HOOK_HDR.search(q):
|
|
179
|
+
if not embedded_fallback:
|
|
180
|
+
em = EMBEDDED.search(q)
|
|
181
|
+
if em:
|
|
182
|
+
embedded_fallback = em.group(1).strip()
|
|
183
|
+
continue
|
|
184
|
+
if len(q) > 2000:
|
|
185
|
+
q = q[:2000] + "..."
|
|
186
|
+
print(redact(q))
|
|
187
|
+
break
|
|
188
|
+
else:
|
|
189
|
+
if embedded_fallback:
|
|
190
|
+
if len(embedded_fallback) > 2000:
|
|
191
|
+
embedded_fallback = embedded_fallback[:2000] + "..."
|
|
192
|
+
print(redact(embedded_fallback))
|
|
167
193
|
except Exception:
|
|
168
194
|
pass
|
|
169
195
|
' 2>/dev/null
|
|
@@ -172,9 +198,14 @@ except Exception:
|
|
|
172
198
|
|
|
173
199
|
# No python3: best-effort grep for the common case where the user message
|
|
174
200
|
# is the only place <user_query> appears in a line. Imperfect but bounded.
|
|
201
|
+
# Drop hook-generated turns (FINAL REVIEW / SUBAGENT / SELF-REVIEW / INTENT
|
|
202
|
+
# ANCHOR) so we never lock review boilerplate as the intent; take the first
|
|
203
|
+
# surviving human <user_query>.
|
|
175
204
|
printf '%s' "$reversed" |
|
|
176
|
-
grep -
|
|
205
|
+
grep -oE '<user_query>[^<]*</user_query>' 2>/dev/null |
|
|
177
206
|
sed -E 's@</?user_query>@@g' |
|
|
207
|
+
grep -vE '^[[:space:]]*(FINAL REVIEW \(end of implementation\)|SUBAGENT FINAL REVIEW|SELF-REVIEW|INTENT ANCHOR)' 2>/dev/null |
|
|
208
|
+
head -n1 |
|
|
178
209
|
sed -E 's/\bnpm_[A-Za-z0-9]{10,}\b/[REDACTED_NPM_TOKEN]/g' |
|
|
179
210
|
head -c 2000
|
|
180
211
|
}
|
|
@@ -150,12 +150,14 @@ EOF
|
|
|
150
150
|
# changed (or a new session) => regenerate and RESET files[] (the "arrastre entre
|
|
151
151
|
# features" fix). Same prompt this session => heal in place (backfill bookkeeping, keep
|
|
152
152
|
# files[]/acceptance) so the NEXT prompt is detected by hash.
|
|
153
|
-
# Hollow = no real intent on disk: empty,
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
153
|
+
# Hollow = no real intent on disk: empty, the hook's <TODO> placeholder, OR
|
|
154
|
+
# hook-generated review boilerplate that a stale extractor locked in (the
|
|
155
|
+
# contamination loop - "FINAL REVIEW (end of implementation)..."). A hollow
|
|
156
|
+
# contract is worse than none (it looks owned, so neither hook nor agent fills
|
|
157
|
+
# it). Treat it as unusable: regenerate when the request is readable, else hand
|
|
158
|
+
# the agent the pre-compile demand to author a real one.
|
|
157
159
|
case "$scope_intent" in
|
|
158
|
-
""|"<TODO"*) scope_hollow=1 ;;
|
|
160
|
+
""|"<TODO"*|"FINAL REVIEW (end of implementation)"*|"SUBAGENT FINAL REVIEW"*|"SELF-REVIEW"*|"INTENT ANCHOR"*) scope_hollow=1 ;;
|
|
159
161
|
esac
|
|
160
162
|
if [ "$scope_exists" = "1" ] && [ "$has_query" = "1" ]; then
|
|
161
163
|
if [ "$scope_hollow" = "1" ]; then
|
package/linux/pre-compile.md
CHANGED
|
@@ -9,12 +9,34 @@ This is the proactive phase. The anti-slop checklist, the self-review trigger
|
|
|
9
9
|
and the final review are reactive — they audit after the fact. You compile the
|
|
10
10
|
intent BEFORE the first token of code so they have the right thing to audit.
|
|
11
11
|
|
|
12
|
+
## Step 0 — Restate the request (the user writes fast; you normalize)
|
|
13
|
+
|
|
14
|
+
Before the Anchor Set, in ONE line, play the request back as you understood it:
|
|
15
|
+
|
|
16
|
+
> **Understood as:** _one clean sentence — grammar fixed, pronouns resolved,
|
|
17
|
+
> implicit constraints made explicit, in the language of the request._
|
|
18
|
+
|
|
19
|
+
Mandatory every implementation turn. This line is the user's catch-point: a
|
|
20
|
+
misread surfaces HERE, before you write a line of code, instead of in review.
|
|
21
|
+
The restatement is **meaning-preserving** — you normalize phrasing, you do NOT
|
|
22
|
+
add scope, drop a constraint, or invent a requirement. If normalizing would
|
|
23
|
+
force a guess that changes what "correct" means, you do not bury the guess in
|
|
24
|
+
the restatement — you ask one sharp question (§5) and wait.
|
|
25
|
+
|
|
26
|
+
The user's verbatim words stay the ground truth: `.scope.json`'s `trace.query`
|
|
27
|
+
keeps them exactly as typed, and final-review traces every diff hunk back to
|
|
28
|
+
THAT, not to your paraphrase. Your normalized sentence becomes `intent` (and the
|
|
29
|
+
Anchor Set's OBJECTIVE below). The two must say the same thing in different
|
|
30
|
+
words — if you cannot make `intent` and `trace.query` agree, you have misread
|
|
31
|
+
the request, and that is the bug to fix first.
|
|
32
|
+
|
|
12
33
|
## The Anchor Set
|
|
13
34
|
|
|
14
35
|
Answer these four, terse, in your first response. One phrase each, not prose:
|
|
15
36
|
|
|
16
|
-
1. OBJECTIVE —
|
|
17
|
-
"improve X" — "make X return Y
|
|
37
|
+
1. OBJECTIVE — your Step 0 restatement, tightened to the operational verb. One
|
|
38
|
+
sentence, what is *strictly* necessary. Not "improve X" — "make X return Y
|
|
39
|
+
when Z".
|
|
18
40
|
2. CONSTRAINTS (local negations) — what you will NOT do. "NO schema migration.
|
|
19
41
|
NO new dependency. NO refactor of the surrounding function." Negations bind
|
|
20
42
|
harder than the objective: a constraint that the task contradicts is a bug
|
|
@@ -33,28 +55,36 @@ Answer these four, terse, in your first response. One phrase each, not prose:
|
|
|
33
55
|
|
|
34
56
|
The `intent-anchor` hook creates and maintains `.scope.json` in the repo root for
|
|
35
57
|
you, automatically, on the first tool of every turn:
|
|
36
|
-
- `intent` is
|
|
58
|
+
- `intent` is seeded from your verbatim request and REFRESHED when the request
|
|
37
59
|
changes — a new prompt regenerates the contract and resets `files[]`, so it
|
|
38
60
|
never carries over between features;
|
|
39
61
|
- `files[]` is auto-recorded — the scope hook appends every file you edit, so
|
|
40
62
|
you never maintain it by hand;
|
|
41
|
-
- `trace
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Your
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
- `trace.query` is the VERBATIM request (the audit anchor), `_intent_hash` and
|
|
64
|
+
`_generated_by` are hook bookkeeping. Leave all three alone.
|
|
65
|
+
|
|
66
|
+
Your two targeted edits on the contract (each a string replace on ONE field, never
|
|
67
|
+
a whole-file rewrite):
|
|
68
|
+
- **`intent`** → replace the verbatim seed with your Step 0 restatement: the
|
|
69
|
+
normalized, meaning-preserving sentence. This is what final-review axis 0
|
|
70
|
+
traces each diff hunk against, so a clean `intent` makes the audit sharp.
|
|
71
|
+
- **`acceptance`** → set the single deterministic check that decides done, which
|
|
72
|
+
the hook cannot derive.
|
|
73
|
+
|
|
74
|
+
Do **NOT** touch `trace.query`, `_intent_hash`, or `_generated_by`, and do **NOT**
|
|
75
|
+
rewrite the whole file: `_intent_hash` is computed from the verbatim `trace.query`,
|
|
76
|
+
not from `intent`, so refining `intent` is safe — but dropping the hash/trace
|
|
77
|
+
disables per-prompt regeneration and brings back cross-feature carryover. Keeping
|
|
78
|
+
`trace.query` verbatim is what lets the audit catch a paraphrase that quietly
|
|
79
|
+
changed the meaning: `intent` and `trace.query` must agree.
|
|
50
80
|
|
|
51
81
|
```json
|
|
52
82
|
{
|
|
53
|
-
"intent": "<
|
|
83
|
+
"intent": "<YOU refine this: your normalized Step 0 restatement>",
|
|
54
84
|
"files": ["<auto-recorded by the hook as you edit>"],
|
|
55
85
|
"acceptance": "<YOU set this: the deterministic check that decides done>",
|
|
56
86
|
"allow_growth": false,
|
|
57
|
-
"trace": { "query": "<
|
|
87
|
+
"trace": { "query": "<VERBATIM request - the hook owns this, leave it>", "ts": "<when>" },
|
|
58
88
|
"_intent_hash": "<hook bookkeeping>",
|
|
59
89
|
"_generated_by":"intent-anchor hook"
|
|
60
90
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursordoctrine",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
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"
|
package/windows/doctrine.md
CHANGED
|
@@ -67,6 +67,10 @@ what the task requires**.
|
|
|
67
67
|
|
|
68
68
|
The natural order is:
|
|
69
69
|
|
|
70
|
+
0. **Restate** the request in one normalized line (`Understood as: …`) before
|
|
71
|
+
you touch a tool — grammar fixed, ambiguity resolved, meaning preserved.
|
|
72
|
+
It is the user's catch-point for a misread, and it becomes the contract's
|
|
73
|
+
`intent`. Full spec in pre-compile.
|
|
70
74
|
1. **Read** the relevant file(s) to understand context.
|
|
71
75
|
2. **Edit** to make the change.
|
|
72
76
|
3. **Read** the file again (the self-review trigger will remind you).
|
|
@@ -83,11 +83,36 @@ function Redact-SecretsFromIntent([string]$text) {
|
|
|
83
83
|
return $text
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
#
|
|
86
|
+
# A <user_query> turn can actually be a HOOK-GENERATED message replayed by Cursor
|
|
87
|
+
# as a user turn: final-review.ps1 and subagent-stop-review.ps1 emit a
|
|
88
|
+
# {followup_message} that Cursor auto-submits as the next user turn, and the
|
|
89
|
+
# self-review / intent-anchor injections get drained into additional_context.
|
|
90
|
+
# If Get-LastUserQuery returns one of those, intent-anchor locks the REVIEW
|
|
91
|
+
# BOILERPLATE into .scope.json as the "intent" (the contamination loop that put
|
|
92
|
+
# "FINAL REVIEW (end of implementation)..." in the contract). Detect them by the
|
|
93
|
+
# fixed headers the hooks emit and skip past them to the real human turn.
|
|
94
|
+
function Test-IsHookGeneratedQuery([string]$text) {
|
|
95
|
+
if (-not $text) { return $false }
|
|
96
|
+
return ($text -match '(?m)^\s*(FINAL REVIEW \(end of implementation\)|SUBAGENT FINAL REVIEW|SELF-REVIEW|INTENT ANCHOR)')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# The final-review / subagent-review followups embed the real human request as:
|
|
100
|
+
# ORIGINAL REQUEST (...):\n---\n<request>\n---
|
|
101
|
+
# When the transcript has been trimmed to just the hook turn, recover the human
|
|
102
|
+
# request from that block instead of returning the boilerplate.
|
|
103
|
+
function Get-EmbeddedOriginalRequest([string]$text) {
|
|
104
|
+
if (-not $text) { return '' }
|
|
105
|
+
if ($text -match '(?s)ORIGINAL REQUEST[^\r\n]*\r?\n-{3,}\r?\n(.+?)\r?\n-{3,}') {
|
|
106
|
+
return $Matches[1].Trim()
|
|
107
|
+
}
|
|
108
|
+
return ''
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Extract the last *human* user <user_query> from a Cursor transcript JSONL.
|
|
87
112
|
# transcript is an array of {role, message} records; we walk backward from the
|
|
88
|
-
# end,
|
|
89
|
-
#
|
|
90
|
-
# 2000 chars so the follow-up prompt stays bounded.
|
|
113
|
+
# end, skipping hook-generated turns (see above), and return the first real human
|
|
114
|
+
# turn's text. Returns '' if there is no transcript or no human user_query. Capped
|
|
115
|
+
# at 2000 chars so the follow-up prompt stays bounded.
|
|
91
116
|
#
|
|
92
117
|
# This is the Tier 0 intent-trace primitive: the final-review hook prepends the
|
|
93
118
|
# extracted request to its followup so the model must trace every diff hunk back
|
|
@@ -97,6 +122,7 @@ function Get-LastUserQuery($obj) {
|
|
|
97
122
|
if ($obj -and $obj.PSObject.Properties['transcript_path']) { $tp = [string]$obj.transcript_path }
|
|
98
123
|
if (-not $tp -or -not (Test-Path -LiteralPath $tp)) { return '' }
|
|
99
124
|
$lines = @(Get-Content -LiteralPath $tp -ErrorAction SilentlyContinue)
|
|
125
|
+
$embeddedFallback = ''
|
|
100
126
|
for ($i = $lines.Count - 1; $i -ge 0; $i--) {
|
|
101
127
|
$line = $lines[$i]
|
|
102
128
|
if (-not $line -or $line -notmatch '"role"\s*:\s*"user"') { continue }
|
|
@@ -117,10 +143,22 @@ function Get-LastUserQuery($obj) {
|
|
|
117
143
|
}
|
|
118
144
|
if ($text -match '(?s)<user_query>\s*(.+?)\s*</user_query>') {
|
|
119
145
|
$q = $Matches[1].Trim()
|
|
146
|
+
# Hook-generated turn -> not the human's words. Remember the embedded
|
|
147
|
+
# ORIGINAL REQUEST (from the most recent such turn) and keep walking.
|
|
148
|
+
if (Test-IsHookGeneratedQuery $q) {
|
|
149
|
+
if (-not $embeddedFallback) { $embeddedFallback = Get-EmbeddedOriginalRequest $q }
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
120
152
|
if ($q.Length -gt 2000) { $q = $q.Substring(0, 2000) + '...' }
|
|
121
153
|
return (Redact-SecretsFromIntent $q)
|
|
122
154
|
}
|
|
123
155
|
}
|
|
156
|
+
# No real human turn survived in the transcript -> fall back to the request
|
|
157
|
+
# embedded in the latest hook followup, if we found one.
|
|
158
|
+
if ($embeddedFallback) {
|
|
159
|
+
if ($embeddedFallback.Length -gt 2000) { $embeddedFallback = $embeddedFallback.Substring(0, 2000) + '...' }
|
|
160
|
+
return (Redact-SecretsFromIntent $embeddedFallback)
|
|
161
|
+
}
|
|
124
162
|
return ''
|
|
125
163
|
}
|
|
126
164
|
|
|
@@ -127,12 +127,14 @@ if (Test-Path -LiteralPath $scopePath) {
|
|
|
127
127
|
if ([string]::IsNullOrWhiteSpace($scopeFiles)) { $scopeFiles = '(none yet - auto-tracked as you edit)' }
|
|
128
128
|
$scopeExists = $true
|
|
129
129
|
$scopeHasHash = ($sj.PSObject.Properties['_intent_hash'] -and -not [string]::IsNullOrWhiteSpace([string]$sj._intent_hash))
|
|
130
|
-
# Hollow = no real intent on disk: empty,
|
|
130
|
+
# Hollow = no real intent on disk: empty, the hook's <TODO> placeholder, OR
|
|
131
|
+
# hook-generated review boilerplate that a stale extractor locked in as the
|
|
132
|
+
# intent (the contamination loop - "FINAL REVIEW (end of implementation)...").
|
|
131
133
|
# A hollow contract is worse than none (it looks owned, so neither hook nor agent
|
|
132
|
-
# fills it; scope-gate appends files to it and final-review audits against
|
|
134
|
+
# fills it; scope-gate appends files to it and final-review audits against garbage).
|
|
133
135
|
# Treat it as unusable: regenerate when we can read the request, else hand the agent
|
|
134
136
|
# the pre-compile demand to write a real one.
|
|
135
|
-
$scopeHollow = ([string]::IsNullOrWhiteSpace($scopeIntent) -or $scopeIntent -match '^\s*<TODO')
|
|
137
|
+
$scopeHollow = ([string]::IsNullOrWhiteSpace($scopeIntent) -or $scopeIntent -match '^\s*<TODO' -or (Test-IsHookGeneratedQuery $scopeIntent))
|
|
136
138
|
# Staleness, hash-agnostic so it survives MODEL-written contracts:
|
|
137
139
|
# - hook-written (has _intent_hash): stale when that hash != current query hash.
|
|
138
140
|
# - model-written (no _intent_hash - the legacy pre-compile.md schema): we cannot
|
package/windows/pre-compile.md
CHANGED
|
@@ -9,12 +9,34 @@ This is the proactive phase. The anti-slop checklist, the self-review trigger
|
|
|
9
9
|
and the final review are reactive — they audit after the fact. You compile the
|
|
10
10
|
intent BEFORE the first token of code so they have the right thing to audit.
|
|
11
11
|
|
|
12
|
+
## Step 0 — Restate the request (the user writes fast; you normalize)
|
|
13
|
+
|
|
14
|
+
Before the Anchor Set, in ONE line, play the request back as you understood it:
|
|
15
|
+
|
|
16
|
+
> **Understood as:** _one clean sentence — grammar fixed, pronouns resolved,
|
|
17
|
+
> implicit constraints made explicit, in the language of the request._
|
|
18
|
+
|
|
19
|
+
Mandatory every implementation turn. This line is the user's catch-point: a
|
|
20
|
+
misread surfaces HERE, before you write a line of code, instead of in review.
|
|
21
|
+
The restatement is **meaning-preserving** — you normalize phrasing, you do NOT
|
|
22
|
+
add scope, drop a constraint, or invent a requirement. If normalizing would
|
|
23
|
+
force a guess that changes what "correct" means, you do not bury the guess in
|
|
24
|
+
the restatement — you ask one sharp question (§5) and wait.
|
|
25
|
+
|
|
26
|
+
The user's verbatim words stay the ground truth: `.scope.json`'s `trace.query`
|
|
27
|
+
keeps them exactly as typed, and final-review traces every diff hunk back to
|
|
28
|
+
THAT, not to your paraphrase. Your normalized sentence becomes `intent` (and the
|
|
29
|
+
Anchor Set's OBJECTIVE below). The two must say the same thing in different
|
|
30
|
+
words — if you cannot make `intent` and `trace.query` agree, you have misread
|
|
31
|
+
the request, and that is the bug to fix first.
|
|
32
|
+
|
|
12
33
|
## The Anchor Set
|
|
13
34
|
|
|
14
35
|
Answer these four, terse, in your first response. One phrase each, not prose:
|
|
15
36
|
|
|
16
|
-
1. OBJECTIVE —
|
|
17
|
-
"improve X" — "make X return Y
|
|
37
|
+
1. OBJECTIVE — your Step 0 restatement, tightened to the operational verb. One
|
|
38
|
+
sentence, what is *strictly* necessary. Not "improve X" — "make X return Y
|
|
39
|
+
when Z".
|
|
18
40
|
2. CONSTRAINTS (local negations) — what you will NOT do. "NO schema migration.
|
|
19
41
|
NO new dependency. NO refactor of the surrounding function." Negations bind
|
|
20
42
|
harder than the objective: a constraint that the task contradicts is a bug
|
|
@@ -33,28 +55,36 @@ Answer these four, terse, in your first response. One phrase each, not prose:
|
|
|
33
55
|
|
|
34
56
|
The `intent-anchor` hook creates and maintains `.scope.json` in the repo root for
|
|
35
57
|
you, automatically, on the first tool of every turn:
|
|
36
|
-
- `intent` is
|
|
58
|
+
- `intent` is seeded from your verbatim request and REFRESHED when the request
|
|
37
59
|
changes — a new prompt regenerates the contract and resets `files[]`, so it
|
|
38
60
|
never carries over between features;
|
|
39
61
|
- `files[]` is auto-recorded — the scope hook appends every file you edit, so
|
|
40
62
|
you never maintain it by hand;
|
|
41
|
-
- `trace
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Your
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
- `trace.query` is the VERBATIM request (the audit anchor), `_intent_hash` and
|
|
64
|
+
`_generated_by` are hook bookkeeping. Leave all three alone.
|
|
65
|
+
|
|
66
|
+
Your two targeted edits on the contract (each a string replace on ONE field, never
|
|
67
|
+
a whole-file rewrite):
|
|
68
|
+
- **`intent`** → replace the verbatim seed with your Step 0 restatement: the
|
|
69
|
+
normalized, meaning-preserving sentence. This is what final-review axis 0
|
|
70
|
+
traces each diff hunk against, so a clean `intent` makes the audit sharp.
|
|
71
|
+
- **`acceptance`** → set the single deterministic check that decides done, which
|
|
72
|
+
the hook cannot derive.
|
|
73
|
+
|
|
74
|
+
Do **NOT** touch `trace.query`, `_intent_hash`, or `_generated_by`, and do **NOT**
|
|
75
|
+
rewrite the whole file: `_intent_hash` is computed from the verbatim `trace.query`,
|
|
76
|
+
not from `intent`, so refining `intent` is safe — but dropping the hash/trace
|
|
77
|
+
disables per-prompt regeneration and brings back cross-feature carryover. Keeping
|
|
78
|
+
`trace.query` verbatim is what lets the audit catch a paraphrase that quietly
|
|
79
|
+
changed the meaning: `intent` and `trace.query` must agree.
|
|
50
80
|
|
|
51
81
|
```json
|
|
52
82
|
{
|
|
53
|
-
"intent": "<
|
|
83
|
+
"intent": "<YOU refine this: your normalized Step 0 restatement>",
|
|
54
84
|
"files": ["<auto-recorded by the hook as you edit>"],
|
|
55
85
|
"acceptance": "<YOU set this: the deterministic check that decides done>",
|
|
56
86
|
"allow_growth": false,
|
|
57
|
-
"trace": { "query": "<
|
|
87
|
+
"trace": { "query": "<VERBATIM request - the hook owns this, leave it>", "ts": "<when>" },
|
|
58
88
|
"_intent_hash": "<hook bookkeeping>",
|
|
59
89
|
"_generated_by":"intent-anchor hook"
|
|
60
90
|
}
|