fix(commands): scope microbit-enforcer SessionStart marker-clear to startup|clear (dogfood F1) - #88
Conversation
…tartup|clear (dogfood F1) The SessionStart hook that clears the freeze/guard/careful markers shipped with no matcher, firing on every source — including resume and compact — so a /freeze was silently lost mid-session after compaction or --resume. Now "matcher": "startup|clear": markers clear only on a fresh session or /clear and survive --resume and compaction. Sources verified against code.claude.com/docs/en/hooks-guide (startup, resume, clear, compact; matcher filters on source). The freeze/guard/careful SKILL.md notes, the microbit-enforcer.sh header, and the templates/core/CLAUDE.md gitignore note are corrected to match, across both the templates and the python-uv-fastapi example mirror. New test/microbit-enforcer/test-sessionstart-matcher.sh. Split from the originally-bundled PR #87 per CONTRIBUTING.md (one logical change per PR); F2 (stack-reality preflight) ships as a separate PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
VERDICT: BLOCK SummaryPR #88 correctly scopes the microbit-enforcer SessionStart marker-clear hook to Blockingconfigure.py — retrofit leaves ghost matcherless SessionStart group that re-negates the fix
matcher = ng.get("matcher") # new template → "startup|clear"
targets = [g for g in out if g.get("matcher") == matcher] # finds existing groups with SAME matcherBefore this PR, any user who already ran A migration guard is needed: before appending the new Advisorytest/microbit-enforcer/test-sessionstart-matcher.sh:11 — The established test pattern across the suite uses |
…p on retrofit (PR #88 review) Existing installs carry a matcherless SessionStart marker-clear group; _merge_hook_groups keys by matcher, so cc-configure --retrofit would append the new startup|clear group beside the old one and the stale matcherless group kept wiping markers on resume/compact — re-negating the freeze-loss fix on every upgrade (the same class as PR #73's standalone->bundled bug, for a matcher change). _merge_hook_groups now does a pre-pass: for each configurator command it collects the matcher(s) the new template places it under, then strips that command from any existing group whose matcher is not one of them (a stale placement). This preserves the deliberate same-command-under-multiple-matchers pattern (the mcp drift-check ships under both startup and resume — the no-duplication / self-heal tests still expect 4 SessionStart groups) and never touches a user's own command. New test/retrofit-hooks/test-sessionstart-matcher-migration.sh. Also aligns test-sessionstart-matcher.sh to the heredoc / sys.path '.' style used by the merge tests (review advisory). --check clean, 104/104 fixtures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t the repo root (dogfood F2) (#89) New check_stack_reality() preflight warns at configure time when a configured check command's (typecheck/lint/test) stack manifest is missing at the repo root — the exact condition that makes stop-run-checks.sh silently skip at runtime (skip rule 3). Maps each command's first binary via the shlex-guarded extract_first_binaries() to a stack manifest (new BINARY_TO_MANIFEST, mirroring manifest_for() in stop-run-checks.sh); names the subdir for monorepos (frontend/package.json). When a warned binary is also absent from the host PATH and a compose/Dockerfile is present, it notes the host hooks will no-op because the toolchain lives in the container — host-PATH-gated, so a monorepo whose compose file only backs services doesn't get a false warning. Also hardens the pre-existing extract_first_binaries: shlex.split is now guarded against ValueError (an unbalanced quote in a cmd_* field used to crash the configurator). Tests: test/cc-manifest/test-stack-reality-preflight.sh (8 cases incl. the host-PATH gate via an injected which) + a malformed-quote case in test-check-commands-extracted.sh. --check clean, 103/103 fixtures, 5 persona snapshots, module smoke. Split from the originally-bundled PR #87 per CONTRIBUTING.md (one logical change per PR); F1 (the SessionStart matcher fix) ships as #88. Incorporates review advisories A1 (host-PATH-gated container note) + A2 (reuse extract_first_binaries). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
VERDICT: COMMENT-ONLY PR fixes the matcherless SessionStart bug correctly (the template, docs, and example mirror are all updated consistently) and adds sensible retrofit migration logic to avoid the stale-group double-fire on Advisoryconfigure.py:1268 — new falsy filter silently drops groups with a non-list out = [g for g in out if g.get("hooks")]This line is new in this PR. Before it, every group cloned from test/retrofit-hooks/test-sessionstart-matcher-migration.sh:59 — multi-matcher test exercises steady-state only, not migration merged3, _ = deep_merge_settings(two, two) # existing == new
|
…rt-matcher # Conflicts: # CHANGELOG.md
|
VERDICT: COMMENT-ONLY PR #88 fixes the microbit-enforcer SessionStart marker-clear bug (matcherless hook fired on every source including BlockingNone. Advisorytest/retrofit-hooks/test-sessionstart-matcher-migration.sh:48 — Test case 2 doesn't assert CLEAR is absent from the matcherless group Test case 2 (the mixed-group scenario: existing matcherless group with both CLEAR and a user's assert any("my-own-hook" in h.get("command", "") for g in ss2 for h in g.get("hooks", []))This passes as long as Test case 1 correctly asserts n_clear2 = sum(1 for g in ss2 for h in g.get("hooks", []) if ".frozen" in h.get("command", ""))
assert n_clear2 == 1, f"CLEAR must appear exactly once after migration; got {n_clear2}: {ss2}"The test comment on line 38–39 is also slightly misleading: it says the mixed group "must be left untouched" but the code correctly strips CLEAR from it (leaving only the user hook); clearer wording would be something like "the user's own hook survives; the configurator command is migrated to its new matcher." No blocking issues — the core logic, migration path, and primary test coverage are correct. |
…dant import; tighten migration test (review follow-ups) (#90) Three non-blocking advisories from the split dogfood PRs #88/#89: (1) check_stack_reality's container-note gate used shutil.which(b), which always returns None for a path-like binary like ./gradlew (a project-local wrapper, never on PATH) — so a Gradle-wrapper project with a Dockerfile and no root build.gradle got a spurious "toolchain in container" note. The gate now file-checks path-like binaries ((target_dir / b).exists()) and only consults shutil.which for bare names. (2) the inline `import shutil` inside check_stack_reality was redundant (shutil is imported at module top) and is removed. (3) test-sessionstart-matcher-migration.sh case 2 now asserts the marker-clear migrates OUT of a user's mixed matcherless group (exactly once, under startup|clear), not merely that the user hook survives — and the F1 CHANGELOG bullet's stale "subset" description is corrected to the shipped placement-set pre-pass. New ./gradlew case in test-stack-reality-preflight.sh. No behavior change for non-path stacks. --check clean, 105/105 fixtures. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
F1 from the container dogfood. The
microbit-enforcerSessionStart marker-clear shipped with nomatcher, so it fired on every SessionStart source (startup,resume,clear,compact). A/freeze//guard//carefulmarker was therefore silently wiped on--resumeand after compaction — mid-session, exactly when a long session most needs the freeze to hold.Fix
templates/commands/microbit-enforcer/settings-patch.jsonSessionStart entry now carries"matcher": "startup|clear"(source set confirmed againstcode.claude.com/docs/en/hooks-guide). Markers clear only on a brand-new session or/clear; they persist across--resume/--continueand compaction.Lifecycle docs corrected to match — across both the templates and the
python-uv-fastapiexample mirror: freeze/guard/carefulSKILL.md, themicrobit-enforcer.shheader, and thetemplates/core/CLAUDE.mdgitignore note.Tests
New
test/microbit-enforcer/test-sessionstart-matcher.sh(asserts the merged settings carry the matcher; red against the unmatched form).configure.py --checkclean; full microbit-enforcer suite green.Scope
Split out of #87 per
CONTRIBUTING.md("one logical change per PR") — this is thefix/patch half. Thefeathalf (F2, thecheck_stack_reality[ STACK WARNINGS ]preflight) ships as a separate PR. Notested_up_to/CC_VERSIONbump.🤖 Generated with Claude Code