Commit Graph
173 Commits
Author SHA1 Message Date
Bastien Chanot 55fad4b7e9 job4: SPEC-02 gitflow-finish-release-fanout
New T13 block in lib/gitflow-test.sh (+9 assertions, 71→80):
T13a release finish → main gets the commit, develop gets it via
merge-back, release branch deleted. T13b two open releases + a
finished hotfix → hotfix commit present in BOTH release branches.
T13c bugfix finish → develop only, main untouched, branch deleted.
Closes J4-02 (CRITICAL): a half-landed release (main-only or
develop-only) or a mis-based bugfix finish was invisible to the
only test suite that exercises gitflow_finish's fan-out.

Mutation (scratch copy, applied via Bash/perl — not Edit/Write, so
config-protection's path-suffix guard on lib/gitflow.sh isn't
tripped for a throwaway file that's never committed): deleted the
develop merge-back line in gitflow_finish's release arm
(gitflow.sh:122-125). RED: T13a fails 3/3 (rc 5 — _gitflow_delete
refuses because develop never got the merge, so the branch isn't
fully merged; develop missing the commit; branch not deleted).
GREEN: real repo unmutated, 80/80 passed (T13a/b/c included).
2026-07-06 18:48:29 +02:00
Bastien Chanot b6d8e79a2d job3: B1 reconcile T2 — hermetic fixture, not the live registry
skills/reconcile/SKILL.md:53 claimed "20/20, shellcheck clean" but the
suite read .claude/memory/blockers.md live, so closing BLK-009
(d1e7423) turned T2b/T2c red for a correct reason unrelated to the
engine. Froze a post-BLK-009 snapshot (lib/tests/fixtures/
blockers-snapshot.md) and pointed T2 at it instead of the live file —
same approach the other T1/T3/T4 fixtures already use. Updated T2b/T2c
expectations to match (BLK-009 resolved, open = {001,003}). Suite is
back to 20/20 GREEN, shellcheck clean, matching the skill's own claim.
2026-07-06 17:35:52 +02:00
Bastien Chanot 898b61c005 job2: F16 remove runtime-dead gitflow-migrate.sh 2026-07-06 12:25:13 +02:00
Bastien Chanot 860b803203 job2: F4 design hook ignores harness notifications; sync test oracle to job1 pointer message (3f639b3 regression) 2026-07-06 12:16:44 +02:00
Bastien Chanot 73c765aa08 feat(install): wire impeccable into the toolchain — deterministic design floor + /impeccable verbs
Complementary to frontend-design (kept: build-time aesthetic direction).
impeccable adds what the chain lacked: 45 deterministic anti-slop rules
(npx impeccable detect, exit 0/2, --json) — the design counterpart of the
semgrep gate — plus 23 design verbs under one /impeccable skill and
persistent per-project design context.

- plugins.lock.json: CLI pinned 3.2.0 (rules update = audit output change
  on unchanged code, LRN-077 class); skill dist = its own release track
- install-plugins.sh Step 8d: staged npx install (tmpdir) -> moved to
  skills-external/impeccable (machine-owned, gitignored, ctx7 pattern);
  never writes through the ~/.claude/skills symlink into the tracked tree
- update-all.sh: pin-honored refresh, Node<24 or failure -> dist kept
- Node >= 24 required (host at 22): steps skip gracefully, activation
  deferred to a deliberate Node bump
- link.sh EXTERNAL_SKILLS, profiles (design/web/web-full/full),
  plugin-advisor, CLAUDE.md design routing, design-gate, README, CHANGELOG
- NOT in design GATE-BLOCK yet: promotion after first dogfood
2026-07-05 14:42:42 +02:00
Bastien ChanotandClaude Opus 4.8 1c69de2f40 feat(skills): wire verify+secure into ship-feature/init-project; onboard no-loop + LRN-093 backstop (verify-loops lot 5)
ship-feature: STEP 0e CONTRACT (request verbatim) → ENRICHED at the STEP 3
validation gate (design criteria appended [gated <date>], the human
micro-gate) → STEP 5 VERIFY+SECURE judges the branch against the ENRICHED
contract via the shared include. Distinct axis from STEP 6 code review, both
run (LRN-095).

init-project: contract seeded from the PROJECT BRIEF (V1 features → criteria)
→ ENRICHED at VALIDATION GATE #1 → STEP 9 VERIFY+SECURE. Adds the security
gate init-project previously lacked (was deferred to a later /onboard).

onboard: explicit NO verify-loop — it produces an audit report, not a change
to verify against a request; contract is scope-only, security-auditor runs
MODE audit (report-only), never a gate. Documented to prevent a misplaced
symmetry loop (BDR-050: dev pipeline != audit).

lib/tests/no-vacuous-locks.test.sh: deterministic backstop for LRN-093 (2nd
recurrence in this chantier → the advisory alone did not hold). Refuses a
literal \n in any grep/tf/tr_/tn pattern across lib/tests/*.test.sh;
flip-tested against a synthetic offender so the guard proves it bites.
lib/tests/loops-heavy.test.sh: 18 structure locks green.

Behavioral dogfood (both vigilance points, real): (1) enrichment — a fresh
verifier reads and checks a [gated] design criterion (ECARTS naming it
precisely); (2) escalation — 3 consecutive ECARTS on the same criterion →
orchestrator STOPs at the max-3 bound + presents the CONTRACT-vs-REALIZED
table, no 4th loop, no commit. First real exercise of the infinite-loop guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-04 04:46:52 +02:00
Bastien ChanotandClaude Opus 4.8 0f0162dcae feat(agents): wire contract + verify + security into feat/bugfix/hotfix (verify-loops lot 4)
lib/verify-secure-loop.md: shared main-loop include. GATE 1 fresh verifier
(blind, contract from disk) → CONFORME straight to GATE 2, ECARTS loop max
3; GATE 2 fresh security-auditor (MODE gate) → PASS to commit, BLOCK loop
max 3 with re-verify-request-FIRST order invariant. Mute agent never a PASS.

feater.md: STEP 0.7 CONTRACT (proportional, silent on a clear feature) +
STEP 3 VERIFY+SECURE via the include. Nominal = one verifier + one security
dispatch; the loop only costs when it loops.

bugfixer.md: STEP 3.5 CONTRACT fed by the DIAGNOSIS (bug report verbatim +
reproduced-then-gone + regression test criteria) + STEP 5 fresh gates via
the include. Renumbered STEP 5 sub-steps (gates before the commit gate).

hotfixer.md: STEP 1.7 CONTRACT (silent autofill, zero questions) + STEP 3
security gate whose FAILURE REVERTS (git restore to pre-flight SHA + escalate
to /bugfix), never loops — the 1-attempt model preserved. No fresh verifier
at hotfix weight (the smoke-check verifies the trivial contract). Adds the
Agent tool to hotfixer.md + hotfix/SKILL.md for the security dispatch.

lib/tests/loops-light.test.sh: 27 structure locks green, shellcheck clean.
Behavioral pipeline dogfood on a fixture (feat adding a feature WITH a SQLi):
GATE1 CONFORME (feature present, SQLi not a conformity gap — orthogonal
gates) → GATE2 BLOCK(1) (checklist caught the %-interp SQLi semgrep's taint
rules missed) → [fix to parameterized] → re-verify CONFORME (order invariant,
feature intact) → re-scan PASS. Loop converges to green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-03 20:47:07 +02:00
Bastien Chanot 5aa4216409 Merge branch 'feature/security-auditor' into feature/verify-loops
# Conflicts:
#	.claude/memory/journal.md
2026-07-03 20:35:07 +02:00
Bastien ChanotandClaude Opus 4.8 2b297bd44a feat(agents): security-auditor SAST gate + onboard/audit-delta grafts (verify-loops lot 3)
agents/security-auditor.md: fresh read-only-on-code SAST gate. Pinned
rulesets p/security-audit + p/secrets + p/owasp-top-ten (owasp REQUIRED —
measured: the 2-ruleset baseline misses SQLi + path-traversal entirely on
realistic Flask code), never --config auto, never auto login (BDR-048).
Severity map: secrets ERROR → CRITICAL, other ERROR → HIGH (block),
WARNING/INFO → reported. gate mode (diff, no Write) vs audit mode (Write
only to REPORT, rule-locked). DEGRADED (semgrep absent) still runs the
checklist and still blocks — never a vacuous pass (LRN-048). Anti-gaming:
a new un-gated nosemgrep suppression is BLOCKING. PROOF mandatory, mute
auditor never a PASS, blind (no iteration history), blocks HIGH/CRITICAL
only (LRN-047).

Grafts: onboard STEP 6 L3a dispatches it in audit mode (report
.onboard-audit/semgrep.md) in BOTH gstack branches — complement to cso
(cso is a gstack submodule, unmodifiable); synthesis picks it up via the
existing .onboard-audit/ sweep. audit-delta security axis runs the SAST
pass first, folds findings into the existing gate/fix/re-verify flow.

lib/tests/security-auditor.test.sh: 28 structure locks green, shellcheck
clean. Behavioral dogfood (fresh agents on a planted fixture):
BLOCK(9) on the vuln commit (2 secrets→CRITICAL, semgrep+checklist
complementarity — checklist caught the 6 semgrep missed off-context);
BLOCK(1) on a new nosemgrep suppression (understood semgrep's 0 was the
mask); DEGRADED → BLOCK(7) on grep-detectable secrets with semgrep hidden.
FP measured on real repos (faunosteo, game): owasp adds only hygiene
findings, contained by diff-scoping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-03 19:13:02 +02:00
Bastien ChanotandClaude Opus 4.8 6aed5eea8c feat(agents): contract interview include + verifier agent (verify-loops lot 2)
lib/contract-interview.md: mandatory upstream passage for all orchestrators.
Verbatim REQUEST (immutable), proportional questions (complete request =
zero, max 3 one batch), testable criteria + file scope, written to disk
immediately (.claude/tasks/contracts/<date>-<slug>-<HHMM>.md — a context-only
contract dies at compaction). Lifecycle: enrichment only at human gates
([gated] marker, scope micro-gate), supersedes for re-scope, aborted runs
deleted or committed status:aborted — never left dirty. Hand-off = path,
not restatement.

agents/verifier.md: fresh read-only verifier. Reads the contract from disk,
renders VERIFY — VERDICT: CONFORME | ECARTS(n) | ERROR. Blind: never
receives iteration history. PROOF line mandatory (LRN-048), UNVERIFIABLE
never MET, mute verifier never a PASS (retry once fresh, 2nd structural
failure = human escalation). Orchestrator protocol documented in-file
(max 3 iterations, re-verify request before security).

lib/tests/contract-verifier.test.sh: 31 deterministic structure locks on
the load-bearing doctrine clauses — green, shellcheck clean.

Behavioral dogfood (2 fresh subagents on a planted fixture): gap case →
ECARTS(2) exactly as planted (NOT-MET located + out-of-scope flagged);
conform case with injected fake iteration history → CONFORME, noise
ignored, real python spot-check as evidence. Both outputs parse-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-03 18:50:28 +02:00
Bastien ChanotandClaude Opus 4.8 3d0ee262c1 fix(hooks): tighten design-toolchain trigger + add fire-log counter
The 07-02 tightening left bare tokens common in non-UI talk (design, component, theme, transition, frontend, palette) -> ~6 false-fires/session during the ECC config audit. Dropped them; dashboard now word-boundary matched (kills the ecc_dashboard.py filename match, keeps 'admin dashboard'); kept animation; added 'front-end design' bigram. Each fire now logs time+token+excerpt to a light file so 're-firing?' is measured, not argued. Regression test 18/18, shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-03 15:52:37 +02:00
Bastien ChanotandClaude Opus 4.8 55347445cc feat(hooks): config-protection PreToolUse guards quality-gate files
Blocks Edit/Write to guardrails (settings.json + .claude/settings*, lib/gitflow.sh, .githooks/*, doctor.sh, hooks/*.sh self-guard, lib/tests/*, lint) so a gate can't be weakened to pass an error. Bypass = one-shot sentinel .claude/.config-edit-ok (non-empty reason, logged+consumed), not an env-var. Adaptation from the ECC second-look (BDR-047 corrob): own bash idiom, not ECC's Node dispatcher. shellcheck clean, test 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-03 15:29:20 +02:00
Bastien ChanotandClaude Opus 4.8 d9fdd4cbdf fix(gitflow): gitflow_finish validates its named branch against HEAD
gitflow_finish ignored its <type> <name> args and always merged the
checked-out branch — `finish bugfix audit-bugs` run from
feature/audit-tokens merged the wrong branch (audit UX trap, 2026-07-02).
Args are now an optional safety ASSERTION: if present and != current
branch, refuse loudly (rc 2) instead of merging the wrong thing. No args
= unchanged (the only real caller, SKILL.md:36, passes none). +7 T12
regression assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
2026-07-03 13:57:59 +02:00
Bastien Chanot c6660c1c5f Merge bugfix/audit-bugs into develop 2026-07-02 14:35:31 +02:00
Bastien ChanotandClaude Fable 5 56bd5ff0c2 feat(tokens): pr-review-toolkit OFF by default — heaviest plugin, PR-only use
Measured: 6 agent descriptions = ~2.2k tokens injected EVERY session
(the single largest plugin contributor) for a toolkit useful only when
reviewing PRs. enabledPlugins → false; removed from full+backend
profiles (BDR-017 caveat already accepts full excluding rarely-used
items); audit.profile KEEPS it = profile reactivation channel.
Per-PR-session: claude plugin enable pr-review-toolkit@claude-code-plugins
(or bash lib/profile.sh apply audit); a later 'profile set full'
re-disables it via the MANAGED_PLUGINS lifecycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zA3Qh2Q1QpcGXzXxKeDHR
2026-07-02 14:17:55 +02:00
Bastien ChanotandClaude Fable 5 17fb6dda43 fix(tests): run-reconcile T6c — oracle pointed at the removed parasite dir
$MEM/../skills resolved to .claude/skills/ (the LRN-042 parasite, removed
2026-06-30 by make plugin Step 8.5), not the real skills/. Green at build
time only because the parasite still existed — green-for-wrong-reason
(LRN-077 class); red ever since. Suite back to 20/20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zA3Qh2Q1QpcGXzXxKeDHR
2026-07-02 14:07:31 +02:00
Bastien ChanotandClaude Fable 5 f0b7e89468 fix(rtk): rtk resolution + absolute-path rewrite — compression was silently dead
rtk lives at ~/.cargo/bin but the hand-managed .bashrc lost the cargo
line: command -v failed in hook AND tool shell, so the hook no-op'd with
a stderr warn on every Bash call — input compression silently OFF.

- Resolve RTK_BIN by probing known install dirs (LRN-036 class).
- Substitute the ABSOLUTE path at the rewrite head: a bare 'rtk …'
  exits 127 in the tool shell, whose PATH the hook cannot fix (proven).
- Compound rewrites carrying further bare rtk segments pass through
  unrewritten: quoted text (commit messages) makes a global substitution
  unsafe — lose compression, never emit a command that 127s (proven:
  a commit chain 127'd mid-flow).
- detect_rtk probes the same dirs so the banner reports capability.
- Re-pinned .rtk-hook.sha256: the rtk BINARY verifies the hook against
  it at execution time and refuses a modified hook — the pin is live
  machinery, not a vestige; coupling documented in the header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zA3Qh2Q1QpcGXzXxKeDHR
2026-07-02 14:07:22 +02:00
Bastien ChanotandClaude Opus 4.8 e8807a7333 feat(gitflow): chore branch type + aiguillage for standalone memory/doc skills
Standalone /capitalize /close /prune-memory /reconcile no longer lean on the .claude/** hook exemption when run on main/develop: the aiguillage branches them to chore/* off develop before writing. New chore type (base develop, finish->develop) added to the lib; hook unchanged (chore/* non-protected). Closes the leak where standalone memory work (memory IS the work, no code branch to follow) landed direct on a protected base. 64/64 gitflow-test green, shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNaYKPEkjH1jbgoX1TwKMX
2026-07-01 13:25:36 +02:00
Bastien ChanotandClaude Opus 4.8 d3d6cede65 feat(release-candidate): orchestrator skill over gitflow release + the version tag
/release-candidate cuts a release by orchestrating the existing gitflow
release mechanic (start from develop; finish fan-out main+develop+delete)
and adding the one piece the lib lacks: the version tag.

- skills/release-candidate/SKILL.md: thin orchestrator — preconditions →
  gitflow start release → prep (version.txt + CHANGELOG, breaking doc'd) →
  run-tests gate → human WHEN-to-release gate → gitflow finish → git tag -a
  vX.Y.Z (in the skill, lib untouched) → push (gated).
- lib/tests/run-release-candidate.sh: throwaway-repo flow replay. RC_TAG=0
  reds the tag (gitflow fans out but never tags); RC_TAG=1 → 5/5.
- CLAUDE.md: Skill routing line. CHANGELOG [Unreleased]: /reconcile +
  /release-candidate under Added (so the eventual v4.0.0 captures them).

Tag scheme vX.Y.Z continues the version.txt/CHANGELOG lineage. writing-skills TDD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
2026-06-30 14:41:12 +02:00
Bastien ChanotandClaude Opus 4.8 82e6322a9f feat(reconcile): deterministic declared-vs-real engine + thin gated skill
/reconcile confronts declarative sources (TODO checkboxes, registry
statuses, ## Index) against real git/fs state and surfaces the gaps,
in 4 categories + contradiction candidates.

- lib/reconcile.sh: engine — body-only enumeration (never the Index),
  git/fs oracles, BLK last-block-wins status, lexical deferral sweep,
  contradiction candidates, pure reconcile_verdict kernel.
- lib/tests/run-reconcile.sh + fixtures (neutral-named): 20/20;
  recursive-coherence T1 reds if the engine reads the Index (teeth).
- skills/reconcile/SKILL.md: thin orchestration + A/B/C write-back gate,
  honest limits (lexical deferrals, contradictions surfaced not asserted).
- CLAUDE.md: Skill routing line.

Founding principle: never trust a declarative source as an oracle — the
skill practices what it preaches (tested). Built via writing-skills TDD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
2026-06-30 13:42:24 +02:00
Bastien ChanotandClaude Opus 4.8 09f5b28d99 feat(doc-sync): MINOR-shape oracle + fail-loud commit (strengthen MINOR gate)
(1) lib/doc-shape.sh — deterministic shape oracle re-checks each LLM-classified
MINOR patch before the silent auto-commit; a shape-suspect patch (added heading
/ oversize / new file / non-doc) escalates to the EXISTING SIGNIFICANT gate.
Genuine MINOR still auto-commits (zero friction, BDR-036 preserved). A structural
floor under the LLM call, not a semantic SIGNIFICANT-detector (BDR-040).

(2) lib/doc-commit.sh — guard the commit itself: a rejected git commit (pre-commit
hook / protected branch / signing) now fails loud with exit 5 + empty stdout,
instead of a false "committed" + the previous HEAD's hash + exit 0 that left docs
silently uncommitted (LRN-071, 3rd occurrence of the swallowed-commit pattern
after LRN-066 and LRN-068/BLK-012).

Wired doc-syncer STEP A4 (whole-set escalation: no=revert all, select=keep subset)
+ doc-commit.md (rc-5 consumer row + ACKNOWLEDGMENTS coherence).
TDD RED->GREEN: run-doc-commit.sh 32/32, run-doc-shape.sh 19/19, behavioral C/D.
shellcheck clean. Branch-guard (3) deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
2026-06-29 17:41:24 +02:00
Bastien Chanot 167ea9678e feat(gitflow): universal gitflow model — lib + skill + orchestrator wiring
lib core (start/finish/init, transactional bootstrap) + migrate + 57-test suite + aiguillage; skills/gitflow + gitignore template; CLAUDE.md gitflow rule; wiring init-project (5f/8/11), onboard (2.6), ship-feature (0/4/9), feat/bugfix/hotfix aiguillage.
2026-06-29 02:58:13 +02:00
Bastien ChanotandClaude Opus 4.8 79741e36e7 fix(deploy): final-review fixes — NEXT.sh-absence regen, git-ignored fail-loud (rc5), bootstrap gitignore guard, doc polish
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 18:11:14 +02:00
Bastien ChanotandClaude Opus 4.8 0ed074f4bb fix(deploy): helper polish — pending allowlist, pipefail, no-op guard, --git-dir
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 17:51:39 +02:00
Bastien ChanotandClaude Opus 4.8 24e6b84add feat(deploy): deploy-commit.sh — allowlist surgical commit for .claude/deploy/
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 16:55:39 +02:00
Bastien ChanotandClaude Opus 4.8 4af339a312 test(lib): doc-commit behavioral check — coupled + fail-closed scenarios
lib/tests/run-doc-behavioral.md — in-vivo whole-chain check (twin of run-behavioral.md for memory). Scenario A: doc-syncer patches a public doc, the include commits it surgically with dangling code present (coupled + surgical). Scenario B: a forbidden .claude/ path in PATCHED_FILES → helper refuses (rc 4), nothing half-committed, offender named (fail-closed + loud). Complements the 28-assertion deterministic suite (run-doc-commit.sh).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 03:22:22 +02:00
Bastien ChanotandClaude Opus 4.8 fb1f359da5 feat(doc-syncer): PATCHED_FILES newline output for doc-commit handoff
doc-syncer now emits PATCHED_FILES — every public-doc file created/modified this run, ONE PATH PER LINE — in both STEP 9 OUTPUT (full audit) and AUTO MODE STEP A4 (the path orchestrators call). NONE stays silent (no line → doc-commit sees empty → no-ops). Additive: detection/patching logic and the `auto-mode scope:` input contract are unchanged → callers unaffected.

Separator contract, producer↔consumer aligned + proven: newline is doc-syncer's OUTPUT format (paths carry no newlines); the agent splits on newline and passes EACH path as a SEPARATE argv element to lib/doc-commit.sh. The helper takes argv (no in-band separator) → a path with spaces survives as one argument. lib/doc-commit.md spells this out (never flatten to a space-joined string + re-split, which would mis-split a spaced path the helper then silently drops). New test T7 PROVES it on real git: 'docs/My Guide.md' → committed as one file (28/28, shellcheck clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 01:07:03 +02:00
Bastien ChanotandClaude Opus 4.8 4a54a65dd4 chore(lib): doc-commit include protocol
lib/doc-commit.md — TAIL of the doc-sync step, twin of capitalize-commit.md. Tells an orchestrator how to commit ONLY the docs doc-syncer patched (PATCHED_FILES), surgically, BEFORE FINISH so they reach the merge/PR.

Centerpiece: a report-by-(rc, hash) table covering ALL four exits — rc 0 visible surface (files + agent-composed change summary, NOT a bare count), rc 0/empty no-op, rc 3 unsafe-state manual fallback, and rc 4 SCOPE VIOLATION relayed LOUDLY as an upstream BDR-022 anomaly (name the offender, do not swallow or hand-commit the rest — the refusal IS the alarm).

Attribution locked in 3 places: the rc 0 <one-line> summary comes from the AGENT (in-thread patch context), the helper returns only the hash — otherwise the surface degenerates to the bare count that the gate-replacement was meant to avoid. Two conscious acknowledgments inline: MINOR non-gated-by-choice (surface replaces gate); partial init-project fix linking BLK-010.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 00:51:42 +02:00
Bastien ChanotandClaude Opus 4.8 ae1f218d3e feat(lib): surgical doc-commit helper + real-exec scope/exclusion tests
New lib/doc-commit.sh: surgical commit of ONLY the public-doc files doc-sync patched (passed as args), twin of memory-commit.sh with INVERSE scope. Δ1 dynamic pathspec filtered to changed paths (LRN-051); Δ2 fail-closed + LOUD scope guard rejecting .claude/** and CLAUDE.md (BDR-022) with a dedicated exit 4; Δ3 no hash anchoring (LRN-052); Δ4 `docs:` message. Hash-only on stdout for `doc_hash=$(...)` capture.

lib/tests/run-doc-commit.sh: 24 assertions, all REALLY EXECUTED on real git fixtures (no presumed behavior). T1a/b/c prove the guard CATCHES — forbidden path alone → exit 4, mixed legit+forbidden → refuse-all (nothing half-committed, offender named); T2 dynamic pathspec no-match filter; T3/T4 dangling + stale-index safety; T5/T6 idempotent + unsafe-state skip. shellcheck clean (both).

Part of the doc-sync coupled chantier (twin of BDR-034). Include + 2 orchestrator reorders follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
2026-06-27 00:38:18 +02:00
Bastien ChanotandClaude Opus 4.8 67c6a8165d 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
2026-06-26 23:46:41 +02:00
Bastien ChanotandClaude Opus 4.8 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 ChanotandClaude Opus 4.8 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 ChanotandClaude Opus 4.8 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 ChanotandClaude Opus 4.8 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
Bastien ChanotandClaude Opus 4.8 11792ccb33 feat(design-gate): §4 anim-lib suggestion — suggest-only, non-blocking, stateless 1-line
Add §4 to lib/design-gate.md: when a non-trivial design task hits a motion
signal (animation/transition/hover/motion/animate, now in §DETECTION) and the
stack is motion-eligible with no anim lib installed, surface a single-line
suggestion for the recommended `motion` package. Suggest-only (install on
explicit consent), non-blocking, stateless (always one line, no marker; calls
the helper, no 3rd copy of the lib list).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-26 00:11:01 +02:00
Bastien ChanotandClaude Opus 4.8 e5e673ac1f refactor(skill): rename validate → web-validate
Clearer scoped name for the W3C + WCAG skill. Updated: folder (git mv),
frontmatter name, H1 title, command refs, CLAUDE.md routing, 6 profiles
(functional — activate the skill by folder name), cross-refs in
harden/seo/depth-matrix/client-handover, agent dispatch refs, README +
USAGE tables.

Confidentiality: the client-deliverable leak-guard regex
(client-handover-writer.md) now matches BOTH /web-validate and legacy
/validate, so older client docs stay covered.

Left intentionally: validator-analyzer agent name (lockstep with
subagent_type + registry), .validate-cache/ + VALIDATE.md (audit-file
family {SEO,GEO,HARDEN,CSO,VALIDATE}.md), .claude/ history (append-only),
CHANGELOG old entry (added a new "renamed" entry instead). NL trigger
keywords kept so "validate" still routes here. Third-party html-validate
untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
2026-06-25 01:29:36 +02:00
Bastien ChanotandClaude Opus 4.8 0b92935d6d feat(profile): activate gstack skills on-demand per profile
gstack stays OFF by default (no per-skill symlink in skills/, zero context
cost). enable_skill now gains a gstack branch: a skill absent from skills/
and skills-disabled/ but present in the skills-external/gstack submodule is
symlinked in on demand when a profile lists it; disable_gstack_not_in()
parks it again on an unrelated profile.

This makes `set full` (which lists 35 gstack skills) work without 35 bogus
"missing — try: bash link.sh" warnings, without abandoning the OFF-by-default
policy. The old remedy message was wrong (link.sh never creates gstack
skills) and is replaced with submodule-aware messages.

Refs BDR-030.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169vjUD1sP9Nx4ZiCa8wvAw
2026-06-24 14:22:36 +02:00
Bastien ChanotandClaude Opus 4.8 f96331844c fix(design-gate): resolve claude on a sanitized PATH (real cause of unknown)
The "unknown -> exit 11" path triggers when `command -v claude` fails. Root
cause is NOT the interactive alias (claude->dtach_claude) not surviving the
subshell — that's true but harmless: the real binary is on the inherited PATH,
so `command -v` finds it in a normal `bash script.sh` (proven: toggle-external
and the gate both resolve claude). The actual lever is PATH carrying the nvm
node bin. A skill/hook that shells the gate out with a sanitized PATH, or a node
upgrade moving the version-pinned nvm path, loses it.

ensure_claude_on_path(): if `command -v claude` already resolves, do nothing;
else probe known install dirs (~/.claude/local, ~/.local/bin, /usr/local/bin)
and the nvm glob, prepending the bin dir — which carries BOTH claude and its
node runtime (claude's shebang needs node, same dir). nvm keeps old versions
after an upgrade, so pick the newest that ships claude via sort -V, not the
first glob match. If nothing resolves, command -v still fails -> unknown ->
exit 11 (fail-visible net stays).

Verified: shellcheck clean; normal PATH -> READY exit 0 (function returns early,
no regression); PATH=/usr/bin:/bin (sanitized hook) -> now resolves claude via
the nvm glob and reports REAL magic state (READY exit 0), where before the fix
it was exit 11 unknown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:12:49 +02:00
Bastien ChanotandClaude Opus 4.8 4d191354c6 fix(design-gate): fail-visible on unverified tools (no silent READY)
When `claude` is unreachable, the plugin/MCP checks (magic, ui-ux-pro-max —
the most important design tools) return `unknown`. The old verdict folded that
into a silent `READY` exit 0 — a fail-OPEN: the gate granted "proceed" exactly
when it had verified nothing. Contradicts the fail-closed-on-uncertainty rule.

Now fail-VISIBLE (not strict fail-closed — claude can be merely slow, false
blocks would get the gate ignored): a third outcome READY BUT UNVERIFIED with a
distinct exit 11. It proceeds, but says so loudly and names the unchecked tools,
telling the user to confirm with `claude mcp list` / `claude plugin list`. The
distinct non-zero code also stops a naive `if gate; then proceed` shell caller
from silently passing. Also covers the non-reproducible "transient claude absent
after apply" flake seen in live testing.

design-gate.md §DECISION updated: exit-code line + a real branch-3 state for 11.

Verified: shellcheck clean; reachable+active -> READY exit 0; claude off PATH ->
READY BUT UNVERIFIED exit 11 naming magic+ui-ux-pro-max; magic off -> INCOMPLETE
exit 10 (trip branch intact after the restructure).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:57:48 +02:00
Bastien ChanotandClaude Opus 4.8 131d0bcb5d feat(secrets): .env source-of-truth in ~/.claude + repo symlink
Move the real secret out of the git tree: the key lives in ~/.claude/.env
(outside the repo), and link.sh symlinks repo/.env -> ~/.claude/.env so
`source "$REPO/.env"` resolves transparently. The secret never enters git —
not as content (it's a link) and not by accident (gitignored).

link.sh: add link_env() — verify ~/.claude/.env exists + has MAGIC_API_KEY
(warn, never create/copy the secret), then create repo/.env -> ~/.claude/.env.
Defensive + idempotent: links only when repo/.env is absent or already the
right symlink; a residual REAL repo/.env is left untouched with a migrate hint
(never clobbered, so the secret can't be destroyed).

.gitignore: harden .env -> .env + .env.* + !.env.example (covers .env.local,
.env.bak, .env.save; keeps the template tracked).

Messages point at ~/.claude/.env (the canonical edit location) instead of the
ambiguous $REPO/.env: design-tool-gate.sh gate output, design-gate.md
(branch 3 + IMPORTANT), toggle-external.sh, install-plugins.sh.

Verified: shellcheck clean (link.sh, toggle-external.sh, design-tool-gate.sh);
link.sh created the symlink (1 change, idempotent re-run); repo/.env absent
from git status; magic-off path still exits 10 with the ~/.claude/.env hint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:44:47 +02:00
Bastien ChanotandClaude Opus 4.8 3eefb8ad7c feat(design-gate): profile-based toolchain gate + design-tool-gate.sh
design-tool-gate.sh: deterministic design-toolchain state check. Reads the
design-core tools from design.profile's `# GATE-BLOCK:` allowlist + their
types via `profile.sh show design --plain` (claude-free parse contract),
checks each on its own channel (skill symlink / claude plugin list / claude
mcp list / command -v). Never reads disabledMcpServers. Exit 0 ready · 10
incomplete · 2 error.

Remedy is always a profile (/profile design), never an atomic tool toggle —
the profile system stays the single source of truth for activation. magic is
required-but-manual: it TRIPS the gate (not advisory) and the output names
the MAGIC_API_KEY step. Non-design tools bundled in the profile (browse,
plan-*, design-shotgun, graphify) are excluded from the trip via GATE-BLOCK,
so the gate fires only on real design tools.

design-gate.md: §DECISION rewritten profile-based (tier → run script → branch
on 3 groups), replacing the old atomic "ask user to activate ui-ux-pro-max".
§DETECTION unchanged. design.profile: add the `# GATE-BLOCK:` allowlist
(8 design-core tools); it is a comment, so read_profile/--plain are unaffected.

Verified: shellcheck clean; magic-off (real design profile) → exit 10 +
API-key line; all active → exit 0; browse-off (non-GATE-BLOCK) → exit 0,
no trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:21:48 +02:00
Bastien ChanotandClaude Opus 4.8 5776195eb6 feat(profile): list counters + grouped show + --plain parse mode
list: ITEMS column with compact per-category counts (e.g. 12s·1p·1m·1c),
left block aligned; full descriptions kept (CLI output, not 80-strict).
show: grouped by type (gstack/external/personal/plugin/mcp/cli) with
status; redundant per-line TYPE column and plugin marketplace suffix dropped.
show --plain: parsable "type<TAB>name" list, no status, zero claude calls —
the contract for the upcoming design gate. All derived from .profile files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3e8LaH2vymmxyh36h3jFU
2026-06-19 19:59:19 +02:00
Bastien ChanotandClaude Opus 4.8 d4a5cfec93 chore(caveman): purge plugin + always-on integration
Disable + uninstall caveman@caveman and delete every repo dependency on
it: SessionStart/UserPromptSubmit hook blocks, standalone hook files,
settings.json enabledPlugins + marketplace entries, install-plugins.sh
STEP 5.5, update-all.sh refresh step, plugins.lock.json entry, doctor.sh
checks, lib/detect-plugins.sh helpers, lib/profile.sh + plugin-advisor +
skills/profile protected-list entries, .gitignore runtime-file block,
and README/USAGE docs. Dead /caveman:compress refs replaced with
manual/claude.ai guidance. Memory-registry terse-format convention kept
(separate subsystem). Version 3.4.0 -> 3.5.0.

On a subscription plan caveman's ~75% output-token compression has no
cost benefit, and the always-on hooks added friction on validation
gates and client deliverables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3e8LaH2vymmxyh36h3jFU
2026-06-19 19:08:40 +02:00
Bastien ChanotandClaude Opus 4.8 f928a53ed9 fix(gstack): link spec skill, complete gitignore allowlist (BLK-007)
A gstack submodule bump added `spec` (v1.47) and the iOS device-farm
5-skill set (v1.43) but gstack `./setup` was never re-run, leaving all 6
as source-only — unlinked, invisible to Claude and untouched by
reset/`gstack on`.

  - Link `spec` only, surgically: skills/spec/SKILL.md symlink matching
    gstack setup's per-skill pattern (real dir + absolute SKILL.md link).
    Platform-agnostic, useful planning skill. Added to full + web-full
    profiles (must be in the active `full` profile or `set full` would
    re-disable it).
  - iOS skills deliberately NOT linked: this is a Linux host, and the iOS
    device-farm needs a Mac daemon + Tailscale + physical iOS devices.
    Linking them would add 5 dead skills plus passive token cost.
  - Complete the .gitignore gstack allowlist: add the 12 entries missing
    from it — `spec`, the 5 `ios-*` (so they stay ignored if a future
    setup on a Mac materializes them), and 6 already-parked skills
    (document-generate, landing-report, scrape, setup-gbrain, skillify,
    sync-gbrain). The parked ones matter because `gstack on` moves parked
    skills into skills/, where any allowlist gap becomes untracked git
    noise. Also drop the stale `skills/checkpoint` entry (renamed to
    context-save).

Verified: `profile show full`/`web-full` report spec enabled; the
source-vs-allowlist drift check is now empty; profile.sh still parses.

The gstack submodule pointer bump itself is left uncommitted (separate
pending work).

Memory: BLK-007 -> resolved; LRN-025 (gitignore allowlist must cover all
toggleable skills incl. parked, else enabling one = git noise).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:41:43 +02:00
Bastien ChanotandClaude Opus 4.8 da4e6b9590 feat(profile): add gstack on|off verb to lib/profile.sh
Centralize gstack toggling in the `profile` command without losing the
active-profile label.

  - `gstack on`  re-enables ALL parked gstack skills (moves
    skills-disabled/gstack__* back) but does NOT touch .active-profile,
    so the user layers full gstack on top of their current profile and
    the statusline label is preserved. Unlike `reset`, which clears the
    label to "none".
  - `gstack off` disables gstack skills not listed in the active profile;
    errors cleanly when no profile is active (needs one to know what to
    keep).

Refactor (behavior-preserving): extract three shared helpers
`enable_all_gstack`, `disable_gstack_not_in`, `parked_gstack_count` and
rewire `cmd_reset` + `cmd_set` to reuse them instead of duplicating the
symlink-toggle loops. Wire `gstack` into main() dispatch, usage(), and the
header usage block.

Docs: SKILL.md argument-hint, examples, and output-policy updated. The
generic `make profile cmd="gstack on"` target already covers Make usage.

Verified: shellcheck CLEAN, `bash -n` OK, 6-case test (help, bad-action,
off-with-no-profile, on, off-trim, on-cycle) with final assertion that the
live symlink state was restored exactly to its pre-test value.

Memory: capitalize BDR-018 (decision), LRN-024 (DRY helper-extraction
pattern), BLK-007 (6 gstack source skills ios-*/spec unlinked post
submodule bump — open follow-up), EVAL-002 (self-eval, false "full.profile
bug" flag corrected pre-edit). Backfill index drift: BDR-017, BLK-005/006.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:31:48 +02:00
Bastien ChanotandClaude Opus 4.6 c44852e665 feat(skills): add pdf-translate — PDF to translated HTML via Vision
OCR/image-based PDF pipeline: convert pages to PNGs, read with Claude
Vision (bypasses unreliable OCR text layer), translate with cross-page
glossary consistency, reconstruct faithful HTML via /design-html.

5 steps: deps check → page images + assets → style analysis →
page-by-page read+translate → HTML reconstruction → visual QA.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 03:38:16 +02:00
Bastien ChanotandClaude Opus 4.6 469c807c10 feat(skills): add design-motion-principles from kylezantos
Motion/animation design skill with 3-designer lens (Emil Kowalski,
Jakub Krehel, Jhey Tompkins). Two modes: Create and Audit.
Complements frontend-design (broad) with deep motion expertise.

Integration points:
- skills-external/design-motion-principles/ — skill files + references
- link.sh EXTERNAL_SKILLS — symlink to ~/.claude/skills/
- install-plugins.sh step 8c — presence check
- update-all.sh step 7.2 — sync from GitHub
- profiles: design, full, web, web-full — listed as external
- plugin-advisor — decision table, recommended sets, conditional rules
- design-gate — symlink check + non-blocking warning if missing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 22:40:23 +02:00
Bastien ChanotandClaude Opus 4.6 237810396c chore(plugins): add frontend-design to plugin-advisor and design-gate
frontend-design (Anthropic's anti-AI-slop skill) was installed but not
referenced in the plugin-advisor decision table, recommended sets, or
conditional rules — nor in the design-gate detection logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:59:19 +02:00
Bastien ChanotandClaude Opus 4.6 057a5b7f5c feat(skills): add frontend-design from anthropic-agent-skills
Source: anthropic-agent-skills plugin cache → skills-external/frontend-design/
Auto-install via install-plugins.sh step 8b, auto-update via update-all.sh
step 7.1, symlinked by link.sh (refactored to loop over EXTERNAL_SKILLS).
Added to profiles: design, web, web-full, full.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 00:58:30 +02:00
Bastien ChanotandClaude cb1e8cd814 feat(profile,statusline): show active profile in statusline via .active-profile cache
`bash lib/profile.sh current` is 12s+ — far too slow to call from the
statusline hook (runs on every keystroke). Add a one-line cache file at
`<repo>/.active-profile`, written by `cmd_apply` and `cmd_reset`. The
statusline reads the file directly with a single `head -n1`, no
sub-shell into `profile.sh`.

Behavior:
- `bash lib/profile.sh set <name>` (which ends in `cmd_apply`) and
  `bash lib/profile.sh apply <name>` both write `<name>` to
  `<repo>/.active-profile`.
- `bash lib/profile.sh reset` writes the literal `none`.
- Statusline inserts the cached profile name between the plan segment
  and the context-bar segment, e.g.
  `Opus 4.7 | claude (master) | Max | full | ████░░░░░░ 42% | 3m`.
- Missing or empty cache → statusline shows `?`.

Cache file is gitignored — it tracks runtime state, not source.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-21 05:14:12 +02:00