Compare commits

..

10 Commits

Author SHA1 Message Date
Bastien Chanot
07e846f0a6 chore(memory): BDR-034 + LRN-051/052 + EVAL-007 — coupled-capitalize v1 2026-06-26 13:27:57 +02:00
Bastien Chanot
037e14dacc test(lib): behavioral end-to-end check doc for coupled-capitalize
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 13:27:57 +02:00
Bastien Chanot
ce34270e47 chore(tasks): coupled-capitalize invariant implementation plan (frozen)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 13:21:05 +02:00
Bastien Chanot
bbfdf10425 docs(changelog): coupled-capitalize invariant v1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 13:21:05 +02:00
Bastien Chanot
df60df66b9 feat(init-project): capitalize founding architecture decisions before FINISH
New STEP 10b (letter-suffix, no renumber): capture the greenfield's founding
decisions (stack choice + why, doctrinal exclusions, conventions) as BDRs before
STEP 11 FINISH, via lib/capitalize-commit.md. F5 filter table distinguishes
founding decision (capitalize) from scaffold detail (skip). Two explicit rules:
- No decision → no entry: trivial projects fabricate nothing; the helper no-ops.
- Founding decisions carry NO commit hash (path+date) by nature — they precede the
  code, so anchoring to the scaffold commit would be a false anchor that dilutes
  the meaning of hash-anchoring elsewhere. Second case where anchoring does not
  apply, after a squash-merged PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 13:04:22 +02:00
Bastien Chanot
e8eff7ebcf fix(ship-feature): capitalize before FINISH — fixes memory stranded outside PR
Reorder: CAPITALIZE moves from STEP 9 (post-FINISH) to STEP 7 (pre-FINISH); FINISH
→8, DOC SYNC→9. The implementation commits exist since STEP 4, so the entries' hash
references are valid, and the memory commit (via lib/capitalize-commit.md) lands on
the branch FINISH integrates — on the push+PR path it was committed after the push
and never reached the PR. DOC SYNC stays post-FINISH = twin chantier (same bug),
annotated and deferred. FAILURE PATHS memory row renumbered 9→7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 12:56:27 +02:00
Bastien Chanot
27636789a9 feat(flows): couple memory-commit to feat/hotfix/bugfix/commit-change via shared include
Each flow's CAPITALIZE step now ends by following lib/capitalize-commit.md, which
surgically commits the approved memory (.claude/memory + .claude/tasks only) as one
chore(memory) commit. No flow hand-rolls git; the helper owns the scope guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 12:50:02 +02:00
Bastien Chanot
b44791bb1b chore(lib): capitalize-commit include protocol
Shared snippet referenced by every dev flow's capitalize step (design-gate.md
pattern). Tail of capitalize: commit the already-approved memory via
memory-commit.sh, surgically. Documents the stdout hash contract, the 2-hash
non-confusion (memory commit vs code-commit anchored in entries), the orchestrator
ordering (before FINISH), and what it must NOT do.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 12:46:51 +02:00
Bastien Chanot
bbef41cebf feat(lib): emit memory-commit hash on stdout + T6/T7 (stdout contract, idempotence)
commit_memory now routes diagnostics to stderr and prints ONLY the memory-commit
short hash to stdout, so the capitalize-commit include can report it. Proven:
- T6: commit→hash (matches independent rev-parse), no-op→empty, unsafe→empty+exit3.
- T7: double run creates exactly one commit (real run, not by construction).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 12:45:06 +02:00
Bastien Chanot
58cb91d2b7 feat(lib): surgical memory-commit helper + deterministic scope tests (T1/T2/T2-bis)
Foundation for the coupled-capitalize invariant (Frame 2): commit ONLY
.claude/memory + .claude/tasks, never `git add -A`. Safety lives in the
pathspec because automation removes the human diff review.

Proven on real git behavior, not assumed:
- T1/T2: dangling code (untracked or pre-staged) never embarked.
- T2-bis: `git commit -- pathspec` takes the working tree, not a stale index.
- T3 idempotent, T4 fail-closed on broken state, T5 TODO.md in scope.

_changed_paths filters to paths with real changes: `git commit -- pathspec`
aborts the whole commit on a no-match pathspec (e.g. empty .claude/tasks),
unlike `git add` which tolerates it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 12:31:46 +02:00
17 changed files with 605 additions and 11 deletions

View File

@ -44,6 +44,7 @@ rules:
| BDR-020 | 2026-06-11 | `/audit-delta`: per-axis SHA markers + always-on fix gate + unreachable-first-run = full report-only | accepted |
| BDR-022 | 2026-06-18 | doc-syncer scoped to public docs; `.claude/` + `CLAUDE.md` read-only context, never targets; conventions + clean mode | accepted |
| BDR-023 | 2026-06-19 | Merge /close into /capitalize — 2 modes + TODO reconcile; /close alias | accepted |
| BDR-034 | 2026-06-26 | Coupled-capitalize invariant v1 — memory commit auto per dev flow (Frame 2) | accepted |
---
@ -538,3 +539,16 @@ rules:
- Blocking yes/skip prompt à la `/onboard` STEP 2.5 — a 2nd STOP mid-build on an optional dep.
- Prose "agent remembers not to re-suggest" — fragile behavioral guard, contradicts [[LRN-046]]/[[LRN-047]].
- **Reference**: commit `11792cc`, `lib/design-gate.md` §4 + §DETECTION (`+motion`/`+animate`). Helper `lib/animation-lib-check.sh` unchanged. Live via symlink (`~/.claude/lib/`→repo). Builds on [[BDR-005]]. See [[LRN-049]].
## BDR-034 — Coupled-capitalize invariant v1 — memory commit auto per dev flow (Frame 2)
- **Date**: 2026-06-26
- **Status**: accepted
- **Decision**: Dev flows committing code now auto-commit memory same breath, via include `lib/capitalize-commit.md` + helper `lib/memory-commit.sh` (surgical: stages+commits `.claude/memory`+`.claude/tasks` only, pathspec, never `git add -A`). 4 inline flows (feat/hotfix/bugfix/commit-change) ref the include at their capitalize step; ship-feature reordered (CAPITALIZE STEP 7 before FINISH STEP 8 — fixes memory committed after push/PR + stranded outside it); init-project gains STEP 10b founding-decisions capitalize. 1 memory commit/flow (F3). Capitalize CONTENT keeps its approval gate — only the COMMIT of approved entries is automated.
- **Why**: Real pain = the 2nd (memory) commit forgotten/manual — ~42% of recent history (17/40 commits) was emergent `chore(memory)`. Frame chosen = "couplé après-code" not "avant commit": keeps hash-anchoring (>50% entries carry `Reference: commit`) + code/memory concern separation; attacks the forgetting, not the ordering. "Capitalize before commit" rejected — inverts a deliberate property AND can't anchor the code hash (hash exists only post-commit).
- **Alternatives rejected**:
- (a) each orchestrator calls capitalize-before-commit — duplicated across 5+ flows (each has bespoke inline capitalize), breaks hash-anchoring, forgettable on next skill added.
- (b) commit-change as the single gate — not on the path of feat/hotfix/bugfix/ship-feature/init-project (they commit inline or via external superpowers); can't detect "pending capitalize".
- (c) single commit chokepoint — doesn't exist; 3 distinct commit mechanisms, one external/unmodifiable (`superpowers:finishing-a-development-branch`).
- Frame 3 (single unified commit, drop hash) — sacrifices >50% entries' anchoring for history aesthetics.
- **Reference**: commits `58cb91d` (helper+tests) · `bbef41c` (hash/stdout + T6/T7) · `b44791b` (include) · `2763678` (4 flows) · `e8eff7e` (ship-feature reorder) · `df60df6` (init-project). Hook (v2, Stop-hook non-blocking BDR-033-style) + doc-sync twin chantier (same PR bug, reorder before FINISH) deferred. See [[LRN-051]], [[LRN-052]], [[EVAL-007]].

