cursordoctrine 0.5.5 → 0.6.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/bin/cli.mjs +50 -0
- package/linux/USER-RULES.md +45 -12
- package/linux/hooks/final-review.sh +1 -1
- package/linux/hooks/intent-anchor.sh +107 -42
- package/linux/pre-compile.md +40 -17
- package/package.json +1 -1
- package/skills/anti-slop/scripts/scope_match.py +6 -2
- package/windows/USER-RULES.md +45 -12
- package/windows/hooks/final-review.ps1 +1 -1
- package/windows/hooks/intent-anchor.ps1 +96 -37
- package/windows/pre-compile.md +40 -17
package/bin/cli.mjs
CHANGED
|
@@ -408,6 +408,56 @@ function verify() {
|
|
|
408
408
|
if (!scope._generated_by || !scope._intent_hash) {
|
|
409
409
|
cleanup(); return { ok: false, detail: 'scaffold missing _generated_by / _intent_hash fields' };
|
|
410
410
|
}
|
|
411
|
+
|
|
412
|
+
// --- Case C: MODEL-written scope (no _intent_hash) + a NEW prompt --------
|
|
413
|
+
// Reproduces the shipped bug (chiquipuesto/WAVE): the model wrote .scope.json
|
|
414
|
+
// per the legacy 4-field schema, so the hook could never detect staleness and
|
|
415
|
+
// scope-gate kept appending files across features. A new prompt (fresh cid =>
|
|
416
|
+
// promptChanged) must now REGENERATE and RESET files[].
|
|
417
|
+
const mkT = (p, q) => writeFileSync(p,
|
|
418
|
+
JSON.stringify({ role: 'user', message: { content: `<user_query>${q}</user_query>` } }) + '\n', 'utf8');
|
|
419
|
+
const cidC = 'npxv5';
|
|
420
|
+
const transcriptC = join(HOME, '.cursor', '.hooks-pending', 'verify-transcript-npxv5.jsonl');
|
|
421
|
+
const failC = (detail) => { try { rmSync(transcriptC, { force: true }); } catch {} cleanup(); return { ok: false, detail }; };
|
|
422
|
+
try { rmSync(scopePath, { force: true }); } catch {}
|
|
423
|
+
writeFileSync(scopePath, JSON.stringify({ intent: q1, files: ['a.ts', 'b.ts'], acceptance: 'keep', allow_growth: false }, null, 2), 'utf8');
|
|
424
|
+
mkT(transcriptC, q2);
|
|
425
|
+
runHook(hook('intent-anchor'), { conversation_id: cidC, cwd: repoDir, transcript_path: transcriptC });
|
|
426
|
+
drainedOf(cidC);
|
|
427
|
+
let sc;
|
|
428
|
+
try { sc = JSON.parse(readFileSync(scopePath, 'utf8')); } catch { return failC('model-written scope corrupted after new prompt'); }
|
|
429
|
+
if (sc.intent !== q2) return failC(`carryover not regenerated (want "${q2}"): ${sc.intent}`);
|
|
430
|
+
if (!Array.isArray(sc.files) || sc.files.length !== 0) return failC(`files[] not reset on regen: ${JSON.stringify(sc.files)}`);
|
|
431
|
+
if (!sc._intent_hash) return failC('regen did not install _intent_hash');
|
|
432
|
+
if (!sc.trace || sc.trace.query !== q2) return failC('regen did not record trace.query');
|
|
433
|
+
runHook(hook('final-review'), { conversation_id: cidC, status: 'completed' });
|
|
434
|
+
rmSync(transcriptC, { force: true });
|
|
435
|
+
|
|
436
|
+
// --- Case D: MODEL-written scope (no _intent_hash) + the SAME prompt ------
|
|
437
|
+
// The model wrote the contract for THIS request this session. The hook must
|
|
438
|
+
// HEAL in place (backfill _intent_hash + trace) WITHOUT resetting files[] or
|
|
439
|
+
// acceptance, so the NEXT prompt change is detectable by hash.
|
|
440
|
+
const cidD = 'npxv6';
|
|
441
|
+
const transcriptD = join(HOME, '.cursor', '.hooks-pending', 'verify-transcript-npxv6.jsonl');
|
|
442
|
+
const failD = (detail) => { try { rmSync(transcriptD, { force: true }); } catch {} cleanup(); return { ok: false, detail }; };
|
|
443
|
+
try { rmSync(scopePath, { force: true }); } catch {}
|
|
444
|
+
mkT(transcriptD, q1);
|
|
445
|
+
// prime last-query-<cidD>.hash with q1 (a hook-written scaffold), clear the
|
|
446
|
+
// latch, then overwrite with a model-style scope for the SAME prompt.
|
|
447
|
+
runHook(hook('intent-anchor'), { conversation_id: cidD, cwd: repoDir, transcript_path: transcriptD });
|
|
448
|
+
runHook(hook('final-review'), { conversation_id: cidD, status: 'completed' });
|
|
449
|
+
writeFileSync(scopePath, JSON.stringify({ intent: q1, files: ['a.ts', 'b.ts'], acceptance: 'keep me', allow_growth: false }, null, 2), 'utf8');
|
|
450
|
+
runHook(hook('intent-anchor'), { conversation_id: cidD, cwd: repoDir, transcript_path: transcriptD });
|
|
451
|
+
drainedOf(cidD);
|
|
452
|
+
let sd;
|
|
453
|
+
try { sd = JSON.parse(readFileSync(scopePath, 'utf8')); } catch { return failD('healed scope corrupted'); }
|
|
454
|
+
if (!sd._intent_hash) return failD('heal did not backfill _intent_hash');
|
|
455
|
+
if (!sd.trace) return failD('heal did not add trace');
|
|
456
|
+
if (!Array.isArray(sd.files) || sd.files.join(',') !== 'a.ts,b.ts') return failD(`heal reset files[] (should preserve): ${JSON.stringify(sd.files)}`);
|
|
457
|
+
if (sd.acceptance !== 'keep me') return failD(`heal clobbered acceptance: ${sd.acceptance}`);
|
|
458
|
+
runHook(hook('final-review'), { conversation_id: cidD, status: 'completed' });
|
|
459
|
+
rmSync(transcriptD, { force: true });
|
|
460
|
+
|
|
411
461
|
cleanup();
|
|
412
462
|
return true;
|
|
413
463
|
});
|
package/linux/USER-RULES.md
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
The user is a senior engineer who reviews every diff before shipping.
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
Change only what the task requires. Preserve existing style and behavior unless
|
|
5
|
+
the task itself is a behavior change. Refactors, renames, cleanup only when
|
|
6
|
+
asked. Leave generated files alone unless explicitly required.
|
|
7
|
+
|
|
8
|
+
## Intent contract (.scope.json)
|
|
9
|
+
The harness auto-creates `.scope.json` in the repo root on your first tool of
|
|
10
|
+
each turn, and re-injects it into your context every turn. Treat it as your
|
|
11
|
+
operating contract, not optional:
|
|
12
|
+
- On a fresh scaffold, FILL the `intent` and `acceptance` TODOs from the user's
|
|
13
|
+
request before editing source. `files[]` is auto-tracked - do not maintain it.
|
|
14
|
+
- When the user's request changes, the scaffold regenerates with a new intent -
|
|
15
|
+
refill it for the new ask.
|
|
16
|
+
- If a hook surfaces the contract, defer to it: it outranks momentum. Edit
|
|
17
|
+
inside the declared scope; if you must grow it, justify it, don't sneak past.
|
|
18
|
+
|
|
19
|
+
## Loop
|
|
20
|
+
1. Read what you need to understand the task.
|
|
21
|
+
2. Make the minimal correct edit.
|
|
22
|
+
3. Review the diff. Fix real issues: broken logic, type errors, unsafe
|
|
23
|
+
behavior, data-loss risk, unrequested API/contract changes, regressions.
|
|
24
|
+
Style and naming taste are not bugs.
|
|
25
|
+
4. Verify proportionally to risk - relevant tests/typechecks for behavior, type,
|
|
26
|
+
API, DB, build, or config changes; nothing for trivial text edits.
|
|
27
|
+
5. Report what changed and what was verified. Stop.
|
|
28
|
+
|
|
29
|
+
## Shell
|
|
30
|
+
Run the smallest command that answers the question. Never print secrets,
|
|
31
|
+
tokens, private keys, or sensitive env vars. Never `curl | sh`, force-push, or
|
|
32
|
+
publish without explicit instruction.
|
|
33
|
+
|
|
34
|
+
## Uncertainty
|
|
35
|
+
If ambiguity affects correctness or safety, ask one sharp question. If
|
|
36
|
+
low-risk, state the assumption and proceed. If a tool returns nothing, say what
|
|
37
|
+
you didn't find - don't fabricate. After two failed attempts at the same
|
|
38
|
+
problem, stop and report observations.
|
|
39
|
+
|
|
40
|
+
## Commits
|
|
41
|
+
Conventional commits: `type(scope): description`. One logical change per
|
|
42
|
+
commit, small and reviewable. Body only when the why isn't obvious from the
|
|
43
|
+
diff. Verify before pushing when applicable. Never push without explicit
|
|
44
|
+
instruction.
|
|
45
|
+
|
|
@@ -110,7 +110,7 @@ body="$(expand_agent_paths "$body")"
|
|
|
110
110
|
# wrong diff. Reset its prior to the Anchor Set, not to its previous attempt.
|
|
111
111
|
reentry_line="
|
|
112
112
|
|
|
113
|
-
RE-ENTRY RULE (Regla R1): if a gate or axis failed, forget the approach that produced it. Re-read your ORIGINAL REQUEST above and your Anchor Set (.scope.json,
|
|
113
|
+
RE-ENTRY RULE (Regla R1): if a gate or axis failed, forget the approach that produced it. Re-read your ORIGINAL REQUEST above and your Anchor Set (.scope.json, maintained by the intent-anchor hook). Fix ONLY what is failing. Do not refactor in this pass - that is History Propagation, the exact failure mode the Anchor Set exists to prevent.
|
|
114
114
|
"
|
|
115
115
|
|
|
116
116
|
file_list=""
|
|
@@ -15,16 +15,19 @@
|
|
|
15
15
|
# at the START of each turn's work, before edits pile up and dilute the
|
|
16
16
|
# original intent. Works UNCONDITIONALLY - no transcript needed.
|
|
17
17
|
#
|
|
18
|
-
# 2. AUTO-CREATE / REGENERATE .scope.json
|
|
19
|
-
# differs from the contract on disk (no
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
18
|
+
# 2. AUTO-CREATE / REGENERATE .scope.json (only when the request is READABLE):
|
|
19
|
+
# when the current <user_query> differs from the contract on disk (no
|
|
20
|
+
# contract yet, _intent_hash mismatch, OR a hollow <TODO> placeholder), the
|
|
21
|
+
# hook WRITES a scaffold to the REPO ROOT: intent locked from the prompt,
|
|
22
|
+
# files as an EMPTY array (scope-gate-audit.sh fills it mechanically as the
|
|
23
|
+
# agent edits - the agent never maintains files[] by hand), acceptance as a
|
|
24
|
+
# TODO the agent sets. We NEVER persist a hollow `intent: <TODO>` file: that
|
|
25
|
+
# caused "el .scope.json se escribe solo sin nada" - when transcript_path is
|
|
26
|
+
# absent on postToolUse the hook can't read the request, so a placeholder
|
|
27
|
+
# with an empty _intent_hash got written, looked owned, and never gained the
|
|
28
|
+
# real intent. Unreadable request -> write nothing, emit the pre-compile
|
|
29
|
+
# demand so the AGENT authors the contract. Never writes to $HOME (bails if
|
|
30
|
+
# no real root resolves -> no ghost files).
|
|
28
31
|
# 3. RE-INJECT on same-prompt turns: when the query is unchanged (contract
|
|
29
32
|
# already current), the hook re-injects the existing contract into the
|
|
30
33
|
# feedback bus so it stays in the model's attentional focus each turn.
|
|
@@ -111,7 +114,10 @@ scope_exists=0
|
|
|
111
114
|
scope_intent=""
|
|
112
115
|
scope_acceptance=""
|
|
113
116
|
scope_files=""
|
|
114
|
-
scope_stale=0
|
|
117
|
+
scope_stale=0 # 1 when the on-disk contract belongs to a DIFFERENT prompt -> regenerate (resets files[])
|
|
118
|
+
needs_heal=0 # 1 when a model-written contract matches THIS prompt but lacks _intent_hash -> backfill in place
|
|
119
|
+
scope_hollow=0 # 1 when the on-disk contract has no real intent (empty or a <TODO> placeholder) -> unusable
|
|
120
|
+
on_disk_hash=""
|
|
115
121
|
scope_path="$root/.scope.json"
|
|
116
122
|
if [ -f "$scope_path" ]; then
|
|
117
123
|
# Prefer jq; fall back to python3 (mirrors hook-common.sh degrade policy).
|
|
@@ -137,50 +143,72 @@ except Exception:
|
|
|
137
143
|
EOF
|
|
138
144
|
[ $? -eq 0 ] && scope_exists=1 || scope_exists=0
|
|
139
145
|
fi
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
# Staleness, hash-agnostic so it survives MODEL-written contracts:
|
|
147
|
+
# - hook-written (has _intent_hash): stale when that hash != current query hash.
|
|
148
|
+
# - model-written (no _intent_hash - the legacy pre-compile.md schema): fall back to
|
|
149
|
+
# $prompt_changed (current query hash != the per-conversation last-query hash). Prompt
|
|
150
|
+
# changed (or a new session) => regenerate and RESET files[] (the "arrastre entre
|
|
151
|
+
# features" fix). Same prompt this session => heal in place (backfill bookkeeping, keep
|
|
152
|
+
# files[]/acceptance) so the NEXT prompt is detected by hash.
|
|
153
|
+
# Hollow = no real intent on disk: empty, or still the hook's <TODO> placeholder.
|
|
154
|
+
# A hollow contract is worse than none (it looks owned, so neither hook nor agent
|
|
155
|
+
# fills it). Treat it as unusable: regenerate when the request is readable, else
|
|
156
|
+
# hand the agent the pre-compile demand to author a real one.
|
|
157
|
+
case "$scope_intent" in
|
|
158
|
+
""|"<TODO"*) scope_hollow=1 ;;
|
|
159
|
+
esac
|
|
160
|
+
if [ "$scope_exists" = "1" ] && [ "$has_query" = "1" ]; then
|
|
161
|
+
if [ "$scope_hollow" = "1" ]; then
|
|
162
|
+
scope_stale=1
|
|
163
|
+
elif [ -n "$on_disk_hash" ]; then
|
|
164
|
+
[ "$on_disk_hash" != "$current_hash" ] && scope_stale=1
|
|
165
|
+
elif [ "$prompt_changed" = "1" ]; then
|
|
166
|
+
scope_stale=1
|
|
167
|
+
else
|
|
168
|
+
needs_heal=1
|
|
169
|
+
fi
|
|
143
170
|
fi
|
|
144
171
|
fi
|
|
145
172
|
|
|
146
|
-
# --- auto-create / regenerate .scope.json
|
|
147
|
-
# CREATION
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
173
|
+
# --- auto-create / regenerate / heal .scope.json ----------------------------
|
|
174
|
+
# CREATION and REGENERATION both REQUIRE the query. We only ever write a
|
|
175
|
+
# contract whose intent we actually know - never a hollow <TODO> scaffold.
|
|
176
|
+
# Persisting a placeholder (the 0.5.3 "unconditional creation") caused "el
|
|
177
|
+
# .scope.json se escribe solo sin nada": no transcript_path on postToolUse ->
|
|
178
|
+
# the hook can't read the request -> intent=<TODO> with empty _intent_hash, a
|
|
179
|
+
# file that looks owned and never gains the real intent. Unreadable request ->
|
|
180
|
+
# write nothing, emit the pre-compile demand so the AGENT authors the contract.
|
|
181
|
+
# A fresh write resets files[] -> ".scope fresco por prompt, sin arrastre."
|
|
182
|
+
# (Hollow on-disk contracts are folded into $scope_stale above, so a readable
|
|
183
|
+
# request also overwrites them here.)
|
|
155
184
|
regenerated=0
|
|
156
185
|
should_create=0
|
|
157
186
|
should_regen=0
|
|
158
|
-
[ "$scope_exists" != "1" ] && should_create=1
|
|
187
|
+
[ "$scope_exists" != "1" ] && [ "$has_query" = "1" ] && should_create=1
|
|
159
188
|
if [ "$has_query" = "1" ] && [ "$scope_exists" = "1" ] && [ "$scope_stale" = "1" ]; then
|
|
160
189
|
should_regen=1
|
|
161
190
|
fi
|
|
191
|
+
now_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)"
|
|
162
192
|
|
|
163
193
|
if [ "$should_create" = "1" ] || [ "$should_regen" = "1" ]; then
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
fi
|
|
170
|
-
# jq preferred; python3 fallback. Write intent, empty files[], TODO
|
|
171
|
-
# acceptance, and record _intent_hash so staleness is self-contained.
|
|
194
|
+
# Both paths require $has_query, so intent is always locked from the request.
|
|
195
|
+
intent_val="$current_query"
|
|
196
|
+
trace_query="$current_query"
|
|
197
|
+
# jq preferred; python3 fallback. Write intent, empty files[], TODO acceptance,
|
|
198
|
+
# trace provenance, and _intent_hash so staleness is self-contained.
|
|
172
199
|
if have_jq; then
|
|
173
|
-
jq -n --arg intent "$intent_val" --arg hash "$current_hash" \
|
|
174
|
-
'{intent:$intent, files:[], acceptance:"<TODO: the one deterministic check that decides done>", allow_growth:false, _intent_hash:$hash, _generated_by:"intent-anchor hook"}' \
|
|
200
|
+
jq -n --arg intent "$intent_val" --arg hash "$current_hash" --arg tq "$trace_query" --arg ts "$now_ts" \
|
|
201
|
+
'{intent:$intent, files:[], acceptance:"<TODO: the one deterministic check that decides done>", allow_growth:false, trace:{query:$tq, ts:$ts}, _intent_hash:$hash, _generated_by:"intent-anchor hook"}' \
|
|
175
202
|
> "$scope_path" 2>/dev/null && regenerated=1
|
|
176
203
|
elif have_py; then
|
|
177
|
-
if I_FILE="$scope_path" I_INTENT="$intent_val" I_HASH="$current_hash" python3 -c '
|
|
204
|
+
if I_FILE="$scope_path" I_INTENT="$intent_val" I_HASH="$current_hash" I_TQ="$trace_query" I_TS="$now_ts" python3 -c '
|
|
178
205
|
import json, os
|
|
179
206
|
obj = {
|
|
180
207
|
"intent": os.environ["I_INTENT"],
|
|
181
208
|
"files": [],
|
|
182
209
|
"acceptance": "<TODO: the one deterministic check that decides done>",
|
|
183
210
|
"allow_growth": False,
|
|
211
|
+
"trace": {"query": os.environ["I_TQ"], "ts": os.environ["I_TS"]},
|
|
184
212
|
"_intent_hash": os.environ["I_HASH"],
|
|
185
213
|
"_generated_by": "intent-anchor hook",
|
|
186
214
|
}
|
|
@@ -199,6 +227,35 @@ with open(os.environ["I_FILE"], "w", encoding="utf-8") as f:
|
|
|
199
227
|
fi
|
|
200
228
|
fi
|
|
201
229
|
|
|
230
|
+
# HEAL a model-written contract that matches the current prompt but lacks the
|
|
231
|
+
# hook's bookkeeping: backfill _intent_hash + trace + _generated_by IN PLACE,
|
|
232
|
+
# preserving the model's files[] and acceptance. Without this a contract written
|
|
233
|
+
# per the legacy pre-compile.md schema (no _intent_hash) can never go stale, so
|
|
234
|
+
# the next prompt never regenerates - the carryover bug. Healing installs the
|
|
235
|
+
# hash so the next prompt change is detected by hash like any hook contract.
|
|
236
|
+
if [ "$needs_heal" = "1" ] && [ "$regenerated" != "1" ]; then
|
|
237
|
+
if have_jq; then
|
|
238
|
+
healed="$(jq --arg hash "$current_hash" --arg tq "$current_query" --arg ts "$now_ts" \
|
|
239
|
+
'._intent_hash = $hash | .trace //= {query:$tq, ts:$ts} | ._generated_by //= "intent-anchor hook (healed)"' \
|
|
240
|
+
"$scope_path" 2>/dev/null)"
|
|
241
|
+
[ -n "$healed" ] && printf '%s\n' "$healed" > "$scope_path"
|
|
242
|
+
elif have_py; then
|
|
243
|
+
I_FILE="$scope_path" I_HASH="$current_hash" I_TQ="$current_query" I_TS="$now_ts" python3 -c '
|
|
244
|
+
import json, os, sys
|
|
245
|
+
path = os.environ["I_FILE"]
|
|
246
|
+
try:
|
|
247
|
+
d = json.load(open(path, encoding="utf-8"))
|
|
248
|
+
except Exception:
|
|
249
|
+
sys.exit(0)
|
|
250
|
+
d["_intent_hash"] = os.environ["I_HASH"]
|
|
251
|
+
d.setdefault("trace", {"query": os.environ["I_TQ"], "ts": os.environ["I_TS"]})
|
|
252
|
+
d.setdefault("_generated_by", "intent-anchor hook (healed)")
|
|
253
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
254
|
+
json.dump(d, f, ensure_ascii=False, indent=2)
|
|
255
|
+
' 2>/dev/null
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
|
|
202
259
|
# files[] is auto-tracked and starts empty; show something readable until the
|
|
203
260
|
# scope hook has recorded the first edit.
|
|
204
261
|
[ -n "$scope_files" ] || scope_files="(none yet - auto-tracked as you edit)"
|
|
@@ -224,17 +281,25 @@ is locked from what you just asked. files[] is AUTO-TRACKED - the scope hook
|
|
|
224
281
|
records every file you edit, so do not maintain it by hand. Set acceptance to
|
|
225
282
|
the one deterministic check that decides done, THEN proceed. This contract will
|
|
226
283
|
be re-injected every turn until your request changes again."
|
|
227
|
-
elif [ "$scope_exists" != "1" ]; then
|
|
228
|
-
|
|
229
|
-
|
|
284
|
+
elif [ "$scope_exists" != "1" ] || [ "$scope_hollow" = "1" ]; then
|
|
285
|
+
if [ "$scope_hollow" = "1" ]; then
|
|
286
|
+
state="the .scope.json in $root is only a <TODO> placeholder (the hook could not read your request to fill it)"
|
|
287
|
+
else
|
|
288
|
+
state="no .scope.json found in $root, and the current request was unavailable to scaffold from"
|
|
289
|
+
fi
|
|
290
|
+
msg="INTENT ANCHOR (pre-compile) - $state.
|
|
230
291
|
|
|
231
292
|
Current request:
|
|
232
293
|
$query_line
|
|
233
294
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
acceptance: the one deterministic check that decides done
|
|
295
|
+
YOU write the real contract to $scope_path now, from THIS conversation, BEFORE
|
|
296
|
+
editing source. Do not leave the <TODO> placeholder:
|
|
297
|
+
intent: one operational sentence - the ACTUAL request (not \"<TODO>\")
|
|
298
|
+
acceptance: the one deterministic check that decides done
|
|
299
|
+
files: [] (leave empty - the scope hook records every file you edit)
|
|
300
|
+
|
|
301
|
+
This is the one case where you own the file: once intent is real, the hook
|
|
302
|
+
takes over (re-injection + per-prompt regeneration)."
|
|
238
303
|
else
|
|
239
304
|
# Contract exists and matches the current prompt -> re-inject it.
|
|
240
305
|
if [ "$has_query" = "1" ]; then
|
package/linux/pre-compile.md
CHANGED
|
@@ -29,29 +29,52 @@ Answer these four, terse, in your first response. One phrase each, not prose:
|
|
|
29
29
|
specific failing test going green is. If you cannot name one, you do not
|
|
30
30
|
yet understand the task — ask.
|
|
31
31
|
|
|
32
|
-
## Materialize it: .scope.json
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
## Materialize it: .scope.json (the hook owns this file)
|
|
33
|
+
|
|
34
|
+
The `intent-anchor` hook creates and maintains `.scope.json` in the repo root for
|
|
35
|
+
you, automatically, on the first tool of every turn:
|
|
36
|
+
- `intent` is locked from your current request and REFRESHED when the request
|
|
37
|
+
changes — a new prompt regenerates the contract and resets `files[]`, so it
|
|
38
|
+
never carries over between features;
|
|
39
|
+
- `files[]` is auto-recorded — the scope hook appends every file you edit, so
|
|
40
|
+
you never maintain it by hand;
|
|
41
|
+
- `trace`, `_intent_hash`, `_generated_by` are hook bookkeeping (provenance and
|
|
42
|
+
change detection). Leave them alone.
|
|
43
|
+
|
|
44
|
+
Your one job on the contract: set **`acceptance`** — the single deterministic check
|
|
45
|
+
that decides done — which the hook cannot derive. Edit ONLY that field (a targeted
|
|
46
|
+
string replace). Do **NOT** rewrite the whole file: overwriting it drops the hook's
|
|
47
|
+
`_intent_hash`/`trace`, which disables per-prompt regeneration and brings back the
|
|
48
|
+
cross-feature carryover. The scope-gate hook audits every edit against `files[]`,
|
|
49
|
+
and final-review axis 0 traces every diff hunk back to `intent`.
|
|
38
50
|
|
|
39
51
|
```json
|
|
40
52
|
{
|
|
41
|
-
"intent":
|
|
42
|
-
"files":
|
|
43
|
-
"acceptance":
|
|
44
|
-
"allow_growth": false
|
|
53
|
+
"intent": "<locked from your request by the hook>",
|
|
54
|
+
"files": ["<auto-recorded by the hook as you edit>"],
|
|
55
|
+
"acceptance": "<YOU set this: the deterministic check that decides done>",
|
|
56
|
+
"allow_growth": false,
|
|
57
|
+
"trace": { "query": "<originating request>", "ts": "<when>" },
|
|
58
|
+
"_intent_hash": "<hook bookkeeping>",
|
|
59
|
+
"_generated_by":"intent-anchor hook"
|
|
45
60
|
}
|
|
46
61
|
```
|
|
47
62
|
|
|
48
|
-
`allow_growth: false` is the default
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
the
|
|
63
|
+
`allow_growth: false` is the default. If the contract has not appeared yet (the
|
|
64
|
+
hook scaffolds on a tool boundary), just proceed — you do not need to hand-write
|
|
65
|
+
it. The declared-editing ladder's rung 1 ("does this need to exist?") still governs
|
|
66
|
+
trivial one-liners.
|
|
67
|
+
|
|
68
|
+
**Exception — a hollow contract is YOURS to write.** The hook can only lock
|
|
69
|
+
`intent` when the harness surfaces your request to it; in some Cursor builds it
|
|
70
|
+
cannot, and the `intent-anchor` hook will then ask YOU to author the contract (or
|
|
71
|
+
you may open `.scope.json` and find `intent` still a `<TODO>` placeholder). In
|
|
72
|
+
that one case, write the whole file yourself from this conversation — a real
|
|
73
|
+
`intent` (the actual request, not `<TODO>`), `acceptance`, and `files: []` — and
|
|
74
|
+
do it BEFORE editing source. Never leave a `<TODO>` intent on disk: a placeholder
|
|
75
|
+
contract looks owned, so nothing ever fills it, and scope-gate/final-review then
|
|
76
|
+
audit your diff against nothing. Once `intent` is real, hand the file back to the
|
|
77
|
+
hook (re-injection + per-prompt regeneration take over).
|
|
55
78
|
|
|
56
79
|
## Regla R3 — Authority
|
|
57
80
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursordoctrine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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"
|
|
@@ -8,12 +8,16 @@ declared-scope check (Step C), so the two never disagree on what counts as
|
|
|
8
8
|
"in scope". It also surfaces the contract's `intent` and `acceptance` fields
|
|
9
9
|
so the calling hook can quote them back to the agent.
|
|
10
10
|
|
|
11
|
-
.scope.json schema (intent
|
|
11
|
+
.scope.json schema (the intent-anchor hook owns this file; this matcher reads
|
|
12
|
+
only intent/files/acceptance/allow_growth and ignores the hook bookkeeping):
|
|
12
13
|
{
|
|
13
14
|
"intent": "one operational sentence of objective",
|
|
14
15
|
"files": [ "repo-relative globs", ... ],
|
|
15
16
|
"acceptance": "the deterministic check that decides success",
|
|
16
|
-
"allow_growth": false
|
|
17
|
+
"allow_growth": false,
|
|
18
|
+
"trace": { "query": "originating request", "ts": "ISO-8601" },
|
|
19
|
+
"_intent_hash": "hook bookkeeping: sha256 of the request, drives per-prompt regen",
|
|
20
|
+
"_generated_by":"intent-anchor hook"
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
Pattern support:
|
package/windows/USER-RULES.md
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
The user is a senior engineer who reviews every diff before shipping.
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
Change only what the task requires. Preserve existing style and behavior unless
|
|
5
|
+
the task itself is a behavior change. Refactors, renames, cleanup only when
|
|
6
|
+
asked. Leave generated files alone unless explicitly required.
|
|
7
|
+
|
|
8
|
+
## Intent contract (.scope.json)
|
|
9
|
+
The harness auto-creates `.scope.json` in the repo root on your first tool of
|
|
10
|
+
each turn, and re-injects it into your context every turn. Treat it as your
|
|
11
|
+
operating contract, not optional:
|
|
12
|
+
- On a fresh scaffold, FILL the `intent` and `acceptance` TODOs from the user's
|
|
13
|
+
request before editing source. `files[]` is auto-tracked - do not maintain it.
|
|
14
|
+
- When the user's request changes, the scaffold regenerates with a new intent -
|
|
15
|
+
refill it for the new ask.
|
|
16
|
+
- If a hook surfaces the contract, defer to it: it outranks momentum. Edit
|
|
17
|
+
inside the declared scope; if you must grow it, justify it, don't sneak past.
|
|
18
|
+
|
|
19
|
+
## Loop
|
|
20
|
+
1. Read what you need to understand the task.
|
|
21
|
+
2. Make the minimal correct edit.
|
|
22
|
+
3. Review the diff. Fix real issues: broken logic, type errors, unsafe
|
|
23
|
+
behavior, data-loss risk, unrequested API/contract changes, regressions.
|
|
24
|
+
Style and naming taste are not bugs.
|
|
25
|
+
4. Verify proportionally to risk - relevant tests/typechecks for behavior, type,
|
|
26
|
+
API, DB, build, or config changes; nothing for trivial text edits.
|
|
27
|
+
5. Report what changed and what was verified. Stop.
|
|
28
|
+
|
|
29
|
+
## Shell
|
|
30
|
+
Run the smallest command that answers the question. Never print secrets,
|
|
31
|
+
tokens, private keys, or sensitive env vars. Never `curl | sh`, force-push, or
|
|
32
|
+
publish without explicit instruction.
|
|
33
|
+
|
|
34
|
+
## Uncertainty
|
|
35
|
+
If ambiguity affects correctness or safety, ask one sharp question. If
|
|
36
|
+
low-risk, state the assumption and proceed. If a tool returns nothing, say what
|
|
37
|
+
you didn't find - don't fabricate. After two failed attempts at the same
|
|
38
|
+
problem, stop and report observations.
|
|
39
|
+
|
|
40
|
+
## Commits
|
|
41
|
+
Conventional commits: `type(scope): description`. One logical change per
|
|
42
|
+
commit, small and reviewable. Body only when the why isn't obvious from the
|
|
43
|
+
diff. Verify before pushing when applicable. Never push without explicit
|
|
44
|
+
instruction.
|
|
45
|
+
|
|
@@ -119,7 +119,7 @@ $body = Expand-AgentPaths $body
|
|
|
119
119
|
# Regla R1 (re-entry): if this review pass is a re-audit after a failed gate or
|
|
120
120
|
# axis, suppress History Propagation - the model must NOT build on its own prior
|
|
121
121
|
# wrong diff. Reset its prior to the Anchor Set, not to its previous attempt.
|
|
122
|
-
$reentryLine = "`n`nRE-ENTRY RULE (Regla R1): if a gate or axis failed, forget the approach that produced it. Re-read your ORIGINAL REQUEST above and your Anchor Set (.scope.json,
|
|
122
|
+
$reentryLine = "`n`nRE-ENTRY RULE (Regla R1): if a gate or axis failed, forget the approach that produced it. Re-read your ORIGINAL REQUEST above and your Anchor Set (.scope.json, maintained by the intent-anchor hook). Fix ONLY what is failing. Do not refactor in this pass - that is History Propagation, the exact failure mode the Anchor Set exists to prevent.`n"
|
|
123
123
|
|
|
124
124
|
$resolved = @($edited | ForEach-Object { Resolve-AgentPath $_ })
|
|
125
125
|
$fileList = ($resolved | Select-Object -First 30) -join "`n "
|
|
@@ -14,18 +14,22 @@
|
|
|
14
14
|
# at the START of each turn's work, before edits pile up and dilute the
|
|
15
15
|
# original intent. Works UNCONDITIONALLY - no transcript needed.
|
|
16
16
|
#
|
|
17
|
-
# 2. AUTO-CREATE .scope.json (
|
|
18
|
-
# contract exists
|
|
19
|
-
# query
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
17
|
+
# 2. AUTO-CREATE .scope.json (only when the request is READABLE): if no valid
|
|
18
|
+
# contract exists and we can read <user_query>, WRITE one now with intent
|
|
19
|
+
# locked from the query. We NEVER persist a hollow `intent: <TODO>` file:
|
|
20
|
+
# that 0.5.3 "unconditional creation" caused "el .scope.json se escribe
|
|
21
|
+
# solo sin nada" - when Cursor doesn't surface transcript_path on
|
|
22
|
+
# postToolUse the hook can't read the request, so it wrote a placeholder
|
|
23
|
+
# with an empty _intent_hash. The file then looks owned (pre-compile.md
|
|
24
|
+
# tells the agent to leave it alone) and never gets the real intent. When
|
|
25
|
+
# the request is unreadable we write nothing and emit the pre-compile
|
|
26
|
+
# demand so the AGENT authors a real contract from the chat it already has.
|
|
27
|
+
# 3. REGENERATE on prompt CHANGE or HOLLOW contract: when the current
|
|
28
|
+
# <user_query> hash differs from the contract's _intent_hash, OR the
|
|
29
|
+
# on-disk contract has no real intent (empty / <TODO> placeholder),
|
|
30
|
+
# overwrite it with the new intent + empty files + TODO acceptance.
|
|
31
|
+
# Requires $hasQuery (you can only lock intent if you can read the
|
|
32
|
+
# request). Never writes to $HOME (bails if no real root resolves -> no
|
|
29
33
|
# ghost files).
|
|
30
34
|
# 4. RE-INJECT on same-prompt turns: when the query is unchanged (contract
|
|
31
35
|
# already current), the hook re-injects the existing contract into the
|
|
@@ -109,7 +113,10 @@ $scopeExists = $false
|
|
|
109
113
|
$scopeIntent = ''
|
|
110
114
|
$scopeAcceptance = ''
|
|
111
115
|
$scopeFiles = ''
|
|
112
|
-
$scopeStale = $false
|
|
116
|
+
$scopeStale = $false # true when the on-disk contract belongs to a DIFFERENT prompt -> regenerate (resets files[])
|
|
117
|
+
$needsHeal = $false # true when a model-written contract matches THIS prompt but lacks _intent_hash -> backfill in place
|
|
118
|
+
$scopeHasHash = $false
|
|
119
|
+
$scopeHollow = $false # true when the on-disk contract has no real intent (empty or a <TODO> placeholder) -> unusable
|
|
113
120
|
$scopePath = Join-Path $root '.scope.json'
|
|
114
121
|
if (Test-Path -LiteralPath $scopePath) {
|
|
115
122
|
try {
|
|
@@ -119,40 +126,68 @@ if (Test-Path -LiteralPath $scopePath) {
|
|
|
119
126
|
if ($sj.files) { $scopeFiles = (@($sj.files) -join ', ') }
|
|
120
127
|
if ([string]::IsNullOrWhiteSpace($scopeFiles)) { $scopeFiles = '(none yet - auto-tracked as you edit)' }
|
|
121
128
|
$scopeExists = $true
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
$scopeHasHash = ($sj.PSObject.Properties['_intent_hash'] -and -not [string]::IsNullOrWhiteSpace([string]$sj._intent_hash))
|
|
130
|
+
# Hollow = no real intent on disk: empty, or still the hook's <TODO> placeholder.
|
|
131
|
+
# 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 <TODO>).
|
|
133
|
+
# Treat it as unusable: regenerate when we can read the request, else hand the agent
|
|
134
|
+
# the pre-compile demand to write a real one.
|
|
135
|
+
$scopeHollow = ([string]::IsNullOrWhiteSpace($scopeIntent) -or $scopeIntent -match '^\s*<TODO')
|
|
136
|
+
# Staleness, hash-agnostic so it survives MODEL-written contracts:
|
|
137
|
+
# - hook-written (has _intent_hash): stale when that hash != current query hash.
|
|
138
|
+
# - model-written (no _intent_hash - the legacy pre-compile.md schema): we cannot
|
|
139
|
+
# hash-compare, so fall back to $promptChanged (current query hash != the per-
|
|
140
|
+
# conversation last-query hash). Prompt changed (or a new session) => stale ->
|
|
141
|
+
# regenerate and RESET files[]; this is the "arrastre entre features" fix (a model-
|
|
142
|
+
# written scope could never go stale, so it never refreshed and scope-gate kept
|
|
143
|
+
# appending files across unrelated features). Same prompt this session => the model
|
|
144
|
+
# wrote it for THIS request; heal in place (backfill the bookkeeping, keep its
|
|
145
|
+
# files[]/acceptance) so the NEXT prompt is detected by hash like any hook contract.
|
|
146
|
+
if ($hasQuery) {
|
|
147
|
+
if ($scopeHollow) {
|
|
148
|
+
# Hollow + we can read the request -> overwrite with the real intent now.
|
|
149
|
+
$scopeStale = $true
|
|
150
|
+
} elseif ($scopeHasHash) {
|
|
151
|
+
$scopeStale = ([string]$sj._intent_hash -ne $currentHash)
|
|
152
|
+
} elseif ($promptChanged) {
|
|
153
|
+
$scopeStale = $true
|
|
154
|
+
} else {
|
|
155
|
+
$needsHeal = $true
|
|
156
|
+
}
|
|
127
157
|
}
|
|
128
158
|
} catch { $scopeExists = $false } # malformed JSON -> treat as missing
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
# --- auto-create / regenerate .scope.json
|
|
132
|
-
# CREATION
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
# postToolUse
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
161
|
+
# --- auto-create / regenerate / heal .scope.json ----------------------------
|
|
162
|
+
# CREATION and REGENERATION both REQUIRE the query. We only ever write a
|
|
163
|
+
# contract whose intent we actually know - never a hollow <TODO> scaffold.
|
|
164
|
+
# Persisting a placeholder file (the 0.5.3 "unconditional creation") was the
|
|
165
|
+
# bug behind "el .scope.json se escribe solo sin nada": when Cursor doesn't
|
|
166
|
+
# surface transcript_path on postToolUse, the hook can't read the request, so
|
|
167
|
+
# it wrote intent=<TODO> with an empty _intent_hash. That file looks owned, so
|
|
168
|
+
# pre-compile.md tells the agent to leave it alone, and it never gets the real
|
|
169
|
+
# intent. When the request is unreadable we now write NOTHING and instead hand
|
|
170
|
+
# the agent the pre-compile demand to author a real contract from the chat it
|
|
171
|
+
# is already responding to. A fresh write resets files[] -> ".scope fresco por
|
|
172
|
+
# prompt, sin arrastre entre features." (Hollow on-disk contracts are folded
|
|
173
|
+
# into $scopeStale above, so a readable request also overwrites them here.)
|
|
141
174
|
$regenerated = $false
|
|
142
|
-
$shouldCreate = -not $scopeExists
|
|
175
|
+
$shouldCreate = (-not $scopeExists) -and $hasQuery
|
|
143
176
|
$shouldRegen = $hasQuery -and $scopeExists -and $scopeStale
|
|
144
177
|
if ($shouldCreate -or $shouldRegen) {
|
|
145
178
|
try {
|
|
146
|
-
$intentVal
|
|
179
|
+
$intentVal = $currentQuery
|
|
180
|
+
$traceQuery = $currentQuery
|
|
147
181
|
$scaffold = [ordered]@{
|
|
148
182
|
intent = $intentVal
|
|
149
183
|
files = @()
|
|
150
184
|
acceptance = '<TODO: the one deterministic check that decides done>'
|
|
151
185
|
allow_growth = $false
|
|
186
|
+
trace = [ordered]@{ query = $traceQuery; ts = (Get-Date).ToString('o') }
|
|
152
187
|
_intent_hash = $currentHash
|
|
153
188
|
_generated_by = 'intent-anchor hook'
|
|
154
189
|
}
|
|
155
|
-
$json = $scaffold | ConvertTo-Json -Depth
|
|
190
|
+
$json = $scaffold | ConvertTo-Json -Depth 8
|
|
156
191
|
[System.IO.File]::WriteAllText($scopePath, $json, [System.Text.UTF8Encoding]::new($false))
|
|
157
192
|
$scopeIntent = $intentVal
|
|
158
193
|
$scopeAcceptance = '<TODO: the one deterministic check that decides done>'
|
|
@@ -163,6 +198,26 @@ if ($shouldCreate -or $shouldRegen) {
|
|
|
163
198
|
} catch { } # write failed (perms / locked) -> fall through to demand msg
|
|
164
199
|
}
|
|
165
200
|
|
|
201
|
+
# HEAL a model-written contract that matches the current prompt but lacks the
|
|
202
|
+
# hook's bookkeeping: backfill _intent_hash + trace + _generated_by IN PLACE,
|
|
203
|
+
# preserving the model's files[] and acceptance. Without this, a contract written
|
|
204
|
+
# per pre-compile.md (no _intent_hash) can never go stale, so the next prompt
|
|
205
|
+
# never regenerates - the carryover bug. Healing installs the hash so the next
|
|
206
|
+
# prompt change is detected by hash like any hook-written contract.
|
|
207
|
+
if ($needsHeal -and -not $regenerated) {
|
|
208
|
+
try {
|
|
209
|
+
$ordered = [ordered]@{}
|
|
210
|
+
foreach ($p in $sj.PSObject.Properties) { $ordered[$p.Name] = $p.Value }
|
|
211
|
+
if (-not $ordered.Contains('trace')) {
|
|
212
|
+
$ordered['trace'] = [ordered]@{ query = $currentQuery; ts = (Get-Date).ToString('o') }
|
|
213
|
+
}
|
|
214
|
+
$ordered['_intent_hash'] = $currentHash
|
|
215
|
+
if (-not $ordered.Contains('_generated_by')) { $ordered['_generated_by'] = 'intent-anchor hook (healed)' }
|
|
216
|
+
$json = $ordered | ConvertTo-Json -Depth 8
|
|
217
|
+
[System.IO.File]::WriteAllText($scopePath, $json, [System.Text.UTF8Encoding]::new($false))
|
|
218
|
+
} catch { }
|
|
219
|
+
}
|
|
220
|
+
|
|
166
221
|
# --- compose the anchor message ---------------------------------------------
|
|
167
222
|
# Three states: regenerated this turn (new prompt), no contract (and no query
|
|
168
223
|
# to scaffold from), or re-injecting an existing current contract.
|
|
@@ -182,18 +237,22 @@ records every file you edit, so do not maintain it by hand. Set acceptance to
|
|
|
182
237
|
the one deterministic check that decides done, THEN proceed. This contract will
|
|
183
238
|
be re-injected every turn until your request changes again.
|
|
184
239
|
"@
|
|
185
|
-
} elseif (-not $scopeExists) {
|
|
240
|
+
} elseif (-not $scopeExists -or $scopeHollow) {
|
|
241
|
+
$state = if ($scopeHollow) { "the .scope.json in $root is only a <TODO> placeholder (the hook could not read your request to fill it)" } else { "no .scope.json found in $root, and the current request was unavailable to scaffold from" }
|
|
186
242
|
$msg = @"
|
|
187
|
-
INTENT ANCHOR (pre-compile) -
|
|
188
|
-
request was unavailable to scaffold from.
|
|
243
|
+
INTENT ANCHOR (pre-compile) - $state.
|
|
189
244
|
|
|
190
245
|
Current request:
|
|
191
246
|
$queryLine
|
|
192
247
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
248
|
+
YOU write the real contract to $scopePath now, from THIS conversation, BEFORE
|
|
249
|
+
editing source. Do not leave the <TODO> placeholder:
|
|
250
|
+
intent: one operational sentence - the ACTUAL request (not "<TODO>")
|
|
196
251
|
acceptance: the one deterministic check that decides done
|
|
252
|
+
files: [] (leave empty - the scope hook records every file you edit)
|
|
253
|
+
|
|
254
|
+
This is the one case where you own the file: once intent is real, the hook
|
|
255
|
+
takes over (re-injection + per-prompt regeneration).
|
|
197
256
|
"@
|
|
198
257
|
} else {
|
|
199
258
|
# Contract exists and matches the current prompt -> re-inject it.
|
package/windows/pre-compile.md
CHANGED
|
@@ -29,29 +29,52 @@ Answer these four, terse, in your first response. One phrase each, not prose:
|
|
|
29
29
|
specific failing test going green is. If you cannot name one, you do not
|
|
30
30
|
yet understand the task — ask.
|
|
31
31
|
|
|
32
|
-
## Materialize it: .scope.json
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
## Materialize it: .scope.json (the hook owns this file)
|
|
33
|
+
|
|
34
|
+
The `intent-anchor` hook creates and maintains `.scope.json` in the repo root for
|
|
35
|
+
you, automatically, on the first tool of every turn:
|
|
36
|
+
- `intent` is locked from your current request and REFRESHED when the request
|
|
37
|
+
changes — a new prompt regenerates the contract and resets `files[]`, so it
|
|
38
|
+
never carries over between features;
|
|
39
|
+
- `files[]` is auto-recorded — the scope hook appends every file you edit, so
|
|
40
|
+
you never maintain it by hand;
|
|
41
|
+
- `trace`, `_intent_hash`, `_generated_by` are hook bookkeeping (provenance and
|
|
42
|
+
change detection). Leave them alone.
|
|
43
|
+
|
|
44
|
+
Your one job on the contract: set **`acceptance`** — the single deterministic check
|
|
45
|
+
that decides done — which the hook cannot derive. Edit ONLY that field (a targeted
|
|
46
|
+
string replace). Do **NOT** rewrite the whole file: overwriting it drops the hook's
|
|
47
|
+
`_intent_hash`/`trace`, which disables per-prompt regeneration and brings back the
|
|
48
|
+
cross-feature carryover. The scope-gate hook audits every edit against `files[]`,
|
|
49
|
+
and final-review axis 0 traces every diff hunk back to `intent`.
|
|
38
50
|
|
|
39
51
|
```json
|
|
40
52
|
{
|
|
41
|
-
"intent":
|
|
42
|
-
"files":
|
|
43
|
-
"acceptance":
|
|
44
|
-
"allow_growth": false
|
|
53
|
+
"intent": "<locked from your request by the hook>",
|
|
54
|
+
"files": ["<auto-recorded by the hook as you edit>"],
|
|
55
|
+
"acceptance": "<YOU set this: the deterministic check that decides done>",
|
|
56
|
+
"allow_growth": false,
|
|
57
|
+
"trace": { "query": "<originating request>", "ts": "<when>" },
|
|
58
|
+
"_intent_hash": "<hook bookkeeping>",
|
|
59
|
+
"_generated_by":"intent-anchor hook"
|
|
45
60
|
}
|
|
46
61
|
```
|
|
47
62
|
|
|
48
|
-
`allow_growth: false` is the default
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
the
|
|
63
|
+
`allow_growth: false` is the default. If the contract has not appeared yet (the
|
|
64
|
+
hook scaffolds on a tool boundary), just proceed — you do not need to hand-write
|
|
65
|
+
it. The declared-editing ladder's rung 1 ("does this need to exist?") still governs
|
|
66
|
+
trivial one-liners.
|
|
67
|
+
|
|
68
|
+
**Exception — a hollow contract is YOURS to write.** The hook can only lock
|
|
69
|
+
`intent` when the harness surfaces your request to it; in some Cursor builds it
|
|
70
|
+
cannot, and the `intent-anchor` hook will then ask YOU to author the contract (or
|
|
71
|
+
you may open `.scope.json` and find `intent` still a `<TODO>` placeholder). In
|
|
72
|
+
that one case, write the whole file yourself from this conversation — a real
|
|
73
|
+
`intent` (the actual request, not `<TODO>`), `acceptance`, and `files: []` — and
|
|
74
|
+
do it BEFORE editing source. Never leave a `<TODO>` intent on disk: a placeholder
|
|
75
|
+
contract looks owned, so nothing ever fills it, and scope-gate/final-review then
|
|
76
|
+
audit your diff against nothing. Once `intent` is real, hand the file back to the
|
|
77
|
+
hook (re-injection + per-prompt regeneration take over).
|
|
55
78
|
|
|
56
79
|
## Regla R3 — Authority
|
|
57
80
|
|