From 135b4872a82db9e32c5571a3071ad942c0d898c5 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Sat, 27 Jun 2026 18:26:18 +0200 Subject: [PATCH] =?UTF-8?q?chore(memory):=20BDR-038,=20LRN-062..066,=20EVA?= =?UTF-8?q?L-009=20=E2=80=94=20capitalize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/memory/decisions.md | 9 +++++++++ .claude/memory/evals.md | 7 +++++++ .claude/memory/journal.md | 1 + .claude/memory/learnings.md | 28 ++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index a5c7087..fb2adfc 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -48,6 +48,7 @@ rules: | BDR-035 | 2026-06-26 | Analyze-before-plan invariant v1 — read-before bookend of coupled-capitalize | accepted | | BDR-036 | 2026-06-27 | Doc-sync coupled invariant — commit docs doc-syncer patches (twin of BDR-034, BUILT not reordered) | accepted | | BDR-037 | 2026-06-27 | v2 capitalize Stop-hook rejected → wire /capitalize+/close to the include | accepted | +| BDR-038 | 2026-06-27 | deploy skill: per-project learning runbook, two-moment cold-resume | accepted | --- @@ -595,3 +596,11 @@ rules: - (b) SessionEnd auto-commit (FAIT) — bypasses STEP 3 content gate, embarks half-written entries, can't report actionably. - (c) abandon with no redirect — leaves the real wiring gap open; fix for an unwired skill = wire it. - **Reference**: read-phase analysis (no hook code ever written); wiring commit (capitalize STEP 5B) follows. Completes [[BDR-034]] rollout; applies [[BDR-033]] doctrine to REJECT (not all nudges — the determinism split is [[LRN-061]]). See [[LRN-061]], [[LRN-047]], [[LRN-049]], [[LRN-054]]. + +## BDR-038 — deploy skill: per-project learning runbook, two-moment cold-resume +- **date**: 2026-06-27 +- **status**: accepted +- **decision**: New `/deploy` skill = per-project runbook in `.claude/deploy/`. 5 artifacts: `PROCEDURE.md` (runbook, in-place edits), `INCIDENTS.md` (`DEP-NNN` ledger, append-only), `STATE.json` (deploy oracle, committed), `PENDING.json` (cold-resume bridge, gitignored), `NEXT.sh` (instantiated checklist, gitignored, hand-run). Two-moment spine: BEFORE (delta-instantiate `NEXT.sh` + hand back) → user deploys OUT-OF-BAND → AFTER (react: MARK success or LEARN from failure). Cold cross-session resume via `PENDING.json` (disk = only memory across the gap). Learn = atomic patch+incident (one `deploy-commit.sh` call, both files). New helper `lib/deploy-commit.sh` (allowlist `.claude/deploy/`). Built via subagent-driven-development (4 tasks). +- **why**: deployment memory that LEARNS (runbook patched in place per failure) beats a frozen runbook; disk-bridge so a resume survives session loss. +- **alternatives**: tag-oracle (rejected — lightweight-tag date unreliable, rebase-fragile, [[LRN-063]]); separate append-only ERRORS log (rejected — git history of `PROCEDURE.md`+`INCIDENTS.md` suffices, no `resolved-by` field); `NEXT.sh`-as-bridge (rejected — ephemeral ≠ persistent → separate `PENDING.json`); reuse doc/memory-commit (rejected — neither can commit `.claude/deploy/`, [[LRN-064]]). +- **reference**: `skills/deploy/SKILL.md`, `lib/deploy-commit.sh`, `templates/deploy/`; branch `feat/deploy-skill` (b210e8d..79741e3, kept un-merged); spec `docs/specs/2026-06-27-deploy-skill-design.md`, plan `docs/plans/2026-06-27-deploy-skill.md`. diff --git a/.claude/memory/evals.md b/.claude/memory/evals.md index 417494f..0d55720 100644 --- a/.claude/memory/evals.md +++ b/.claude/memory/evals.md @@ -29,6 +29,7 @@ rules: | 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 | | EVAL-008 | 2026-06-27 | Doc-sync coupled machinery — 28/28 real-exec, swap-sweep caught prior debt | keep | +| EVAL-009 | 2026-06-27 | deploy skill subagent-driven build: multi-stage review + pressure-test net-positive | keep | --- @@ -106,3 +107,9 @@ rules: - **Output**: 6 surgical commits `ae1f218` · `4a54a65` · `fb1f359` · `636b491` · `e81f629` · `1b01b95`. Caught + fixed a PRIOR-chantier latent ref bug (README:153, stale since e8eff7e's swap). Scope expanded mid-chantier (sweep found the inline-flow gap → 3 flows wired). - **Anomalies**: (1) the deferred note ("reorder only") was WRONG → corrected in read-phase before any code ([[LRN-058]]). (2) init-project PARTIAL — [[BLK-010]]/[[BLK-011]] deferred = NEW work, surfaced not papered over. Both engraved in [[BDR-036]]. - **Action**: keep. BLK-010 (scaffold/unborn-HEAD) + BLK-011 (GSD ROADMAP post-FINISH) + MINOR-gate strengthening = separate chantiers. + +## EVAL-009 — deploy skill subagent-driven build: multi-stage review + pressure-test net-positive +- **output**: `/deploy` skill (helper + SKILL.md + templates + bootstrap), built via subagent-driven-development (4 tasks; fresh implementer + per-task spec+quality review each). +- **method**: per-task review (sonnet; opus on the keystone) + writing-skills pressure-test (fresh agent on a `PENDING.json`+moved-HEAD fixture) + final whole-branch review (opus). +- **anomalies**: (1) the PLAN's code carried 3 latent bugs — missing `git add` for new files, SC2086 unquoted `$viol`, comment-before-shebang SC1128 — all caught by the implementer's TDD+shellcheck gate → plan-code is a DRAFT, the test gate is load-bearing. (2) the final whole-branch review caught 2 Important seam-bugs INVISIBLE to per-task reviews: target-repo `.claude/`-ignored silent no-op ([[LRN-066]]) + `NEXT.sh`-absence non-regeneration → holistic review earns its keep. (3) pressure-test confirmed the cold-resume discipline holds under temptation (the agent excluded the moved-HEAD `0034`). (4) a reviewer subagent bugged out once (user killed it) → re-dispatched clean (transient, not a finding). +- **action**: keep. Multi-stage adversarial review + a behavioral pressure-test caught classes of bug single-pass review misses — worth the cost on a keystone skill. diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 601f94a..6c64428 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -212,3 +212,4 @@ rules: - Doc-sync coupled invariant (twin of BDR-034, BUILT not reordered): new `lib/doc-commit.sh` (inverse-scope surgical, fail-closed exit 4 on `.claude/`) + `lib/doc-commit.md` include; doc-syncer emits `PATCHED_FILES` (one path/line) → agent → distinct argv (space-safe). 2 orchestrators reordered DOC SYNC before FINISH (ship-feature 9→8, init-project 12→10c, GSD 13→12), 3 inline flows wired (feat/bugfix/hotfix). 6 commits `ae1f218` · `4a54a65` · `fb1f359` · `636b491` · `e81f629` · `1b01b95`. 28/28 real-exec, shellcheck clean. BDR-036, LRN-058/059/060, EVAL-008. - Sweep caught PRIOR-chantier debt (README:153 stale since e8eff7e's swap) + expanded scope to 3 inline flows (asymmetry vs memory was decider). Swap flips meanings ≠ letter-insertion (LRN-059). Deferred note "reorder only" refuted in read-phase — doc-syncer commits nothing (LRN-058). BLK-010 (scaffold/unborn HEAD + worktree) + BLK-011 (GSD ROADMAP post-FINISH) deferred = new work. - v2 capitalize Stop-hook REJECTED on facts: `Stop`=per-turn (self-defeat, nags mid-flush, LRN-047), `SessionEnd`=debug-log-only (can't nag) + gate-bypass. Real gap = OUBLI de câblage: `/capitalize`+`/close` never call `capitalize-commit.md` (predate it 7-60d; wiring commits never touched them; commit done by hand 35×, orphans self-heal). Redirect = wire the include (STEP 5B); `/close` alias follows. BDR-037 + LRN-061 (capstone: runtime net for an unwired skill → check wiring first; deterministic gap = fix structurally, non-det aléa = net OK cf BDR-033). Next: câblage + dogfood (5B commits future capitalizations). +- /deploy skill built (subagent-driven, 4 tasks + opus keystone review + pressure-test + final whole-branch review). 5 artifacts (.claude/deploy/), two-moment cold-resume via PENDING.json, atomic learn coupling, new lib/deploy-commit.sh (allowlist .claude/deploy/). Branch feat/deploy-skill (b210e8d..79741e3, kept un-merged). BDR-038 + LRN-062..066 + EVAL-009 capitalized; TODO unchanged. diff --git a/.claude/memory/learnings.md b/.claude/memory/learnings.md index 2691e7c..40faa02 100644 --- a/.claude/memory/learnings.md +++ b/.claude/memory/learnings.md @@ -60,6 +60,11 @@ rules: | LRN-059 | 2026-06-27 | Step-number SWAP flips meanings (sweep refs) ≠ letter-suffix insertion (shifts nothing) | any pipeline renumber | | LRN-060 | 2026-06-27 | Fail-closed guard proven by what it REFUSES (loudly); pass dynamic lists as argv not separator-string | automated scoped-commit / destructive guards | | LRN-061 | 2026-06-27 | Runtime net for an unwired skill → check the wiring first (deterministic gap = fix structurally; non-det aléa = net OK, cf BDR-033) | "build a hook/watcher to catch when X isn't done" | +| LRN-062 | 2026-06-27 | deploy first-run detection = file-existence, never `git describe` | any "first run vs incremental" tool — detect by explicit on-disk marker | +| LRN-063 | 2026-06-27 | delta-since-marker = `git diff --name-only X HEAD` (two endpoints), never rev-list/three-dot | any delta-since-checkpoint over git — explicit two endpoints for tree diff | +| LRN-064 | 2026-06-27 | surgical-commit helper family partitions `.claude/`; new subtree needs own allowlist sibling | adding a committable `.claude/X` subtree | +| LRN-065 | 2026-06-27 | cross-session cold-resume skill = disk-bridge read-first (audit-delta convention) | any "do work → user acts out-of-band → resume later" skill | +| LRN-066 | 2026-06-27 | surgical-commit must fail LOUD on git-ignored target paths (else silent no-op) | any helper relying on `git status --porcelain` to detect changes | --- @@ -742,3 +747,26 @@ rules: - **Context**: deferred "v2 capitalize hook" ([[BDR-037]]). Read-phase killed it before code: git proved skills predate the include (oubli), memory already committed by hand 35×, orphans self-heal via `commit_memory`. The hook would've been disabled within an hour (frequent ignored nag). - **Future application**: any "build a hook/watcher/lint to catch when X isn't done" — first grep whether X is even WIRED at its source. Deterministic/structural gap (missing include/call) → fix structurally; reserve runtime nets for non-deterministic lapses, never to complete a rollout. Classify by determinism BEFORE building. - **Reference**: [[BDR-037]], [[BDR-034]] (rollout this completes), [[BDR-033]] (the GOOD net — contrast). Conditions [[LRN-047]], [[LRN-049]], [[LRN-054]]. + +## LRN-062 — deploy first-run detection = file-existence, never `git describe` +- **pattern**: detect "first deploy / no prior marker" by `[ -f .claude/deploy/STATE.json ]` (deterministic). NEVER `git describe --tags --match 'deploy/*'` — it errors `fatal: No names found, cannot describe anything`, exit 128, when no matching tag exists (verified git 2.53). Oracle = committed `STATE.json` holding `deployed_sha` (external ledger; never infer from context — [[LRN-054]]). +- **context**: deploy skill design. The describe-128 result is only the REASON NOT to use describe — never the detection path. +- **future application**: any "first run vs incremental" tool — detect by an explicit on-disk marker's existence, not by a git query that errors on the empty case. + +## LRN-063 — delta-since-marker = `git diff --name-only HEAD` (two endpoints), never rev-list/three-dot +- **pattern**: "files changed since marker X" = `git diff --name-only HEAD` — two explicit endpoints = literal tree diff. NEVER `git rev-list X..HEAD` (ancestry → phantom deltas after rebase: an orphaned marker yields the whole history). NEVER three-dot `X...HEAD` (merge-base → UNDERCOUNTS on divergence). Verified git 2.53 (linear: all forms agree; diverged: two-dot = both sides, three-dot = one side only). +- **context**: deploy delta mechanism. Footgun: `git diff A..B` ≡ `git diff A B` (two endpoints), but `rev-list A..B` = ancestry — same `..` token, different meaning per command. +- **future application**: any delta-since-checkpoint over git — explicit two endpoints for the tree diff (artifact list); reserve `rev-list` for commit-counting only. + +## LRN-064 — surgical-commit helper family partitions `.claude/`; a new subtree needs its own allowlist sibling +- **pattern**: the surgical-commit helpers each own a `.claude/` partition by OPPOSITE rules — `memory-commit.sh` ALLOWLISTS `.claude/memory`+`.claude/tasks`; `doc-commit.sh` EXCLUDES all `.claude/**` (loud rc 4, BDR-022 — [[LRN-060]], [[BDR-036]]). So committing a NEW `.claude/` subtree (e.g. `.claude/deploy/`) can reuse NEITHER: doc-commit refuses it, memory-commit ignores it. Verified live: real `doc-commit.sh` → rc 4 on `.claude/deploy/PROCEDURE.md`. Solution: mint a sibling (`deploy-commit.sh`) with a TARGET allowlist for the new subtree — guard order = traversal `*..*` reject FIRST, then `.claude/deploy/*` allow, else refuse. Inherit rc 3 unsafe-git, short-hash stdout, changed-paths filter. +- **future application**: adding a committable `.claude/X` subtree → new allowlist sibling, don't bend an existing helper; order the path guard traversal-first. + +## LRN-065 — cross-session cold-resume skill = disk-bridge read-first (audit-delta convention) +- **pattern**: a skill that hands BACK control mid-flow (user acts out-of-band) and RESUMES — possibly in a NEW session, context gone — must carry ALL resume state on disk. A bridge file's PRESENCE = the wait-marker ("in flight, awaiting report"); STEP 0 reads it FIRST and resumes from its captured `{base, target, delta, step_reached}` WITHOUT recomputing (HEAD may have moved during the gap → "current HEAD" is wrong). Convention = audit-delta "the state file is the only memory between runs", extended from run-to-run to a MID-FLOW pause. `client-handover` only pauses in-context (synchronous), NOT cold — deploy is the first cold-resume form. A `runbook_rev` (FULL sha) does double duty: in-flow regenerate trigger + cold-resume staleness check; regenerate the instantiated artifact if ABSENT or stale. Pressure-test confirmed (fresh agent resumed from the bridge, excluded the moved-HEAD temptation). +- **future application**: any "do work → user acts out-of-band → resume later" skill — persist a disk bridge, read it first, never recompute on resume; mark the wait by the bridge's existence, not by conversation context. + +## LRN-066 — surgical-commit must fail LOUD on git-ignored target paths (else silent no-op) +- **pattern**: `git status --porcelain -- ` HIDES git-ignored paths → a surgical-commit helper that filters changes via porcelain SILENTLY no-ops (rc 1) when the target project ignores the path (e.g. `.claude/` wholesale) → the artifact never persists, the skill silently forgets. Fix: guard with `git check-ignore -q ` BEFORE the changed-filter; any passed path ignored → LOUD refusal + dedicated rc (5), never a silent no-op. Fail-closed/loud over silent. (Same porcelain mechanism as the changed-filter — [[LRN-051]].) +- **context**: `deploy-commit.sh`; the FINAL whole-branch review caught it (per-task reviews could not — it is a skill↔target-repo seam). Applies to the whole memory/doc/deploy-commit family. +- **future application**: any helper relying on `git status --porcelain` to detect changes — add a `git check-ignore` guard; a path that must persist but is ignored has to fail loud, not no-op.