View File

@ -27,6 +27,7 @@ rules:
| EVAL-004 | 2026-06-11 | darwin eval 26 perso skills + 4-bug fix round | keep |
| EVAL-005 | 2026-06-23 | Obsolete `claude --effort max` alias missed across Step 9 edits | correct |
| EVAL-006 | 2026-06-25 | prune-memory v1.1 TDD — 6 guards (0a3e766), validated on real data | keep |
| EVAL-007 | 2026-06-26 | Coupled-capitalize machinery — TDD 13 + e2e, surgical scope proven | keep |
---
@ -86,3 +87,12 @@ rules:
- **Method**: run-deterministic all-green (RED-1/2/5/6); RED-3/4 by single faithful subagent runs on throwaway fixtures (deterministic oracles — byte-identical + layer-(a) substring; N=6 tolerance-zero fleet documented in run-behavioral.md, NOT exhausted — 1 run sufficed to prove presence then closure); REAL-data re-test on live learnings.md (602 l): fidelity 0 false-positive vs old line-grep 13, census PROVEN counting both sides (HEAD/WORK not=107/114, no=64/71, never=34/34 — no category dropped). Scope held (only learnings.md), reverted after (measurement, not a kept prune). Clean tree verified first; git + cp backup.
- **Anomalies**: (1) SAFE ≠ USEFUL — compression (pass C) marginal on already-caveman dense content (~3.6% trim, registry GREW); real value = index-drift (D found 19 missing Index rows on the real learnings.md, measured then reverted) + merge (B), not C on dense. (2) RED-8 OPEN — fidelity proves "no negation DELETED", not "none ADDED wrongly"; visible empirically (not/no +7 in the measurement, passed under the drop-radar); remote (compression subtracts) but real. (3) RED-7 OPEN — merge LRN-014+016 maybe PRIMED by the SKILL.md example, not real overlap; verify. (4) two guard bugs caught by VALIDATION not logic: awk `\<` unsupported (mawk) → not/no counted 0; `NR==FNR` blind when working census empty = the deletion case → both fixed + re-validated. (5) REAL ANCHOR FOR PASS D — this very evals.md had EVAL-005's Index row MISSING (pre-existing drift), exactly what the skill's D pass auto-corrects; hand-backfilled in a separate `fix(memory)` commit. learnings.md likewise carries 19 missing Index rows (deferred to an intentional prune). D is NOT theoretical.
- **Action**: keep — safe, useful for B/D, compression marginal on dense (documented limit). `Fixed in v1.1 (TDD found it)` WAS the RED-1 defect (claim of a verify never run); TDD note now TRUE (real suite passes). Patterns → LRN-046/047/048; open items → tests/BACKLOG.md (RED-7/RED-8).
## EVAL-007 — Coupled-capitalize machinery (helper + include + 6 flows)
- **Date**: 2026-06-26
- **Output**: `lib/memory-commit.sh` + `lib/capitalize-commit.md` + wiring of 6 dev flows for the coupled-capitalize invariant (BDR-034).
- **Method**: TDD — RED harness first (helper absent → fail), then 13 deterministic tests (`lib/tests/run-deterministic.sh`): T1/T2 dangling code (untracked + pre-staged) not embarked, T2-bis stale-staged memory → working-tree committed, T3 idempotent no-op, T4 fail-closed on broken git state (exit 3), T5 TODO.md in scope, T6 stdout contract 3-case (hash/empty/empty), T7 double-run = at most one commit. Plus in-vivo e2e (code commit → capitalize writes memory → include commits it: 3 commits, memory commit `.claude/` only, dangling untouched, mem_hash ≠ code_hash). `shellcheck lib/*.sh` clean.
- **Anomalies**: (1) `git commit -- pathspec` strict-on-no-match — caught by real-exec, would have silently aborted the commit on the majority of flows (any run where one scoped path is clean) → fixed by `_changed_paths` BEFORE integration ([[LRN-051]]). (2) v1 helper emitted no clean hash → caught at include design (the reported `<hash>` would have been aspirational) → added `bbef41c` (hash→stdout, diag→stderr), proven by T6. Both caught by exec/review, not assumed.
- **Action**: keep.
- **Reference**: commits `58cb91d`..`df60df6`.

View File

@ -201,3 +201,8 @@ rules:
- TDD'd `/prune-memory` (only destructive skill, untested + carried a false `Fixed in v1.1 (TDD found it)` claim): 6 dangers (RED-1..6) closed by deterministic guards, skill `0a3e766`. Real-data run on learnings.md exposed SAFE≠USEFUL (compression marginal on dense; value = index/merge, not C) + a 13/13-false-positive line-grep fidelity guard → replaced by a per-entry count census (0 FP, proven counting both sides). RED-7 (example-priming) + RED-8 (added-negation) filed in BACKLOG. EVAL-006, LRN-046/047/048.
- Wired `design-gate.md` §4: anim-lib suggestion when a design task hits a motion signal — suggest-only, non-blocking, stateless 1-line (no marker). `motion`/`animate` added to §DETECTION (source). Chose stateless-minimal over a state marker, conditional on stakes: a 1-line cosmetic note's re-fire is annoyance not risk → no marker-grade infra (unlike LRN-046/047's destructive context). Helper unchanged, no 3rd copy of the lib list. Live via symlink. BDR-033, LRN-049.
- Process: caught "write-before-show" twice this session on a live (symlinked) file → on edit=deploy targets the pre-write diff is the only control gate → inverted to show→validate→write. LRN-050.
## 2026-06-26
- Coupled-capitalize invariant v1: dev flows auto-commit memory via include `lib/capitalize-commit.md` + helper `lib/memory-commit.sh` (surgical pathspec, never `-A`; hash→stdout). Frame 2 (après-code-couplé, hash-anchoring kept, 2 commits, memory commit automatic per flow). 6 commits `58cb91d..df60df6`. BDR-034, LRN-051/052, EVAL-007.
- Caught `git commit -- pathspec` strict-on-no-match by real-exec test (would silent-abort on majority of flows) → `_changed_paths` filter (LRN-051). ship-feature reordered CAPITALIZE→before FINISH (fixes memory stranded outside PR). init-project STEP 10b founding decisions (no hash by nature, LRN-052). Hook v2 + doc-sync twin chantier deferred.
- TDD: 13 deterministic + in-vivo e2e, shellcheck clean (EVAL-007). Pre-existing Index drift (decisions 11, learnings 21 rows missing) noted for /prune-memory — not backfilled here.

