skills/prune-memory/tests/run-deterministic.sh:11 default changed from
$HOME/.claude/skills/prune-memory/SKILL.md to $HERE/../SKILL.md (kept
the ${SKILL:-…} env override; reordered HERE's definition before it,
since the new default references $HERE). Closes J4-11 (FIXTURE-DRIFT):
the suite sourced the INSTALLED path, safe today only because
~/.claude/skills/prune-memory is a symlinked directory back to this
repo — if an install ever materializes real copies instead of
symlinking, the suite would silently test the wrong (stale) artifact
while the shipped SKILL.md drifts unnoticed.
Behavior identical today (verified: symlink resolves to the same
inode, `diff` confirms byte-identical content).
GREEN: real repo, suite still all GREEN (RED-1/2/5/6/7).
Red demo (lean scratch copy — skills/prune-memory/{SKILL.md,tests/
run-deterministic.sh} only): moved $HERE/../SKILL.md away → loud
`grep`/`awk: cannot open ... No such file or directory` errors, exit
1, RED-2/RED-5 flip status — proves the new default is genuinely what
gets read, not a silent fallback.
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.
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.
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.
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.
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).
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).
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).
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).
Makefile test target now loops lib/tests/run-*.sh in addition to
*.test.sh + gitflow-test.sh, special-casing run-release-candidate.sh
with RC_WORK=$(mktemp -d) RC_TAG=1. Closes J4-01 (CRITICAL): the 5
run-*.sh suites (memory-commit 13, doc-commit 32, doc-shape 19,
reconcile 20, release 5/5) were excluded from the repo's only
aggregate gate.
Mutation (scratch copy, never the working tree): dropped the
`-- "${changed[@]}"` pathspec from lib/memory-commit.sh:86's commit
call. RED: run-deterministic.sh T2 fails (pre-staged dangling code
gets embarked instead of staying staged), make test exits 2.
GREEN: real repo unmutated, make test exits 0, all suites incl. the
5 previously-excluded ones (RESULT: 13/32/19 passed, 20 GREEN, 5
GREEN RC_TAG=1).
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.
EVAL-018: job3 shipped, 46/46 findings verified, 20/23 fixes applied
(B1 blocked on sentinel scope, D2-D5+B6 skipped by decision), zero
residual on final re-sweep. LRN-105: explorer subagents need an
explicit ban on executing the subject-under-test's own CLI, not just
"read-only" framing (caught mid-run: a subagent ran `graphify .`).
Both sections never matched any component (envelope §1-§9 vs the real
§0-§15 structure; 8-axis integer weights vs the agents' 7/4 and 6/5
percentage-weight scoring). Kept what this file legitimately owns: the
depth-decision matrix and the sibling-skill dedup rules.
- A1: delete STEP 5d (graphify --mode quick doesn't exist in the CLI; the
command always failed, masked by `|| true` — STEP 10's full pass already
covers the graph)
- A2: STEP 10 full-pass build flag --output -> --out
- A8: pipeline is 12 steps (STEP 0-11), not 11; header template unchanged
(N/11 correctly denotes the max index of a 0-indexed 12-step sequence)
- A2: graphify build flag --output -> --out
- A4: ROADMAP xref points to the real /onboard add gsd path, not a nonexistent STEP 9 decision
- A5: exact gitflow init commit message (matches lib/gitflow.sh:163)
- A6: bare skill names (design-review, browse) — no gstack: namespace exists
- A7: eval pattern for recommend_anim_install_cmd (the function only echoes; must eval its output)
BDR-038 recorded NEXT.sh file + AskUserQuestion hand-back as the /deploy
design; 52f6678 removed both (LRN-102: pre-tool-call text may never render)
with no superseding decision. BDR-054 regularizes it. One-line banners on
docs/plans/2026-06-27-deploy-skill.md and docs/specs/2026-06-27-deploy-skill-design.md
point to the shipped behavior; historical body left untouched.