Commit Graph
90 Commits
Author SHA1 Message Date
Bastien Chanot 3c796ade9c job5: J5-04 delete detect_security_guidance (born dead at 45c3507 re-add) 2026-07-07 00:13:07 +02:00
Bastien Chanot 91c7dccdfb job4: SPEC-12 deploy-commit exit taxonomy
lib/deploy-commit.sh: a rejected `git commit` (pre-commit hook,
protected branch, signing failure) now exits 6 (loud stderr, distinct
from rc 1's "nothing to do") instead of sharing rc 1 with the no-op
cases. Header comment documents the full 0/1/2/3/4/5/6 taxonomy.
Closes J4-22 (UNTESTABLE): at client repos, a failed deploy-state
commit was indistinguishable BY EXIT CODE from "nothing to do" (rc 1
was shared 3 ways); exit-code-only callers couldn't disambiguate
(stderr-parsing callers already could).

Caller census (per report's explicit gate): skills/deploy/SKILL.md
documents and parses this exit-code contract in TWO places (bootstrap
commit + incident-recovery commit). Flagged to the user before
committing; confirmed GO to add rc 6 there too (additive — no existing
code's meaning changes) so the documented contract stays accurate for
live deploy runs.

New T10 in lib/tests/deploy-commit.test.sh (+3 assertions, 13→16):
rejecting pre-commit hook sandbox — asserts rc 6, empty stdout (no
stale hash), HEAD unmoved.

GREEN: full `make test` exit 0 (deploy-commit 16/16 incl. T10).
shellcheck clean, bash -n clean.
2026-07-06 21:55:25 +02:00
Bastien Chanot 999c7c475e job4: install guard fail-closed
install-plugins.sh: mktemp failure building CFG_SNAPSHOT now aborts
the install loudly (err + exit 1) instead of warning and continuing
UNGUARDED — a failed guard used to mean CLAUDE.md/.claude/settings.json/
settings.json could be silently rewritten by graphify's installer for
the rest of that run. Closes §3.4.

Added T5 to lib/tests/curated-config-guard.test.sh: extracts the
WIDER header block (GUARDED_CONFIGS through the closing `fi` — the
fail-closed logic lives in the top-level if/else, outside
restore_curated_configs(), so it needs its own awk range) in a
subshell with a stubbed `mktemp` forced to fail; asserts exit 1 and a
loud "mktemp failed" message. +2 assertions (4→6).

Verified: bash -n clean, shellcheck clean (both files), full `make
test` exit 0.
2026-07-06 21:52:10 +02:00
Bastien Chanot 5511c51a8e job4: seam design-tool-gate.sh
Env-var-only seams (§3.2), zero logic change (diff is only the seam
lines): PROFILE_SH gains DESIGN_GATE_PROFILE_SH override; CLAUDE_BIN
replaces the 5 bare `claude` invocation sites (ensure_claude_on_path's
reachability check + the plugin/mcp branches of tool_active — 2x
`command -v claude` + `claude plugin list` + `claude mcp list`). The
`echo "... claude mcp list  claude plugin list"` hint in the
READY-BUT-UNVERIFIED message stays literal (advisory text for a human,
not an invocation).

Unlocks (BACKLOG, not built here): a hermetic gate suite via
DESIGN_GATE_PROFILE_SH + CLAUDE_BIN injection, promoting J4-21.

Verified: bash -n clean, shellcheck clean, `design-tool-gate.sh design`
against the real repo unchanged ("READY"), full `make test` exit 0.
2026-07-06 21:46:19 +02:00
Bastien Chanot 04da103ed6 job4: seam profile.sh
Env-var-only seams (§3.2), zero logic change (diff is only the seam
lines): REPO gains PROFILE_REPO_OVERRIDE (cd -P already correct, no
bugfix needed here — only toggle-external.sh had the logical-cd bug);
CLAUDE_BIN="${CLAUDE_BIN:-claude}" replaces the 8 bare `claude`
invocation sites (4 `command -v claude` checks + `claude plugin list`/
`plugin enable`/`plugin disable`/`mcp list`). The advisory `info "..."`
hint strings that tell a HUMAN what to type stay literal "claude" —
those aren't invocations.

Unlocks (BACKLOG, not built in this job): a hermetic profile.sh suite
via HOME/REPO/CLAUDE_BIN injection, promoting J4-19 from UNTESTABLE.

Verified: bash -n clean, shellcheck clean, `profile.sh current`/`list`
behaviorally unchanged against the real repo, full `make test` exit 0.
2026-07-06 19:39:19 +02:00
Bastien Chanot 1da906aef6 job4: fix toggle-external logical cd (BLK-006 class)
REPO resolution changed from a logical `cd` to `cd -P` (BLK-006 class:
direct invocation via a symlinked path — the real ~/.claude/lib ->
<repo>/lib layout — was resolving REPO to the symlink's logical parent
instead of the physical repo root). Combined with the REPO seam
(§3.2): TOGGLE_EXTERNAL_REPO_OVERRIDE env var, same pattern as the
other SEAMS-bundle files, zero other logic change (diff is one line).

Closes J4-20 (UNTESTABLE + latent bug). Previous commit ("test
toggle-external logical cd (red)") proved the bug live via the new
lib/tests/toggle-external-repo-resolution.test.sh: `status
emil-design-eng` through a symlinked path reported "missing" instead
of "enabled". This commit turns that test green.

Verified: shellcheck clean, bash -n clean, `bash lib/toggle-external.sh
list` against the real repo unchanged (gstack/emil-design-eng/
darwin-skill/magic enabled, find-skills missing — matches prior state).
GREEN: full `make test` exit 0, including the new test (1/1).
2026-07-06 19:37:41 +02:00
Bastien Chanot 7490b4d571 job4: test toggle-external logical cd (red)
New lib/tests/toggle-external-repo-resolution.test.sh: sandbox
replicating the real ~/.claude/lib -> <repo>/lib symlink layout,
invokes toggle-external.sh THROUGH the symlinked path and asks
`status emil-design-eng` (marked enabled in the physical repo tree).
Demonstrates J4-20 (UNTESTABLE + latent bug) against the CURRENT code,
on purpose — this commit is RED: reports "missing" instead of
"enabled", because toggle-external.sh:34's logical `cd` (no -P)
resolves REPO to the symlink's logical parent instead of the physical
repo root, so SKILLS_DIR/DISABLED_DIR point at the wrong tree. Same
BLK-006 bug class as profile.sh's historical breaks, un-ported here —
latent today (no in-repo caller hits direct `~/.claude/lib/...`
invocation), but reachable.

This intentionally reds `make test`. Next commit fixes it.
2026-07-06 19:36:56 +02:00
Bastien Chanot aae8cd68f6 job4: fix memory-commit fail-loud
Ports doc-commit.sh:123-138's fail-loud pattern verbatim-adapted.
`commit_memory` now checks `git commit`'s own exit status: on
rejection (pre-commit hook, protected branch, signing failure, …) it
returns 5 (mirrors doc-commit's rc 5 — memory-commit's 0/2/3 were
already taken) with a loud stderr message and NOTHING on stdout,
instead of falling through to `git rev-parse --short HEAD` and
silently reporting the previous (stale) commit as if it were new.

Closes J4-04 (UNTESTABLE, consequence CRITICAL). Previous commit
("test memory-commit masked failure (red)") proved the bug live: rc=0,
stale hash leaked on stdout, on the CURRENT code. This commit turns
that same T8 green: rc=5, empty stdout, HEAD unmoved.

GREEN: full `make test` exit 0 — 90(gitflow)/16(deterministic, T8
included)/32(doc-commit)/23(doc-shape)/25(reconcile)/5(release) +
24/4/13/20 (*.test.sh), shellcheck clean.
2026-07-06 19:34:31 +02:00
Bastien Chanot 7c9709802d job4: test memory-commit masked failure (red)
New T8 in lib/tests/run-deterministic.sh: pre-commit hook that always
rejects (exit 1), then attempts a memory-commit. Demonstrates J4-04
(UNTESTABLE, consequence CRITICAL) against the CURRENT code, on
purpose — this commit is RED: rc=0 (expected 5), stdout leaks the
stale (unchanged) HEAD hash instead of staying empty. `set -uo
pipefail` (no -e) means a rejected `git commit` doesn't stop the
function — it falls through to `git rev-parse --short HEAD`, which
prints the PREVIOUS HEAD and succeeds, so the caller sees what looks
like a valid hash for a commit that never happened. HEAD itself is
correctly unmoved (git did block it) — only the reporting is masked.

This intentionally reds `make test` (memory-commit.sh not yet fixed).
Next commit fixes it.
2026-07-06 19:33:48 +02:00
Bastien Chanot 5e19419981 job4: SPEC-06 config-protection-payload-matrix
New T18-T20 in lib/tests/config-protection.test.sh (+4 assertions,
20→24). T18 Write payload, T19 MultiEdit payload → both exit 2 (pass
trivially today — the extraction is tool-name-agnostic — but lock
against a future narrowing to Edit-only; stated honestly, per report).
T20 sentinel containing ONLY whitespace bytes (" \n\t", not literally
empty) → exit 2 AND consumed — exercises config-protection.sh:44-46's
`grep -q '[^[:space:]]'` check specifically, which the pre-existing
T17 (zero-byte file) doesn't reach. Closes J4-07 (WEAK): every payload
in this suite said "Edit", so a future Edit-only narrowing (or a
weaker sentinel-emptiness check) would have failed open with no red.

DOUBLY GATED per report §3.5 (edits config-protection's own test) +
user's stated exception (STOP and show the exact draft before writing,
even though the formal AUTHORIZATION line said AUTHORIZED) — drafted
inline, user confirmed "proceed as drafted" before the sentinel/edit.

Mutations (lean scratch copy — only hooks/config-protection.sh + this
test file, not the whole repo/.git), one at a time, each reverted
before the next:
- T18/T19: gated the file_path extraction on `tool_name == "Edit"`
  (python3 tool_name check + if/else) → both red alone, everything
  else (incl. T1-T17) unaffected.
- T20: swapped the whitespace-aware `grep -q '[^[:space:]]'` for
  `[ -n "$reason" ]` (byte-count only) → T20 reds alone; T17 (the
  zero-byte case) stays green either way, confirming T20 tests
  something T17 structurally cannot.
GREEN: real repo unmutated, 24/24 passed, shellcheck clean.
2026-07-06 19:32:02 +02:00
Bastien Chanot f033defa9d job4: SPEC-07 doc-shape-removed-envelope
New S11-S13 in lib/tests/run-doc-shape.sh (+4 assertions, 19→23) +
truncate_last_n() helper (removes exactly N lines from the END of a
committed file — pure removal, 0 added lines, no heading, so the
ADDED-envelope and heading checks at doc-shape.sh:70/78 can't fire
first). Baseline = 40 plain committed lines, then truncated. S11
remove exactly 20 (== default DOC_SHAPE_MAX_REMOVED, `-gt` boundary)
→ within (0). S12 remove 30 → exceeds (1), stderr names the path. S13
DOC_SHAPE_MAX_REMOVED=5 override + 6-line removal → exceeds (1).
Closes J4-08 (WEAK): the REMOVED branch was never driven over
threshold by any existing case (S4 only removes 2 lines) — a
regression here mislabels a large doc deletion MINOR and doc-syncer's
auto-commit flow would swallow it silently (the exact RISK-1 BDR-040's
oracle exists for).

Mutation (lean scratch copy — only doc-shape.sh + run-doc-shape.sh,
not the whole repo/.git): changed `-gt "$DOC_SHAPE_MAX_REMOVED"` to
`-gt 2000` (doc-shape.sh:82). RED: S12 fails both assertions (30
removed no longer exceeds) and S13 fails (the hardcoded literal also
kills the env-override contract — DOC_SHAPE_MAX_REMOVED=5 no longer
has any effect). S11 stays green (20 removed was always within,
mutation-invariant). 3/3 reds land exactly where expected.
GREEN: real repo unmutated, 23/23 passed, shellcheck clean.
2026-07-06 19:22:52 +02:00
Bastien Chanot 42fc2e6acb job4: SPEC-03 curated-config-guard
New lib/tests/curated-config-guard.test.sh (+4 assertions). Extracts
restore_curated_configs() from install-plugins.sh AT TEST RUNTIME via
awk '/^restore_curated_configs\(\) \{/,\/^\}/' (verified single-
occurrence, column-0 closing brace) so drift in the real script
propagates into the test instead of testing a frozen copy. Harness
defines GUARDED_CONFIGS/CFG_SNAPSHOT/REPO/info() itself (the array
literal at install-plugins.sh:41 is outside the extracted range).
Sandbox REPO with the 3 fake guarded files + a pre-populated
CFG_SNAPSHOT; mutates CLAUDE.md only (simulated installer drift);
asserts: mutated file restored byte-identical (cmp -s), the other two
guarded files' content unchanged (not touched by the restore loop),
snapshot dir removed. Closes J4-03 (CRITICAL): the guard against
graphify's installer clobbering CLAUDE.md/settings.json had zero test
coverage.

Mutation (copy of install-plugins.sh, lean scratch — only that one
file, not the whole repo/.git): inverted the cmp condition
(`! cmp -s` → `cmp -s`) at the line the report names. RED: T1 fails
(the mutated file no longer gets restored — the inverted condition
only copies when already identical, a no-op, and skips restoration
exactly when it's needed). T2/T3/T4 stay green, confirming the
mutation is localized to the restore path.
GREEN: real repo unmutated, PASS=4 FAIL=0, shellcheck clean.
2026-07-06 19:20:25 +02:00
Bastien Chanot 9b1fb92d89 job4: SPEC-09 retire-real-state-snapshot
Deleted T4e + its coupled echo note in lib/tests/run-reconcile.sh and
the fixtures/real-state.snapshot it read — superseded by SPEC-08's T7,
which actually DRIVES the tree_clean/pushed/msg_committed oracles
instead of miming them via a static line-count regex. Closes J4-09
(WEAK+drift): T4e only counted fixture line-suffixes matching
`=(true|resolved|present)$`; the snapshot itself was stale
(BLK-009=open contradicted blockers-snapshot.md's already-resolved
status) and unowned, and the drift was inert (`=open` doesn't even
match the count regex) — the assertion could never have caught
anything.

Updated skills/reconcile/SKILL.md:53's hardcoded "20/20" claim to the
new total (unguarded file, same logical step, ordered after SPEC-08+
SPEC-10 per the report).

grep -c 'real-state.snapshot' lib/tests/run-reconcile.sh == 0
(verified). No red demo (deletion, per spec) — gate is the green run
+ that grep. GREEN: 25/25 passed, shellcheck clean.
2026-07-06 19:17:23 +02:00
Bastien Chanot fb749f4e30 job4: SPEC-10 decisions-snapshot-fixture
New lib/tests/fixtures/decisions-snapshot.md (neutral name, LRN-077
style): carries a --help token (so reconcile_contradiction_candidates
still surfaces the BDR-001 ⇄ --help-chantier candidate against
todo-snapshot.md), a "one-line ticket" line, and representative
OUT-OF-SCOPE/DEFERRED/follow-up context. T3 and T5 in run-reconcile.sh
now read this fixture instead of the LIVE $MEM/decisions.md; deleted
the $MEM variable definition and its stale comment. Closes J4-10
(FIXTURE-DRIFT): T3/T5 were the last live-registry reads in this
suite (T2 was fixed in job3-B1) — any legitimate prune/reword of the
real decisions.md would have reded the suite for a reason unrelated
to the reconcile engine itself.

grep -c '$MEM' lib/tests/run-reconcile.sh == 0 (verified).
GREEN: real repo, 26/26 passed (all 4 T3 markers + T5 candidate found
via the fixture).
Red demo (per spec — no code mutation, this is a fixture-substitution
spec): lean scratch copy, pointed T3's decisions-arg at /dev/null
transiently → "one-line ticket" (the only marker living solely in the
decisions-side fixture, not in todo-snapshot.md) goes missing, RED;
the other 3 markers stay green (satisfied by todo-snapshot.md alone).
Proves the assertions actually read the fixture rather than passing
vacuously.
2026-07-06 19:16:04 +02:00
Bastien Chanot 12c0d1d9fd job4: SPEC-08 oracle-sandbox
New T7 block in lib/tests/run-reconcile.sh (+6 assertions, 20→26):
a throwaway git repo under mktemp with a LOCAL BARE origin drives the
3 previously-unexercised oracles live: tree_clean (dirty→rc≠0, clean→
rc0), pushed (pushed to origin FIRST so origin/main exists — else
rev-list is vacuously empty — then rc0 when synced, rc≠0 once 1 ahead),
msg_committed (rc0 for a present commit message, rc≠0 for an absent
one). Closes J4-12 (DEGRADED, prerequisite of SPEC-09/10): these 3
oracles backed report-only /reconcile output with zero test coverage —
a silent inversion would mis-report open-work state.

Mutation (lean scratch copy — only lib/reconcile.sh + lib/tests/
run-reconcile.sh + its fixtures + .claude/memory/decisions.md, not the
whole repo/.git, per the /tmp-exhaustion lesson from SPEC-01/02/04):
inverted tree_clean's rc (`-z` → `-n` on the porcelain-status check;
the report's literal "--quiet → negated" wording doesn't match this
function's actual `[ -z ... ]` shape, so applied the equivalent
semantic inversion). RED: both T7a assertions fail (dirty reads as
clean and vice versa); T7b/T7c stay green, confirming the mutation is
localized. (T6a/b/c red in the lean copy too, expected — no real git
history / skills dir there — unrelated to the mutation.)
GREEN: real repo unmutated, 26/26 passed (T7 included).
2026-07-06 19:12:48 +02:00
Bastien Chanot c8e91e8924 job4: SPEC-05 init-identity-precheck-zero-mutation
New T15 block in lib/gitflow-test.sh (+7 assertions, 83→90): fresh git
init sandbox with NO identity (GIT_CONFIG_GLOBAL=/dev/null
GIT_CONFIG_SYSTEM=/dev/null, git 2.53 supports the override) →
gitflow_init must return rc 1 AND leave zero mutation: no develop
branch, unborn HEAD, hooksPath unset, nothing staged, no .gitignore/
.githooks written. Closes J4-06 (WEAK): every test repo up to now set
an identity first, so this precheck never fired.

Mutation (lean scratch copy — only lib/gitflow.sh + lib/gitflow-test.sh
+ templates/gitignore/standard.gitignore, not the whole repo/.git, to
avoid repeating the /tmp exhaustion from the SPEC-01/02/04 full-repo
copies): deleted the identity precheck (gitflow.sh:178-179). RED: 3/7
T15 assertions fail — "nothing staged", "no .gitignore written", "no
.githooks written" — while rc stays 1 and HEAD stays unborn (git itself
still refuses the identity-less commit). This is the half-applied-init
failure mode named in the finding (BLK-012 class): same exit code, but
now via a partial mutation instead of a clean upfront refusal — exactly
why the spec pins zero-mutation checks beyond rc alone.
GREEN: real repo unmutated, 90/90 passed (T15 included).
2026-07-06 19:10:27 +02:00
Bastien Chanot 70d47957c6 job4: SPEC-04 hook-exemption-matrix
New T14 block in lib/gitflow-test.sh (+3 assertions, 80→83), direct
.githooks/pre-commit invocation (T10-style): T14a mixed code+.claude
staged together on main → BLOCKED (whitelist must not let code ride
along .claude/). T14b MERGE_HEAD present + code staged on main →
exit 0 (conflict-resolution commit exemption, gitflow.sh:222). T14c
hook installed+activated BEFORE the first commit (gitflow_install_hook,
not gitflow_init's deferred activation) → root commit still succeeds
(gitflow.sh:221). Closes J4-05 (WEAK): these 3 exemption paths were
untested — a whitelist regression, or the root/merge exemptions
breaking, would have been silent.

Mutations (scratch copy, applied via Bash/sed — not Edit/Write, avoids
tripping config-protection's path-suffix guard on lib/gitflow.sh for a
throwaway file that's never committed), one at a time, each reverted
before the next:
- T14c: deleted the root-commit guard (gitflow.sh:221,
  `git rev-parse --verify -q HEAD ... || exit 0`) → T14c reds alone.
- T14b: deleted the MERGE_HEAD guard (gitflow.sh:222) → T14b reds alone.
- T14a: report's candidate mutation ("remove grep -v '^\.claude/'")
  self-corrects (still blocks mixed, via the inverted over-blocking
  direction — doesn't red). Used the pinned alternative instead:
  `head -1` → `head -0` in the whitelist check (gitflow.sh:230),
  neutering the non-empty test so every protected-branch commit is
  wrongly allowed. T14a reds, plus (expected, same root cause) the
  pre-existing T3 "block direct code on main" and T10 DRIFT(main)/
  DRIFT(develop) also red — consistent with a whitelist regression
  of this shape being a broad, not narrow, break.
GREEN: real repo unmutated, 83/83 passed (T14a/b/c included).
2026-07-06 18:59:44 +02:00
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