View File

@ -49,6 +49,8 @@ rules:
| LRN-046 | 2026-06-25 | Destructive skill: deterministic oracle (byte-identical / count census) > semantic judge | any destructive/irreversible skill; behavioral-oracle TDD |
| LRN-047 | 2026-06-25 | A noisy safety guard (13/13 FP) = a guard you learn to ignore = risk → refine, don't tolerate | any guard/alert/lint that can false-positive |
| LRN-048 | 2026-06-25 | A "0/OK/pass" must prove it LOOKED (counted both sides), else verify hard-wired to pass | any verify/test/lint reporting success |
| LRN-051 | 2026-06-26 | `git commit -- pathspec` strict on no-match → filter scoped commits to changed paths | any scoped-commit automation |
| LRN-052 | 2026-06-26 | Hash-anchoring: 2 cases it does NOT apply (pre-code founding, squash-merge) | capitalizing founding/arch decisions; squash repos |
---
@ -643,3 +645,19 @@ rules:
- **Context**: this session twice wrote-then-showed on `lib/design-gate.md` (live via symlink). Both harmless (non-destructive), but the pattern would bite on a destructive live edit. User flagged it → inverted to show→validate→write.
- **Future application**: before editing any file, check if it is live (`readlink -f`, compare to `~/.claude/`); if live, treat the pre-write diff as a mandatory approval gate, not an optional preview. Generalizes to any "edit = deploy" target (dotfiles, served config, hot-reloaded sources).
- **Reference**: `lib/design-gate.md` (symlink → `~/.claude/lib/`). Sibling to [[LRN-044]] (write-through-symlink → resolve real path). Linked to [[BDR-033]].
## LRN-051 — `git commit -- <pathspec>` strict on no-match → filter scoped commits to changed paths
- **Date**: 2026-06-26
- **Pattern**: Automating a scoped commit (commit only subtree X), pass to `git add`/`git commit` ONLY paths with real pending changes. `git add -- <pathspec>` TOLERATES a no-match pathspec (rc 0, stages the matching ones); `git commit -- <pathspec>` is STRICT — one no-match pathspec ABORTS the whole commit (`error: pathspec '<x>' did not match any file(s) known to git`). So a clean scoped path (e.g. empty `.claude/tasks`) silently aborts the commit on most runs. Filter via `git status --porcelain -- <path>` to changed paths only. Bonus: `git commit -- pathspec` = PARTIAL commit (working-tree of those paths, ignores rest of index) → surgical-scope safety: dangling code (untracked OR pre-staged) never embarked.
- **Context**: building `lib/memory-commit.sh`. Naive `git commit -- .claude/memory .claude/tasks` aborted whenever `.claude/tasks` was clean. Caught by real-exec test (T1/T2/T2-bis), NOT by assuming git's behavior — `add` and `commit` are NOT symmetric on pathspecs.
- **Future application**: any "commit only subtree X" automation — filter to changed paths; rely on partial-commit for surgical scope; never assume tool behavior symmetric across sibling subcommands — exec-test it.
- **Reference**: commit `58cb91d` (`_changed_paths` filter + T1/T2/T2-bis), `bbef41c` (stdout hash contract). See [[BDR-034]].
## LRN-052 — Hash-anchoring applicability — 2 cases where `Reference: commit <hash>` does NOT apply
- **Date**: 2026-06-26
- **Pattern**: The anchoring convention (`Reference: commit <hash>`) means "the commit that IMPLEMENTS this decision" (BDR-033 → 11792cc). It does NOT apply in 2 cases: (1) a FOUNDING decision made pre-code (at design time) — attested by no implementing commit; anchoring it to the unrelated scaffold commit is a FALSE anchor. (2) a SQUASH-MERGED PR — the anchored commit ceases to exist post-squash. Forcing a hash in either case dilutes what "anchored" means everywhere else. Rule: pre-code founding decisions carry NO hash (path+date suffice); squash-merge workflows can't anchor.
- **Context**: building init-project STEP 10b (capitalize founding architecture decisions). A founding "Astro not Next" has no implementing commit. Surfaced the BOUNDARY of the anchoring convention — completes it, not contradicts it.
- **Future application**: capitalizing founding/architecture decisions, or working in squash-merge repos — do NOT fabricate a hash; the anchor only means something when a real implementing commit exists.
- **Reference**: commit `df60df6` (init-project STEP 10b hash rule), `lib/capitalize-commit.md` (2-hash non-confusion). See [[BDR-034]], [[BDR-033]].

View File

