feat(analyze-before-plan): read-before bookend — scan memory (+code) before planning
Shared include lib/analyze-before-plan.md (two-pass on '## <PREFIX>-' headings, disposition-not-reading invariant, guarded no-op). Wired into the dev flows: ship-feature STEP 0d (analyzer code+memory, INPUT INJECTION into brainstorm/plan + STEP 3 reconciliation gate), bugfix STEP 2.5 (blockers-first), feat STEP 0.6 (decisions-first, MINI-PLAN names in-force or states none), hotfix opt-in. analyzer gains a RELATED MEMORY output section pointing at the include (DRY). init-project / onboard no-op by construction (guarded scan on absent/empty registries). Mirror of the coupled-capitalize write-after (BDR-034): read-before / write-after bookend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
This commit is contained in:
parent
07e846f0a6
commit
67c6a8165d
@ -64,6 +64,12 @@ RISKS:
|
||||
|
||||
OPEN QUESTIONS:
|
||||
- <ambiguity to clarify>
|
||||
|
||||
RELATED MEMORY:
|
||||
- IN FORCE: <id> — <title> — <how it constrains this work> [status] (detail each)
|
||||
- ALREADY SEEN: <BLK-id> — <title> [status] (known cause/fix — don't re-derive)
|
||||
- NON-BINDING: <c> surfaced, none binding — <bare id refs> (superseded / N-A — counted)
|
||||
- SELECTION: scanned <N> headings — surfaced <K> = in-force <a> + seen <b> + non-binding <c>
|
||||
```
|
||||
|
||||
Surface discovered patterns and conventions in the analysis output
|
||||
@ -73,6 +79,26 @@ happens in the main thread via the gated capitalize flow.
|
||||
|
||||
---
|
||||
|
||||
## RELATED MEMORY (read-before)
|
||||
|
||||
When `.claude/memory/` exists and holds entries, run the memory-relevance scan per
|
||||
`$HOME/.claude/lib/analyze-before-plan.md` (PASS 1: grep `## <PREFIX>-` headings →
|
||||
select on titles → PASS 2: full-read only the selected bodies) and populate the
|
||||
RELATED MEMORY section of OUTPUT.
|
||||
|
||||
The contract is DISPOSITION, not retrieval: every surfaced ID gets a verdict —
|
||||
in-force / already-seen / non-binding. Detail the binding ones (IN FORCE, ALREADY SEEN —
|
||||
they constrain the work); COUNT the non-binding (superseded / N-A) as one line with bare
|
||||
refs — a per-entry paragraph on a non-binding match dilutes the in-force ones that bite.
|
||||
A surfaced ID left undisposed is the gap this closes. You judge bearing; the main-thread
|
||||
plan decides what to DO.
|
||||
|
||||
Read-only here too: reading registries is within Read/Grep; the "Do not modify files" rule
|
||||
still forbids any write — Index backfill or new entries are never your job. Empty or absent
|
||||
registries → omit the section (no-op).
|
||||
|
||||
---
|
||||
|
||||
## EDGE CASES
|
||||
|
||||
| Situation | Action |
|
||||
|
||||
@ -60,6 +60,18 @@ Trace the bug from symptom to root cause:
|
||||
# grep for the same pattern to assess blast radius
|
||||
```
|
||||
|
||||
## STEP 2.5 — MEMORY READ-BEFORE (blockers-first)
|
||||
|
||||
Run the scan per `$HOME/.claude/lib/analyze-before-plan.md`, blockers-weighted: a resolved
|
||||
BLK may already name THIS exact root cause; an in-force BDR may constrain the fix. Emit
|
||||
RELATED MEMORY. Consumption is NATURAL — the agent emitting this IS the one writing STEP 3's
|
||||
diagnosis (reader = planner, no external skill to inject into).
|
||||
|
||||
TEETH: STEP 3's DIAGNOSIS must name any binding prior (`PRIOR: BLK-xxx — known cause/fix`,
|
||||
or `honors BDR-xxx`) OR the RELATED MEMORY line states none bears. Reading blockers then
|
||||
diagnosing without naming a match is the read-then-ignore failure this prevents.
|
||||
`.claude/memory/` absent → guarded no-op, proceed.
|
||||
|
||||
## STEP 3 — HYPOTHESIZE + PLAN
|
||||
|
||||
Present findings before fixing:
|
||||
|
||||
@ -58,6 +58,13 @@ Follow `$HOME/.claude/lib/design-gate.md`:
|
||||
tell the user to run `/profile design` before proceeding.
|
||||
- If no signals → skip (zero overhead).
|
||||
|
||||
## STEP 0.6 — MEMORY READ-BEFORE (decisions-first)
|
||||
|
||||
Run the scan per `$HOME/.claude/lib/analyze-before-plan.md`, decisions-weighted: a BDR may
|
||||
already constrain or forbid the approach; an LRN may name a gotcha to apply. Emit RELATED
|
||||
MEMORY; feed STEP 1 MINI-PLAN. Inline consumption — reader = planner, no injection.
|
||||
`.claude/memory/` absent → guarded no-op (zero overhead on a memory-less repo).
|
||||
|
||||
## STEP 1 — MINI-PLAN
|
||||
|
||||
Quick mental model, not a formal plan document:
|
||||
@ -66,6 +73,9 @@ Quick mental model, not a formal plan document:
|
||||
2. Describe the approach in 2-5 bullet points.
|
||||
3. Note any edge cases to handle.
|
||||
4. If tests exist for the area, note which tests to add/update.
|
||||
5. Disposition (from STEP 0.6): name each in-force BDR/LRN this plan honors
|
||||
(`honors BDR-xxx by …`), or state `no in-force decision constrains this feature`.
|
||||
A plan with neither = read-then-ignore; the disposition must surface as a trace.
|
||||
|
||||
Print the plan as a compact checklist:
|
||||
```
|
||||
|
||||
@ -31,6 +31,14 @@ git log --oneline -3
|
||||
"This looks deeper than a hotfix. Load `$HOME/.claude/agents/bugfixer.md`
|
||||
and run the BUGFIXER agent on this target."
|
||||
|
||||
OPTIONAL — memory check (exempt by default; hotfix = obvious fix, mirror of its capitalize
|
||||
skip). For a RECURRING or urgent bug only, a quick blockers-only glance may save time:
|
||||
|
||||
[ -d .claude/memory ] && grep -nE '^## BLK-' .claude/memory/blockers.md # "déjà vu ?"
|
||||
|
||||
If a prior BLK names this bug, jump to its solution. Not mandatory; no RELATED MEMORY
|
||||
disposition required at hotfix weight.
|
||||
|
||||
## STEP 1.5 — DESIGN GATE
|
||||
|
||||
Follow `$HOME/.claude/lib/design-gate.md`:
|
||||
|
||||
123
lib/analyze-before-plan.md
Normal file
123
lib/analyze-before-plan.md
Normal file
@ -0,0 +1,123 @@
|
||||
# ANALYZE-BEFORE-PLAN — couple the memory read to the dev flow
|
||||
|
||||
Inline snippet. Include at the START of a dev flow, BEFORE the plan forms. This is the
|
||||
HEAD of analyze; `capitalize-commit.md` is the TAIL. Read-before / write-after — the two
|
||||
ends of one bookend: a flow consults the decisions / blockers / learnings it will later add to.
|
||||
|
||||
It does NOT decide the plan. It hands the planner a DISPOSED list of prior entries that
|
||||
bear on the work, so the plan cannot form blind to a decision already in force or a
|
||||
blocker already solved.
|
||||
|
||||
## WHEN TO RUN
|
||||
|
||||
- Inline flow (feat / bugfix): main thread, AFTER the related code is read, BEFORE the
|
||||
mini-plan / diagnosis. PASS 2 is bounded-tiny (selection narrows it); no subagent —
|
||||
preserves stay-light.
|
||||
- Orchestrator with a code-analysis step (ship-feature / init-project): run it INSIDE the
|
||||
analyzer subagent. Its fresh context reads code + memory with zero redundancy against
|
||||
the main thread; only its compact digest returns. The analyzer's RELATED MEMORY output
|
||||
section IS this snippet's OUTPUT.
|
||||
- hotfix: not wired by default (mirror of its capitalize skip). Available opt-in for a
|
||||
blockers-only quick check ("urgent bug déjà vu ?").
|
||||
|
||||
## DO
|
||||
|
||||
1. PRECONDITION — NO-OP unless `.claude/memory/` exists AND holds at least one registry
|
||||
file. TESTED reality: a bare `grep … .claude/memory/*.md` on an ABSENT dir (or a dir
|
||||
with no `.md`) does NOT no-op — the unmatched glob is passed literally and grep ERRORS
|
||||
(`No such file or directory`, exit 2). So the GUARD makes "absent → no-op", never the
|
||||
grep. This is init-project's STEP 2 reality: the registries are created at STEP 5, so at
|
||||
analyze time they are ABSENT — the guard must fire on absent, not merely on empty.
|
||||
|
||||
2. PASS 1 — list every entry, drift-proof, GUARDED:
|
||||
|
||||
[ -d .claude/memory ] && ls .claude/memory/*.md >/dev/null 2>&1 \
|
||||
&& grep -nE '^## (BDR|LRN|BLK|EVAL)-[0-9]+' .claude/memory/*.md
|
||||
|
||||
One `ID — title` line per entry, for 100% of entries by construction (an entry IS its
|
||||
heading). A present-but-template-empty registry (header only) → grep exits 1 (clean
|
||||
no-match) → no-op naturally; only the ABSENT / no-file case needed the guard. The REGEX
|
||||
is the filter, not the file list: the glob also reads journal.md (date-keyed
|
||||
`## YYYY-MM-DD`) and any non-entry file, which contribute zero matches — no need to
|
||||
exclude them. Do NOT read the `## Index` table: it drifts (entries land in the body, the
|
||||
manual Index update lapses — measured 32-40% missing on a mature repo); headings cannot.
|
||||
|
||||
3. SELECT — IDs whose title bears on $REQUEST. Judge on titles (one dense line each), not
|
||||
on bodies. Over-inclusion is SAFE: an entry that proves non-binding costs one bare ref
|
||||
in OUTPUT, not a paragraph (see DISPOSITION) — so err toward including on a dense cluster.
|
||||
|
||||
4. PASS 2 — full-read ONLY the selected bodies (heading to next `##`) to recover
|
||||
status / why / solution / alternatives. Unconditional for the selected set — see THE
|
||||
INVARIANT for why there is no "skip if already in context" branch.
|
||||
|
||||
5. DISPOSE — emit RELATED MEMORY (OUTPUT). Every surfaced ID gets a verdict.
|
||||
|
||||
## OUTPUT — RELATED MEMORY (disposition, not a dump)
|
||||
|
||||
RELATED MEMORY (read-before):
|
||||
IN FORCE — must constrain this work (detail each — they bite):
|
||||
- BDR-026 — <title> — <how it constrains> [accepted]
|
||||
- LRN-050 — <title> — <how it applies>
|
||||
ALREADY SEEN — known cause/fix, don't re-derive (detail each):
|
||||
- BLK-009 — <title> [upstream]
|
||||
NON-BINDING — superseded / N-A, COUNTED not detailed:
|
||||
- <c> surfaced, none binding — BDR-013, LRN-022, … (bare refs, one line)
|
||||
SELECTION: scanned <N> headings / 4 registries —
|
||||
surfaced <K> = in-force <a> + seen <b> + non-binding <c>.
|
||||
|
||||
- Disposition rule: DETAIL what binds (IN FORCE, ALREADY SEEN); COUNT what doesn't
|
||||
(NON-BINDING) as one line + bare refs. The collective verdict still disposes each
|
||||
non-binding ID — its bare ref under "none binding" IS its disposition — but a per-entry
|
||||
paragraph on a non-binding match dilutes the in-force ones that bite. On a dense cluster
|
||||
(K up to ~14) this is what keeps the 3 that matter from drowning under 11 that don't.
|
||||
- Compact even for binding ones: ID + title + one-clause bearing. Bodies were read to
|
||||
JUDGE; only the disposition persists — never paste bodies into the plan.
|
||||
- a + b + c = K: every surfaced ID is accounted for. An unaccounted surfaced ID is the gap
|
||||
this prevents.
|
||||
- Nothing bears → `RELATED MEMORY: none of <N> entries bears on this task` (still proves
|
||||
PASS 1 ran — LRN-048).
|
||||
|
||||
## THE INVARIANT — disposition, not reading
|
||||
|
||||
The guarantee is NOT "the agent read the memory" (an act, unverifiable after the fact).
|
||||
It is "the plan disposed of every relevant prior entry" (a list, verifiable in OUTPUT).
|
||||
LRN-048 one step further: the teeth are "did it STATE a verdict on each surfaced ID?",
|
||||
not "did it look?".
|
||||
|
||||
So there is no "skip PASS 2 if already in context" branch. "Already in context" has no
|
||||
deterministic oracle: self-judgment is the rejected behavioral guard (LRN-046); a session
|
||||
marker records "was read", not "still present", so it false-skips after a compaction (and
|
||||
is the marker cost BDR-033 priced); the agent cannot grep its own window. PASS 2 reads the
|
||||
selected set unconditionally — cheap by construction — and the invariant bites on the
|
||||
disposition, which holds whether a body was freshly read or recalled.
|
||||
|
||||
A decision WRITTEN earlier in the same conversation (ship-feature posts BDR-035, then a
|
||||
bugfix runs) MUST still be surfaced and disposed as in-force: content sitting in context
|
||||
is not the current flow having TREATED it as a constraint. Re-surfacing is the feature.
|
||||
|
||||
## HARD RULE — read-only
|
||||
|
||||
Touches nothing. No write, no Index update, no memory mutation. Symmetric to
|
||||
capitalize-commit's surgical scope (there: stage ONLY memory; here: stage NOTHING). Index
|
||||
backfill, if ever wanted, is `/prune-memory` passe D — never this snippet.
|
||||
|
||||
## ORDERING (orchestrators only)
|
||||
|
||||
`superpowers:brainstorming` / `writing-plans` are external skills — we cannot make them
|
||||
read our registries. So this runs BEFORE them, pre-loading the disposition into the plan
|
||||
they form. Mirror of capitalize-commit running BEFORE finishing-a-development-branch: there
|
||||
the memory commit must precede integration; here the memory read must precede planning.
|
||||
|
||||
## NO-OP / IDEMPOTENT
|
||||
|
||||
Empty or absent registries → silent no-op (greenfield init-project; onboard, which CREATES
|
||||
memory and has none prior). Pure read → safe to run twice; naturally idempotent.
|
||||
|
||||
## WHAT THIS DOES NOT DO
|
||||
|
||||
- Does NOT read the related CODE — the flow does that (feat STEP 0, bugfix STEP 2). Sole
|
||||
exception: ship-feature, where the analyzer subagent this runs in reads code too (Gap A).
|
||||
- Does NOT decide the plan — it primes it with a disposed list of constraints.
|
||||
- Does NOT write or mutate memory — read-only; capitalize (write-after) is the other bookend.
|
||||
- Does NOT depend on the `## Index` table — keys off `## <PREFIX>-` headings (drift-immune).
|
||||
- Does NOT skip PASS 2 on an "already in context" guess — no oracle for it; the read is cheap.
|
||||
@ -60,19 +60,64 @@ During implementation (STEP 4), when making decisions about fast-lib APIs:
|
||||
- Read the relevant `.ctx7-cache/<lib>.md` file before writing code.
|
||||
- This avoids repeated ctx7 calls and keeps docs available without context cost.
|
||||
|
||||
## STEP 0d — ANALYZE BEFORE PLAN (code + memory, read-before)
|
||||
Dispatch the analyzer subagent (fresh context) on the request — it produces the
|
||||
read-before digest the plan must not form without:
|
||||
|
||||
Agent(subagent_type="analyzer", description="ship-feature — read-before", prompt="""
|
||||
Read-only analysis to PRIME a feature plan (NOT debug). REQUEST: <$ARGUMENTS>.
|
||||
1. CODE — locate the zone the feature touches: grep/glob the request's nouns /
|
||||
identifiers across the tree → candidate files (PASS 1), read them (PASS 2).
|
||||
>50 candidates → scoped sweep per your EDGE CASES, list zones, don't read all.
|
||||
Too vague to locate → report ambiguous zones, do NOT block.
|
||||
2. MEMORY — run the scan per $HOME/.claude/lib/analyze-before-plan.md, emit
|
||||
RELATED MEMORY (disposed).
|
||||
Output your standard ANALYSIS + RELATED MEMORY. No solutions.""")
|
||||
|
||||
The returned digest (ANALYSIS + RELATED MEMORY) stays in the orchestrator's context — it
|
||||
is FED to STEP 1 and STEP 2 and reconciled at STEP 3. Degradation: request too vague →
|
||||
analyzer flags ambiguous zones, does not block (STEP 1 refines). `.claude/memory/` empty or
|
||||
absent → analyzer omits RELATED MEMORY (no-op); the step still returns the code ANALYSIS.
|
||||
Additive — distinct from STEP 5 ANALYZE (post-impl regression) and STEP 4b DEBUG.
|
||||
|
||||
## STEP 1 — BRAINSTORM
|
||||
Invoke `superpowers:brainstorming`. Refine request into validated design via Socratic questioning. Don't proceed until design approved.
|
||||
Invoke `superpowers:brainstorming` — but FEED it the STEP 0d digest as binding context,
|
||||
not the raw request alone:
|
||||
"Feature request: <$ARGUMENTS>.
|
||||
In-force constraints (must hold): <only the IN-FORCE + ALREADY-SEEN items from 0d's
|
||||
RELATED MEMORY, detailed>.
|
||||
Existing code that bears: <ANALYSIS CONTEXT / KEY COMPONENTS from 0d>.
|
||||
Brainstorm WITHIN these — don't re-explore a direction an in-force BDR rejects or a
|
||||
BLK already closed."
|
||||
Inject ONLY what constrains: the NON-BINDING count does NOT enter the brainstorm input
|
||||
(the injection inherits the OUTPUT filter — detail what binds, drop what doesn't).
|
||||
Consumption = INPUT INJECTION (we can't modify the external skill; we control its input).
|
||||
Refine request into validated design via Socratic questioning. Don't proceed until design approved.
|
||||
|
||||
## STEP 2 — PLAN
|
||||
Invoke `superpowers:writing-plans`. Break design into tasks (2-5 min each). Each task: exact file paths, full code, verification steps.
|
||||
Invoke `superpowers:writing-plans` with the validated design AND the 0d digest: every task
|
||||
must be consistent with the in-force constraints; where a task implements or affects one,
|
||||
note the ID inline. Break design into tasks (2-5 min each). Each task: exact file paths, full code, verification steps.
|
||||
|
||||
## STEP 3 — VALIDATION GATE ★ MANDATORY STOP
|
||||
```
|
||||
SHIP FEATURE — VALIDATION GATE
|
||||
FEATURE: <n> | TASKS: <count>
|
||||
<numbered task list>
|
||||
|
||||
RELATED MEMORY — disposition CLAIMED by this plan (review each):
|
||||
- BDR-026 [in force] — plan honors it by: <how>
|
||||
- LRN-050 [in force] — plan applies it by: <how>
|
||||
- BLK-009 [already seen] — <how avoided / why N-A>
|
||||
|
||||
Review the claims above — flag any item the plan does NOT actually honor.
|
||||
Approve and execute? (yes / request changes)
|
||||
```
|
||||
This block EXPOSES each in-force item with the plan's CLAIMED disposition, for human
|
||||
review — it does NOT auto-detect conflicts. An agent blind to a conflict won't list it;
|
||||
forcing a per-item claim is what gives the reviewer the surface to catch it. A display,
|
||||
never a guarantee (same discipline as the memory-commit `✅<hash>`: show what's true, never
|
||||
assert a check not performed). No RELATED MEMORY from 0d → omit the block.
|
||||
Changes → back to STEP 2. Approved → continue.
|
||||
|
||||
## STEP 4 — IMPLEMENT
|
||||
@ -175,6 +220,8 @@ The pipeline must handle these without aborting silently:
|
||||
|---|---|
|
||||
| STEP 0b — `CLAUDE.md` missing | STOP with the printed message ("Run `/onboard` first…"). Do not proceed. |
|
||||
| STEP 0c — `ctx7` not installed but fast-libs detected | Skip pre-fetch silently. During STEP 4, log `📚 ctx7 cache miss for <lib>` and continue with vanilla model knowledge. |
|
||||
| STEP 0d — request too vague for analyzer to locate a code zone | analyzer reports ambiguous zones and does NOT block; STEP 1 brainstorm refines scope. The memory disposition is still produced. |
|
||||
| STEP 0d — `.claude/memory/` absent (project not yet onboarded) | analyzer's guarded scan no-ops (the `[ -d ]` guard fires — a bare glob would error); proceed with the code ANALYSIS alone. STEP 7 creates registries later. |
|
||||
| STEP 1 — brainstorming returns "design unclear" twice | Escalate: ask user "Switch to /init-project (greenfield-style design) or refine the feature request?" |
|
||||
| STEP 3 — user replies "request changes" | Loop back to STEP 2 with user's notes. Cap at 3 iterations; on the 3rd "request changes" without approval, ask "Pause and rescope this feature?" |
|
||||
| STEP 4 — subagent crashes (tool error, not test failure) | Treat as STEP 4b error path, present hypothesis-led gate. |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user