@ -0,0 +1,60 @@
# Coupled-capitalize Invariant — Implementation Plan (v1)
> Frozen 2026-06-26. Frame 2 (capitalize AFTER code commit, hash anchoring kept,
> 2 commits kept) — make the memory commit automatic & never-forgotten, coupled
> per dev flow. Hook = v2 (separate). doc-sync = twin chantier (same PR bug,
> queued). NO `git add -A` ever — safety lives in the pathspec.
**Goal:** every dev flow that commits code also capitalizes AND commits its
memory automatically, in the same breath, via a shared include — without
duplicating logic and without ever embarking dangling code.
**Architecture:** `lib/memory-commit.sh` (detect + surgical commit scoped to
`.claude/memory` + `.claude/tasks`, never `-A`) + include `lib/capitalize-commit.md`
referenced by the 6 flows (design-gate.md pattern). ship-feature reordered
(capitalize before FINISH) to fix the PR-stranding bug.
## Global Constraints (verbatim, apply to every task)
- Stage: `git add -- .claude/memory .claude/tasks`. Commit: `git commit -m <msg> -- .claude/memory .claude/tasks`. NEVER `git add -A` / `git add .` / `git commit -a`.
- Idempotent: clean tree → no-op, exit 0, no commit.
- Fail-closed on broken state: detached HEAD / merge / rebase / cherry-pick in progress → no commit, skip (exit 3).
- Registries always English. Commit msg style: `chore(memory): <IDs> — <flow> <short>`.
- Capitalize CONTENT keeps its approval gate (unchanged); only the COMMIT of approved entries becomes automatic.
- shellcheck clean on `lib/memory-commit.sh` + test harness.
## File Structure
| Action | File | Responsibility |
|---|---|---|
| Create | `lib/memory-commit.sh` | detect + surgical commit (CLI + sourceable) |
| Create | `lib/capitalize-commit.md` | include protocol |
| Create | `lib/tests/run-deterministic.sh` | T1T5 + T2-bis, git fixture via mktemp |
| Create | `lib/tests/run-behavioral.md` | end-to-end manual per-flow check |
| Modify | `agents/feater.md` | STEP 6 → reference include |
| Modify | `agents/hotfixer.md` | STEP 5 → reference include |
| Modify | `agents/bugfixer.md` | STEP 7 → reference include |
| Modify | `agents/commit-changer.md` | Phase 4 → reference include |
| Modify | `skills/ship-feature/SKILL.md` | reorder capitalize before FINISH + reference |
| Modify | `skills/init-project/SKILL.md` | net-new capitalize founding decisions (F5) before FINISH |
| Modify | `CHANGELOG.md` + `.claude/memory/` | document the invariant (BDR + LRN) |
## Tasks
- **Task 1**`lib/memory-commit.sh` (TDD). Tests T1, T2, **T2-bis**, T3, T4, T5
MUST be REALLY EXECUTED with real outputs reported before Task 2 (user hard
requirement — no presumed git behavior).
- T1: untracked dangling code NOT embarked.
- T2: pre-staged dangling code NOT embarked, remains staged.
- **T2-bis: stale-staged memory (version A) vs working-tree (version B) →
commit must contain B (what capitalize wrote), not A.** Proves `git add --`
re-stage neutralizes stale index. If raw pathspec-commit took A → guard needed.
- T3: idempotence (clean → no-op exit 0). T4: unsafe state → skip exit 3.
T5: TODO.md embarked.
- **Task 2**`lib/capitalize-commit.md` include (WHEN / DO / HARD RULE / ORDERING / IDEMPOTENT).
- **Task 3** — wire feater/hotfixer/bugfixer/commit-changer (1-line reference each).
- **Task 4** — ship-feature reorder: 7=CAPITALIZE(+include), 8=FINISH, 9=DOC SYNC; renumber FAILURE PATHS; doc-sync stays post-FINISH (twin chantier note).
- **Task 5** — init-project: add STEP 10.6 CAPITALIZE FOUNDING DECISIONS (F5 filter table: structuring decision = capitalize, scaffold detail = skip) before STEP 11 FINISH.
- **Task 6** — behavioral verify + shellcheck + CHANGELOG + BDR/LRN (dogfood the include).
## Deferred (explicit, by design not by omission)
- Hook (Stop, non-blocking, stateless, BDR-033 style) → v2.
- doc-sync PR-stranding (same reorder-before-FINISH) → twin chantier.
- hash-anchoring vs squash-merge → known blind spot, out of scope.

View File

@ -233,3 +233,17 @@ Version 3.4.0 → 3.5.0.
- [x] PHASE 3: install-plugins.sh STEP 5.5; update-all.sh block; plugins.lock.json; doctor.sh; lib/detect-plugins.sh; lib/profile.sh; plugin-advisor.md; skills/profile/SKILL.md
- [x] PHASE 4: README row; USAGE always-on line; CHANGELOG; CLAUDE.md cmd ref; skills/capitalize+prune-memory cmd refs; version.txt
- [x] PHASE 5: shellcheck clean (SC1091 info only); full diff reviewed → committed + merged to master
## 2026-06-26 — coupled-capitalize invariant v1 (Frame 2)
Plan: [.claude/tasks/2026-06-26-coupled-capitalize-invariant.md](2026-06-26-coupled-capitalize-invariant.md)
Goal: every dev flow commits its memory automatically (1 commit/flow) via shared
include; ship-feature reordered (capitalize before FINISH = PR-bug fix). Hook v2,
doc-sync twin chantier deferred. Safety in the pathspec, never `git add -A`.
- [x] Task 1 — `lib/memory-commit.sh` + tests T1/T2/T2-bis/T3/T4/T5/T6/T7 (real exec, outputs reported) — 58cb91d + bbef41c
- [x] Task 2 — `lib/capitalize-commit.md` include — b44791b
- [x] Task 3 — wire feater/hotfixer/bugfixer/commit-changer — 2763678
- [x] Task 4 — ship-feature reorder (capitalize before FINISH) — e8eff7e
- [x] Task 5 — init-project founding-decisions capitalize (F5) — df60df6
- [x] Task 6 — behavioral verify + shellcheck + CHANGELOG + BDR/LRN — this commit
- [ ] v2 (deferred) — Stop hook (non-blocking, BDR-033 style) reusing the detector
- [ ] twin chantier (deferred) — doc-sync reorder before FINISH (same PR bug)

View File

@ -9,6 +9,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
<!-- DRAFT (doc-syncer): grounded in commits since 3.4.0; review wording + completeness before release. -->
### Added
- Coupled-capitalize: dev flows (feat / hotfix / bugfix / commit-change, ship-feature, init-project) auto-commit their memory in the same breath, via shared `lib/capitalize-commit.md` + `lib/memory-commit.sh` (surgical — `.claude/memory` + `.claude/tasks` only, never `git add -A`)
- `/audit-delta` — recurring multi-axis audit (norms / bugs / dead code / security) scoped to changes since last run, with per-axis SHA markers
- `/capitalize` — flush uncapitalized context to the memory registries before `/clear` or `/compact`
- `/prune-memory` — curate and compress the `.claude/memory/` registries
@ -23,6 +24,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
- `.claude/{tasks,memory,audits}/` governance layout + 5 memory registries (decisions, learnings, blockers, journal, evals)
### Changed
- `/ship-feature`: capitalize + memory commit moved before FINISH (was after) — fixes memory committed after a push/PR and stranded outside it
- `/init-project`: new STEP 10b captures founding architecture decisions as BDRs before FINISH
- `/validate` renamed to `/web-validate` — clearer scoped name (W3C + WCAG); routing, skill profiles, cross-references, and the client-deliverable leak-guard updated (the guard still matches legacy `/validate` so older client docs stay covered)
- `/seo` split into parallel `seo` + `geo` agents with shared resources
- `/onboard` rewritten: archetype-aware pipeline (orchestrator + config-only agent), security audit archetype-aware

View File

@ -184,6 +184,11 @@ A bugfix with an understood root cause is almost always worth one entry:
If the bug was trivial and the root cause not transferable → skip with `CAPITALIZE: trivial, skip`.
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
hash, and no-ops if nothing was written.
---
## RULES

View File

@ -152,3 +152,9 @@ Append approved entries + update the Index of each registry file. Add a line to
**Language rule**: written entries are ALWAYS in English (see CLAUDE.md "Memory registries" § Language). The interactive gate may mirror the user's language; the appended entries must not.
If all commits are pure chore/docs/style with nothing to log → skip with `CAPITALIZE: nothing to log`.
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
hash, and no-ops if nothing was written. This is a separate commit from the Phase 3
code commits — their hashes are already anchored inside the entries.

View File

@ -152,6 +152,11 @@ Always append a 1-line entry to today's heading in `.claude/memory/journal.md`.
If no substantive capture candidate → skip with `CAPITALIZE: nothing to log`.
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
hash, and no-ops if nothing was written.
---
## RULES

View File

@ -124,6 +124,12 @@ Always append a 1-line entry to today's heading in `.claude/memory/journal.md` (
**Language rule**: the journal line and any proposed BLK/LRN entries are ALWAYS written in English (see CLAUDE.md "Memory registries" § Language).
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
hash, and no-ops if nothing was written. The always-on journal line means a
trivial hotfix still produces a `chore(memory): journal — …` commit (Frame 2 / F3).
---
## RULES

89
lib/capitalize-commit.md Normal file
View File

@ -0,0 +1,89 @@
# CAPITALIZE-COMMIT — couple the memory commit to the dev flow
Inline snippet. Include at the END of any dev flow's CAPITALIZE step, AFTER the
approved registry / journal / TODO entries are written. It commits ONLY the
memory, surgically, so the flow never leaves an uncommitted `.claude/memory`
behind and never embarks dangling code.
This is the TAIL of capitalize, not a replacement: the SCAN + APPROVAL GATE that
decide WHAT to write are unchanged and run before this snippet. Only the COMMIT
of the already-approved entries is automated here.
## WHEN TO RUN
At the capitalize step, once the approved entries are written to
`.claude/memory/*` (and any `.claude/tasks/TODO.md` reconcile is done), with the
code already committed.
- Inline-commit flows (feat / hotfix / bugfix / commit-change): run it right
after writing the entries, on the current branch.
- Orchestrators that integrate via `superpowers:finishing-a-development-branch`
(ship-feature / init-project): run it BEFORE the FINISH step — otherwise the
memory commit strands outside the merge/PR. See ORDERING.
This snippet commits whatever is PENDING under `.claude/memory` + `.claude/tasks`;
it does NOT decide content. A flow whose gate wrote only a journal line yields a
`chore(memory): journal — …` commit (one memory commit per flow — Frame 2 / F3).
If a flow should stay fully silent on a trivial run, that is the FLOW's gate
policy (e.g. hotfix skipping the journal line), not this snippet's concern —
tune it there.
## DO
1. Compose the message from the IDs just written + the flow, matching repo style
`chore(memory): <IDs> — <flow> <short>`. Examples:
- `chore(memory): BDR-034 + LRN-051 — feat dark-mode toggle`
- `chore(memory): BLK-010 resolved — bugfix profile path`
- `chore(memory): journal — hotfix copy typo` (journal-only, nothing else)
2. Commit surgically via the helper, capturing the memory-commit hash it prints:
mem_hash=$(bash "$HOME/.claude/lib/memory-commit.sh" commit "<message>")
rc=$?
3. Report by (rc, mem_hash):
- rc 0, mem_hash non-empty → `✅ mémoire committée <mem_hash>`
- rc 0, mem_hash empty → `CAPITALIZE: rien à committer` (helper no-op)
- rc 3 → unsafe git state (detached / merge in progress);
memory stays in the working tree for a manual
commit — surface the helper's stderr.
`<mem_hash>` is the MEMORY commit (the one that ADDS the entries). It is NOT
the code-commit hash that capitalize anchored INSIDE the entries
(`Reference: commit <code-hash>`). Two commits, two hashes — never report the
code hash here.
## HARD RULE — surgical scope
The helper stages and commits ONLY `.claude/memory` + `.claude/tasks`, filtered
to paths with real changes, via pathspec — never `git add -A` / `git add .` /
`git commit -a`. Automation removes the human diff review that would catch an
accidental stage, so the scope IS the safety. Do NOT bypass the helper with a
manual `git add` / `git commit`: that reintroduces the exact risk the helper
removes (proven: dangling code, untracked or pre-staged, is never embarked; a
no-match pathspec is filtered, not fatal).
## ORDERING (orchestrators only)
`finishing-a-development-branch` may merge-and-delete the branch or push a PR. A
memory commit created AFTER it lands outside the integrated history — stranded
on the PR path. So in ship-feature / init-project this snippet runs BEFORE
FINISH. The code commits already exist (implementation step), so the entries'
hash references are valid at this point.
## WHAT THIS DOES NOT DO
- Does NOT commit code — the code commit happened upstream (implementation step).
- Does NOT push — pushing / merging is FINISH's job, which runs AFTER this in
orchestrators.
- Does NOT decide WHAT to capitalize — the scan + approval gate upstream own
that; this only commits what was already approved and written.
- Does NOT reference or echo the code-commit hash anchored in the entries — it
reports only the memory-commit hash the helper returns.
- Does NOT run if you hand-roll `git add` / `git commit` instead of the helper —
bypassing it drops the surgical-scope guarantee. Always call the helper.
## IDEMPOTENT
Safe to run when memory is already clean: the helper no-ops (exit 0, empty
stdout, no commit). Running it twice creates at most one commit.

108
lib/memory-commit.sh Executable file
View File

@ -0,0 +1,108 @@
#!/usr/bin/env bash
# memory-commit.sh — surgically commit ONLY .claude/memory + .claude/tasks.
#
# Used by the dev-flow capitalize step (and, later, the v2 Stop hook) to couple
# the memory commit to the flow. Safety lives in the PATHSPEC, never in a human
# diff review — automation removes that review, so the scope must be airtight:
# code that happens to be dirty or staged is NEVER embarked.
#
# Usage (CLI):
# memory-commit.sh pending # exit 0 if memory/tasks have changes, 1 if clean
# memory-commit.sh commit "<message>" # surgical commit; exit 0 ok/no-op, 3 unsafe state
#
# Output contract for `commit`: diagnostics go to stderr; on a real commit the
# short hash of the MEMORY commit is the ONLY thing on stdout (empty on no-op or
# unsafe), so callers can capture it: `mem_hash=$(memory-commit.sh commit "msg")`.
#
# Sourceable: `memory_pending` and `commit_memory` for the v2 hook.
set -uo pipefail
MC_PATHS=(".claude/memory" ".claude/tasks")
_in_git_repo() { git rev-parse --git-dir >/dev/null 2>&1; }
# True (0) when the repo is in a state where we must NOT auto-commit:
# detached HEAD, or a merge/rebase/cherry-pick in progress.
_unsafe_state() {
local gitdir
gitdir="$(git rev-parse --git-dir 2>/dev/null)" || return 0
if [ -e "$gitdir/MERGE_HEAD" ] || [ -e "$gitdir/rebase-merge" ] ||
[ -e "$gitdir/rebase-apply" ] || [ -e "$gitdir/CHERRY_PICK_HEAD" ]; then
return 0
fi
git symbolic-ref -q HEAD >/dev/null 2>&1 || return 0 # detached HEAD
return 1
}
# Scoped paths that have actual pending changes. A bare/empty path (e.g. an
# empty .claude/tasks dir) is excluded: `git commit -- <pathspec>` aborts the
# WHOLE commit on a pathspec that matches no known file, even though `git add`
# tolerates it. So scope = only paths git would accept.
_changed_paths() {
local p
for p in "${MC_PATHS[@]}"; do
[ -e "$p" ] || continue
[ -n "$(git status --porcelain -- "$p" 2>/dev/null)" ] && printf '%s\n' "$p"
done
}
# 0 if something is pending under the scoped paths, 1 if clean / absent.
memory_pending() {
_in_git_repo || return 1
local changed
mapfile -t changed < <(_changed_paths)
[ "${#changed[@]}" -gt 0 ]
}
# Surgical commit of the scoped paths only. Returns 0 (ok or no-op), 3 (unsafe).
# On a real commit, prints the memory-commit short hash to stdout (stderr = diag).
commit_memory() {
local msg="${1:?commit message required}"
_in_git_repo || {
echo "memory-commit: not a git repo — skip" >&2
return 3
}
if _unsafe_state; then
echo "memory-commit: detached HEAD or merge/rebase in progress — skip (no commit)" >&2
return 3
fi
local changed
mapfile -t changed < <(_changed_paths)
if [ "${#changed[@]}" -eq 0 ]; then
echo "memory-commit: nothing pending — no-op" >&2
return 0
fi
# Re-stage working-tree content of the scoped paths over any stale index entry,
# then commit ONLY those paths. The pathspec on `git commit` makes it a partial
# commit: other staged files (dangling code) are not recorded.
git add -- "${changed[@]}"
if git diff --cached --quiet -- "${changed[@]}"; then
echo "memory-commit: only ignored/no-op changes — no-op" >&2
return 0
fi
# Contract: diagnostics go to stderr; on success ONLY the memory-commit short
# hash goes to stdout, so a caller can do `mem_hash=$(... commit "msg")`.
git commit -q -m "$msg" -- "${changed[@]}"
git rev-parse --short HEAD
}
main() {
local cmd="${1:-}"
case "$cmd" in
pending) memory_pending ;;
commit)
shift
commit_memory "${1:-}"
;;
*)
echo "usage: memory-commit.sh {pending | commit <message>}" >&2
return 2
;;
esac
}
# Run main only when executed, not when sourced.
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi

View File

@ -0,0 +1,45 @@
# Behavioral check — coupled-capitalize, end-to-end
The deterministic suite (`run-deterministic.sh`, T1T7) proves `memory-commit.sh`
in isolation. This is the in-vivo whole-chain check: a real dev-flow shape —
code commit, then capitalize writes memory, then the include commits it — with
dangling code present, proving the memory commit is coupled AND surgical.
## Scenario (run on a throwaway repo)
```bash
R="$(mktemp -d)"; cd "$R"
git init -q && git config user.email t@t.t && git config user.name t
mkdir -p .claude/memory .claude/tasks src
printf 'baseline\n' > .claude/memory/decisions.md
git add -A && git commit -qm baseline
# 1) the flow commits CODE
printf 'feature code\n' > src/feature.txt
git add -- src/feature.txt && git commit -qm "feat: the feature"
code_hash="$(git rev-parse --short HEAD)"
# 2) capitalize writes the approved entry (referencing the code hash) + journal
printf '\n## BDR-099 — example\n- Reference: commit %s\n' "$code_hash" >> .claude/memory/decisions.md
printf -- '- did the thing\n' >> .claude/tasks/TODO.md
# 3) a code file is left dangling (must NOT be embarked)
printf 'WIP do not commit\n' > src/dangling.txt
# 4) the include commits the memory surgically
mem_hash="$(bash "$HOME/.claude/lib/memory-commit.sh" commit "chore(memory): BDR-099 — example")"
```
## Expected (assert)
- Exactly TWO commits after baseline: the code commit, then the memory commit.
- The memory commit (`$mem_hash`) contains ONLY `.claude/memory/decisions.md`
and `.claude/tasks/TODO.md` — never `src/feature.txt` (already committed) or
`src/dangling.txt` (WIP).
- `src/dangling.txt` is still untracked after the memory commit.
- `$mem_hash` (the memory commit) ≠ `$code_hash` (anchored inside the entry).
If all hold, the chain is coupled (memory committed in the same breath as the
flow) and surgical (no dangling code embarked). This mirrors what feat / hotfix /
bugfix / commit-change do via their capitalize step, and what ship-feature /
init-project do before FINISH.

146
lib/tests/run-deterministic.sh Executable file
View File

@ -0,0 +1,146 @@
#!/usr/bin/env bash
# Deterministic tests for lib/memory-commit.sh.
#
# Proves the surgical-scope safety contract on REAL git behavior (not assumed):
# - dangling code (untracked OR pre-staged) is NEVER embarked in a memory commit
# - stale-staged memory (version A) yields the WORKING-TREE version (B) that
# capitalize just wrote — `git add --` re-stage neutralizes the stale index
# - clean tree → no-op ; broken git state → skip ; TODO.md is in scope
#
# No -e: run every test and report, even after a failure.
set -uo pipefail
HERE="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELPER="$HERE/../memory-commit.sh"
PASS=0
FAIL=0
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; PASS=$((PASS + 1)); }
ko() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=$((FAIL + 1)); }
# Fresh throwaway repo with a baseline commit (.claude/memory tracked, src/ tracked).
new_repo() {
local d
d="$(mktemp -d)"
git -C "$d" init -q
git -C "$d" config user.email t@t.t
git -C "$d" config user.name tester
mkdir -p "$d/.claude/memory" "$d/.claude/tasks" "$d/src"
printf 'baseline\n' >"$d/.claude/memory/decisions.md"
printf 'src baseline\n' >"$d/src/app.txt"
git -C "$d" add -A
git -C "$d" commit -qm baseline
printf '%s' "$d"
}
# Files recorded in HEAD (no diff noise).
head_files() { git -C "$1" diff-tree --no-commit-id --name-only -r HEAD; }
echo "T1 — untracked dangling code is NOT embarked"
R="$(new_repo)"
printf 'NEW DECISION\n' >>"$R/.claude/memory/decisions.md"
printf 'junk\n' >"$R/src/dangling.txt"
(cd "$R" && "$HELPER" commit "chore(memory): T1") >/dev/null 2>&1
committed="$(head_files "$R")"
status="$(git -C "$R" status --porcelain)"
printf ' committed: [%s]\n' "$committed"
printf ' status : [%s]\n' "$status"
if [ "$committed" = ".claude/memory/decisions.md" ]; then ok "only memory committed"; else ko "expected only memory, got [$committed]"; fi
if printf '%s\n' "$status" | grep -q '^?? src/dangling.txt$'; then ok "dangling code still untracked"; else ko "dangling code not left untracked"; fi
rm -rf "$R"
echo "T2 — PRE-STAGED dangling code is NOT embarked, stays staged"
R="$(new_repo)"
printf 'NEW DECISION\n' >>"$R/.claude/memory/decisions.md"
printf 'junk\n' >"$R/src/dangling.txt"
git -C "$R" add src/dangling.txt
(cd "$R" && "$HELPER" commit "chore(memory): T2") >/dev/null 2>&1
committed="$(head_files "$R")"
status="$(git -C "$R" status --porcelain)"
printf ' committed: [%s]\n' "$committed"
printf ' status : [%s]\n' "$status"
if [ "$committed" = ".claude/memory/decisions.md" ]; then ok "only memory committed"; else ko "expected only memory, got [$committed]"; fi
if printf '%s\n' "$status" | grep -q '^A src/dangling.txt$'; then ok "pre-staged code remained staged, not embarked"; else ko "pre-staged code state wrong"; fi
rm -rf "$R"
echo "T2-bis — stale-staged memory (A) vs working tree (B): commit must take B"
R="$(new_repo)"
printf 'STALE STAGED VERSION A\n' >>"$R/.claude/memory/decisions.md"
git -C "$R" add .claude/memory/decisions.md # index = A
printf 'baseline\nFRESH WORKING B\n' >"$R/.claude/memory/decisions.md" # working = B (no 'A' line)
(cd "$R" && "$HELPER" commit "chore(memory): T2bis") >/dev/null 2>&1
blob="$(git -C "$R" show HEAD:.claude/memory/decisions.md)"
printf ' committed blob:\n'
printf '%s\n' "$blob" | sed 's/^/ | /'
if printf '%s' "$blob" | grep -q 'FRESH WORKING B' && ! printf '%s' "$blob" | grep -q 'STALE STAGED VERSION A'; then
ok "working-tree (B) committed, stale staged (A) discarded"
else
ko "stale staged version (A) leaked into the commit"
fi
rm -rf "$R"
echo "T3 — clean tree → no-op, exit 0, HEAD unchanged"
R="$(new_repo)"
before="$(git -C "$R" rev-parse HEAD)"
(cd "$R" && "$HELPER" commit "chore(memory): T3")
rc=$?
after="$(git -C "$R" rev-parse HEAD)"
printf ' exit=%s HEAD %s\n' "$rc" "$([ "$before" = "$after" ] && echo unchanged || echo CHANGED)"
if [ "$rc" -eq 0 ] && [ "$before" = "$after" ]; then ok "no-op, exit 0, HEAD unchanged"; else ko "expected no-op exit 0, got exit $rc"; fi
rm -rf "$R"
echo "T4 — broken git state (mid-merge) → skip, exit 3, no commit"
R="$(new_repo)"
printf 'CHANGE\n' >>"$R/.claude/memory/decisions.md"
: >"$R/.git/MERGE_HEAD"
before="$(git -C "$R" rev-parse HEAD)"
(cd "$R" && "$HELPER" commit "chore(memory): T4")
rc=$?
after="$(git -C "$R" rev-parse HEAD)"
printf ' exit=%s HEAD %s\n' "$rc" "$([ "$before" = "$after" ] && echo unchanged || echo CHANGED)"
if [ "$rc" -eq 3 ] && [ "$before" = "$after" ]; then ok "unsafe state skipped, exit 3, no commit"; else ko "expected skip exit 3, got exit $rc"; fi
rm -rf "$R"
echo "T5 — .claude/tasks/TODO.md is in scope"
R="$(new_repo)"
printf -- '- [ ] task\n' >"$R/.claude/tasks/TODO.md"
(cd "$R" && "$HELPER" commit "chore(memory): T5") >/dev/null 2>&1
committed="$(head_files "$R")"
printf ' committed: [%s]\n' "$committed"
if printf '%s\n' "$committed" | grep -q '^.claude/tasks/TODO.md$'; then ok "TODO.md embarked"; else ko "TODO.md not embarked"; fi
rm -rf "$R"
echo "T6 — stdout contract: commit→hash, no-op→empty, unsafe→empty"
R="$(new_repo)"
printf 'CHG\n' >>"$R/.claude/memory/decisions.md"
out="$( (cd "$R" && "$HELPER" commit "chore(memory): T6") 2>/dev/null )"
expected="$(git -C "$R" rev-parse --short HEAD)"
printf ' commit stdout=[%s] HEAD=[%s]\n' "$out" "$expected"
if [ -n "$out" ] && [ "$out" = "$expected" ]; then ok "commit emits the memory-commit hash on stdout"; else ko "hash mismatch [$out] != [$expected]"; fi
out="$( (cd "$R" && "$HELPER" commit "chore(memory): T6-noop") 2>/dev/null )"
printf ' no-op stdout=[%s]\n' "$out"
if [ -z "$out" ]; then ok "no-op emits nothing on stdout"; else ko "no-op leaked stdout [$out]"; fi
printf 'CHG2\n' >>"$R/.claude/memory/decisions.md"
: >"$R/.git/MERGE_HEAD"
out="$( (cd "$R" && "$HELPER" commit "chore(memory): T6-unsafe") 2>/dev/null )"
rc=$?
printf ' unsafe stdout=[%s] exit=%s\n' "$out" "$rc"
if [ -z "$out" ] && [ "$rc" -eq 3 ]; then ok "unsafe emits nothing on stdout, exit 3"; else ko "unsafe leaked stdout [$out] or rc $rc"; fi
rm -rf "$R"
echo "T7 — double run: at most one commit (real run, not by construction)"
R="$(new_repo)"
printf 'ONCE\n' >>"$R/.claude/memory/decisions.md"
base="$(git -C "$R" rev-list --count HEAD)"
h1="$( (cd "$R" && "$HELPER" commit "chore(memory): T7-run1") 2>/dev/null )"
after1="$(git -C "$R" rev-list --count HEAD)"
h2="$( (cd "$R" && "$HELPER" commit "chore(memory): T7-run2") 2>/dev/null )"
after2="$(git -C "$R" rev-list --count HEAD)"
printf ' counts base=%s after1=%s after2=%s ; h1=[%s] h2=[%s]\n' "$base" "$after1" "$after2" "$h1" "$h2"
if [ "$after1" -eq "$((base + 1))" ] && [ -n "$h1" ]; then ok "run1 created exactly one commit (hash emitted)"; else ko "run1 commit count wrong"; fi
if [ "$after2" -eq "$after1" ] && [ -z "$h2" ]; then ok "run2 is a no-op (no 2nd commit, empty stdout)"; else ko "run2 was not a no-op"; fi
rm -rf "$R"
echo
printf 'RESULT: %d passed, %d failed\n' "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]

View File

@ -161,6 +161,57 @@ Load `$HOME/.claude/agents/analyzer.md`. Check: no regressions, no deviations, n
## STEP 10 — CODE REVIEW
Invoke `superpowers:requesting-code-review`. Fix all CRITICAL before proceeding.
## STEP 10b — CAPITALIZE FOUNDING DECISIONS (memory registries)
A greenfield's founding architecture decisions are the highest-value BDRs — the
"why Astro not Next", the SPA-ban for a public site, the API-versioning policy.
Losing them means losing the rationale of the foundations. Capture them BEFORE
STEP 11 FINISH so the memory commit lands on the branch FINISH integrates.
Capture ONLY structuring decisions, not scaffold detail:
| Capitalize — founding (BDR) | Skip — scaffold detail |
|--------------------------------------------------------------|-----------------------------------|
| Stack / framework choice + why (Astro vs Next) | directory names, scaffolded files |
| Architecture pattern, data-flow shape | dev-tooling / formatter config |
| Doctrinal exclusions (public site = no SPA, API /v1 day one) | which template files were copied |
| Security defaults adopted; conventions binding future code | anything reversible / obvious |
Source the candidates from: the PROJECT BRIEF (STEP 1), the DESIGN's resolved
decisions (STEP 3), and the validated STACK / CONVENTIONS / EXCEPTIONS (STEP 4
gate). These ARE the founding decisions — the user just approved them.
**No decision → no entry.** A trivial project with no genuine structuring choice
capitalizes NOTHING. Do NOT fabricate a BDR to fill the step. Print
`CAPITALIZE: no founding decision to log`; the memory commit below then no-ops.
1. Pre-fill a BDR-XXX entry per founding decision (id, date, title, decision,
why, alternatives rejected — from the DESIGN's rejected options).
2. Present grouped:
```
CAPITALIZE — founding decisions proposées
[ decisions.md ] BDR-XXX — <decision><1-line why>
Valider lesquels ? (all / <IDs> / edit / skip)
```
3. Append approved entries + update the Index. Append a journal line under today.
**Hash rule — founding decisions carry NO commit hash; use path + date only.**
This is by nature, not an omission: a founding decision is made at DESIGN
(STEP 3), BEFORE any code, attested by no implementing commit — there is no hash
to anchor. Anchoring it to the unrelated scaffold commit would be a FALSE anchor
that dilutes what `Reference: commit <hash>` means everywhere else (the commit
that IMPLEMENTS the decision, e.g. BDR-033 → 11792cc). This is the SECOND case
where hash-anchoring does not apply — the first being a squash-merged PR, whose
anchored commit ceases to exist.
**Language rule**: written entries are ALWAYS in English (CLAUDE.md "Memory
registries"). The gate may mirror the user's language; entries must not.
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits the approved founding decisions (`.claude/memory` +
`.claude/tasks` only, never `git add -A`) as one `chore(memory)` commit, BEFORE
STEP 11 FINISH so the memory is integrated with the branch, not stranded. If
nothing was capitalized, the helper no-ops — no commit.
## STEP 11 — FINISH
Invoke `superpowers:finishing-a-development-branch`. Tests pass, build clean, no placeholders, initial commit ready.

View File

@ -110,16 +110,8 @@ Load `$HOME/.claude/agents/analyzer.md`. Check: no regressions, no stale code, n
## STEP 6 — CODE REVIEW
Invoke `superpowers:requesting-code-review`. Fix all CRITICAL before proceeding.
## STEP 7 — FINISH
Invoke `superpowers:finishing-a-development-branch`. Tests pass, build clean, ready to merge.
## STEP 8 — DOC SYNC
Load `$HOME/.claude/agents/doc-syncer.md`.
Execute in automatic mode:
`auto-mode scope: <list of files modified during this session>`
## STEP 9 — CAPITALIZE (memory registries)
Feature shipped implies at least one design decision worth capturing. Run this before declaring done:
## STEP 7 — CAPITALIZE (memory registries)
Feature shipped implies at least one design decision worth capturing. Run this BEFORE STEP 8 FINISH — the implementation commits (STEP 4) already exist, so the entries' hash references are valid, and the memory commit lands on the branch that FINISH integrates (otherwise it strands outside the PR):
1. Scan conversation context for:
- **Design / architecture choices with rationale** → candidate for `decisions.md` (BDR-XXX).
@ -146,6 +138,23 @@ Feature shipped implies at least one design decision worth capturing. Run this b
If nothing substantive to log → print `CAPITALIZE: nothing substantive to log` and skip.
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
hash, and no-ops if nothing was written. It runs BEFORE STEP 8 FINISH so the
memory is integrated with the branch, not stranded outside the PR.
## STEP 8 — FINISH
Invoke `superpowers:finishing-a-development-branch`. Tests pass, build clean, ready to merge.
## STEP 9 — DOC SYNC
<!-- Stays post-FINISH = TWIN CHANTIER: doc-sync has the same PR-stranding bug
capitalize just fixed (artifacts written after integration). Deferred to
v-next; move it before FINISH then. Not fixed here to keep v1 scoped. -->
Load `$HOME/.claude/agents/doc-syncer.md`.
Execute in automatic mode:
`auto-mode scope: <list of files modified during this session>`
---
## RULES
@ -171,7 +180,7 @@ The pipeline must handle these without aborting silently:
| STEP 4 — subagent crashes (tool error, not test failure) | Treat as STEP 4b error path, present hypothesis-led gate. |
| STEP 4b — option A retried 2× still failing | Force fall-through to B or C. Do not loop a 3rd time. |
| STEP 6 — review returns CRITICAL items | Loop back to STEP 4 for those items only. Cap at 2 review-cycle iterations; if still CRITICAL, escalate. |
| STEP 9`.claude/memory/` missing | Create the registry files from `~/.claude/templates/memory/` first, then proceed. |
| STEP 7`.claude/memory/` missing | Create the registry files from `~/.claude/templates/memory/` first, then proceed. |
---