fix(hooks): widen PostToolUse gate matchers to MultiEdit + @file-content-gate/@matcher-parity preventer - #1054
Merged
Merged
Conversation
…(3-round review) Captures the design after 3 rounds of dual top-down + bottom-up review. Scope: widen 6 PostToolUse gate hooks Edit|Write -> Edit|Write|MultiEdit (incl. check-doc-authority-header from merged #1009, discovered when the worktree base was refreshed off stale de7fc9d -> 4a9b508). Single atomic PR: the fix + a ~15-LOC grep probe (parity for case-TOOL hooks, @file-content-gate marker for path-only hooks) + a live RED fixture. Full principle No.33 deferred to incidence N>=2.
…sed edit-site Round-4 review (fresh base 4a9b508) found: - R1: reuse-first path — extend check-hook-marker.sh (edit-time) + channel-coverage.sh (CI) instead of a new standalone probe. Lands preventer at earliest reachable channel (rule-enforcement-channel- selection §4), avoids consumer dead-token (R2). - R5: missed edit-site tests/install-sh/gh-934-ship-doc-authority-hook.test.sh:56 (hardcoded Edit|Write equality from #1009) — added to §2 inventory. - R4 nit: citation BFR §5 -> dual-implementation-discipline §9 (probe != principle slot). Decision (option B): DUAL-LAYER gate — edit-time catches early, CI catches reliably (the original bug was itself an edit-time-hook bypass; single-layer trust in the same mechanism class is not robust).
…e|MultiEdit MultiEdit emits a SEPARATE PostToolUse event (tool_name:MultiEdit) that an Edit|Write matcher does NOT catch (falsifier: docs/2026-07-17-matcher- widening-design.md §1). A MultiEdit that violates a gate's rule slipped past silently. The real bug shape is body<->registration drift: 3 of 6 hooks already had 'case "$TOOL" in Edit|Write|MultiEdit)' in their body but were registered Edit|Write. SSOT-first: harness-model.json (5) -> render --write -> setup.d/10-skills.sh (206,246) + install.sh (469,493) (inject-matching-rule + check-doc-authority- header shipped) + gh-934 tests (session-ux 56/57/136, doc-authority-hook 56) + re-capture 4 greenfield npm baselines (brownfield stable, 3 python stable). inject-memory-codification stays Write-only (A5 — creation moment). check-doc-authority-header from merged #1009 (was invisible until the worktree base was refreshed off stale de7fc9d -> 4a9b508). Gates GREEN: render --check, byte-identical 11/11, refresh-covers-full- delivery, both gh-934 tests (11/11 + 14/14), shellcheck clean. STEP 2 (dual-layer preventer: extend check-hook-marker.sh + channel- coverage.sh + RED fixtures) follows in this same PR.
Self-contained SSOT for an unattended resume session. Includes the STEP 0 hypothesis-verification block (verify state on disk BEFORE work), the resume point (STEP 1 done at f707d68d1; STEP 2 Layer 1 in-progress uncommitted), the strict task order A-G, the autonomy/fork policy for an operator-away run, critical nuances (shipped-only tolerance, A5, frozen intentional-skip list), and the policy reminders (Decision B dual-layer APPROVED, do not re-litigate).
STEP 1 widened 5 PostToolUse gate matchers Edit|Write -> Edit|Write|MultiEdit in the CC channel (.claude/settings.json) + SSOT (.ai-factory/harness-model.json). At STEP 1's base the plugin channel (plugin/hooks/hooks.json, added by staging zcode-parity #1030/#1036/#1046) did not exist; post-rebase it carried the same 5 gate matchers still narrow. Widened them surgically (exact-string sed, 5 lines) rather than via `render-harness-config.mjs --write`, which would clobber the hand-maintained ZCode-parity twins (inject-project-digest, inject-output-language, warn-subagent-report-zcode) that emitPlugin does not reproduce from the SSOT. Diff = 5 matcher lines only; twins intact (5 refs); valid JSON. See docs/superpowers/specs/2026-07-17-matcher-widening-decisions.md D2 + finding F1 (pre-existing, ungated staging SSOT/render drift — out of scope, surfaced only). Prior-art: skipped — rendered-output matcher widening + session decisions doc, no new capability (no dependency, no new code module).
…dening preventer) Prevents the body<->registration matcher-drift class that STEP 1 fixed from recurring. A hook that validates a file's *content* (path-only — no internal tool_name filter) MUST be registered with matcher Edit|Write|MultiEdit, else a MultiEdit edit that violates the rule slips past silently (the registration matcher is the gate's ONLY tool-filter). 3 of 6 hooks had drifted this way. Mechanism (edit-time, earliest reachable channel per README goal): - New `# @file-content-gate:` marker declared on the 4 path-only content-gate hooks (validate-prompt, check-doc-authority, check-doc-authority-header, and check-hook-marker documents its own extension). - check-hook-marker.sh extended: when a hook declares @file-content-gate AND is registered in the framework-self .claude/settings.json, assert its matcher contains Edit, Write AND MultiEdit; else _adv_violation (exit 1 on CC). - Tolerance: a shipped-only hook (no framework-self settings.json entry, e.g. check-doc-authority-header) is SKIPPED — the gate has no framework-side matcher to read for it; its matcher is enforced by the install-sh firing tests + Layer 2. Tests: 3 new arms in check-hook-marker.test.ts (paired-negative Edit|Write -> exit 1; paired-positive Edit|Write|MultiEdit -> exit 0; shipped-only absent-from-settings -> exit 0). Full suite 11/11 green on the rebased base (staging #1030 runHook object shape + ZCODE arm reconciled). CI-backstop (population sweep) follows in Layer 2. Design: 4-round-reviewed spec docs/superpowers/specs/2026-07-17-matcher-widening-design.md. Prior-art: skipped — edit-time gate extension to an existing hook + marker comments on existing hooks, no new capability (no dependency, no new code module >=50/80 LOC, no new subdir).
…son twin-entry not render-reproducible)
…stop Closes the case-TOOL coverage gap (cold-review MAJOR) and places the CI backstop in the CC-config bucket, NOT the harness-agnostic channel-coverage probe (operator-flagged). Layer 1 (edit-time, check-hook-marker.sh): - New @matcher-parity invariant: a hook with `case "$TOOL" in <tools>)` must have its registered matcher ⊇ that case-arm set. Covers the 3 case-TOOL gates that carry no @file-content-gate marker (check-kickoff-traps, check-worker-dispatch-channel, check-hook-marker itself). Uses each hook's existing `case` author-declaration — no new marker, no allowlist. Self-calibrating: inject-memory-codification (A5, Write-only) stays GREEN (matcher Write ⊇ case-arm Write) with no hardcoded MultiEdit demand. - Refactored the settings.json matcher lookup into _reg_matcher(), anchored on the closing `"` so check-doc-authority no longer substring-matches check-doc-authority-header. Layer 2 (CI backstop, packages/core/hooks/check-hook-marker.test.ts): - A vitest describe block runs the REAL edit-time gate against EVERY tracked hook against the live settings.json — catches a matcher narrowed directly in harness-model.json/settings.json even when no hook .sh is edited (the vector Layer 1 cannot see). Reuses the gate verbatim (no parallel population reader). 2 RED fixtures prove non-vacuousness; ≥5-hook sentinel. - Bucket: CC-config (hooks), NOT tests/agnosticism/ — the MultiEdit requirement is CC-specific (inert on other harnesses), so it must not contaminate the harness-agnostic PORTABLE verdict. Supersedes the kickoff Task-C plan (channel-coverage.sh extension + tests/fixtures/matcher-drift/). Suite 17/17 green. See decisions doc D3 (bucket) + D4 (parity rule). Prior-art: skipped — edit-time gate extension + test arms for the existing hook capability, no new capability (no dependency, no new code module >=50/80 LOC, no new subdir).
… staging inject-* staleness) The @file-content-gate marker added to the shipped check-doc-authority-header.sh shifts its install fingerprint. Re-capturing (snapshot.sh, SNAPSHOT_MODE=capture) also corrects three baselines that origin/staging left STALE — inject-matching-rule / inject-output-language / inject-project-digest — hooks staging changed via zcode-parity but never re-captured (my source for them is byte-identical to staging's; only staging's recorded baseline was old). byte-identical is an all-or-nothing gate → a full re-capture is mandatory to green it; there is no way to refresh only the marker-touched hook. Captured hashes match the live source (verified via shasum). See decisions doc F2. Framework-internal hooks (check-hook-marker, check-doc-authority, validate-prompt) ship to 0 consumers → untouched here. Prior-art: skipped — regenerated install snapshot baselines after a shipped-hook marker edit, no new capability.
…iew MINOR)
The case-arm extraction matched `case "$TOOL" in …` on ANY line, including comments — and
this hook's own @matcher-parity doc block now contains that pattern in prose. Current
population was safe only because the real code arm precedes the comment mentions and head -1
takes the first; one reorder would mis-extract the prose arm → false parity verdict. Strip
comment lines (^[[:space:]]*#) before extraction, mirroring the ^#-anchored marker check.
New test arm PARITY comment-immunity: a prose `case "$TOOL" in Read)` above the real
`case "$TOOL" in Write)` with matcher Write → exit 0 (reads the real {Write}, not {Read}).
Suite 18/18 green.
Prior-art: skipped — hardening the edit-time gate's parser + one test arm, no new capability.
… + F1 cleared); 1147/1147 green
…oEmit)
vitest passed but tsc --noEmit (all-workspaces CI job) rejected `delete fullEnv.ZCODE_PROJECT_DIR`
— the {...process.env, CLAUDE_PROJECT_DIR} literal lost ProcessEnv's index signature. Explicit
annotation restores it, mirroring the sibling runHook helper.
Prior-art: skipped — type annotation on a test helper, no new capability.
…g operator merge
10 tasks
artyhoo
added a commit
that referenced
this pull request
Aug 17, 2026
…sts nobody ran (#1441) ## Summary `tests/install-sh/meta-all-wired.test.sh` has gated exactly ONE shell-test directory since it shipped. Nothing gated the other six, so the `#armed-but-not-fired` false-green that principle 38 measured for vitest was live for shell tests too. Enumerated over `git ls-files`: **130 tracked `*.test.sh`, 12 invoked by no workflow step** — `tests/plugin/` (9 of its 10), `tests/aif-doctor/` (2), `scripts/probe-channels.test.sh` (1). Principle 41 closes the population; eleven of the twelve are wired in the same commit. **The finding is worth more than the gate.** One of the twelve is RED, and had been silently so across five hook revisions. ## The red test — traced, not guessed `tests/plugin/end-of-turn-reminder-zcode.test.sh` case (1) asserts the Stop hook suppresses a repeated question through its ZCode-rollout arm. The hook answers `decision: block` instead. `ZCODE_ROLLOUT_DIR` appears in that test file and **nowhere else in the tree**. The arm shipped in #1044 into the *twin* `plugin/hooks/end-of-turn-reminder`, never into the source `.claude/hooks/end-of-turn-reminder.sh`. That twin now carries no `@plugin-transform` marker and is byte-identical to its source — i.e. identity-generated — so regeneration overwrote the arm. The test that would have caught it ran at no channel, so it could not say so, through #1054, #1137, #1142, #1349 and #1409. That is the argument for this gate in one sentence: a directory nobody wired hid a real capability loss for five revisions of the file it guarded. **It is allowlisted — not wired, not deleted.** Wiring it would paint CI red on a defect this PR did not cause; deleting it would destroy the only evidence. «Restore the arm or retire the test» is a behaviour decision this gate does not get to make. The `COVERAGE_ALLOWLIST` entry carries the full diagnosis and instructs removal in the commit that settles it. **This needs a decision from you** — see Parked questions. ## Changes - **`packages/core/principles/41-shell-test-ci-coverage.test.ts`** (new) — population = git-tracked `*.test.sh`; registry = `.github/workflows/` step text. Seven arms reusing 38's structure: real-tree, non-vacuity, two seeded negatives (step removal · population addition), fail-closed on an unreadable workflow dir, comment-only mentions, allowlist hygiene. - **`.github/workflows/audit-self.yml`** — 11 previously-unrun tests wired as steps. The `tests/aif-doctor/` pair is safe on a clean runner by construction, not by hope: both stub `docker`/`gh` explicitly and carry an in-file «ENVIRONMENT-INDEPENDENCE PROOF (must pass with real docker on PATH)» — verified locally at 9/9 and 5/5. - **`scripts/run-local-ci-sweep.sh` + `run-local-ci-sweep-coverage.test.sh`** — `tests/plugin/` and `tests/aif-doctor/` registered as battery families, covered by one row **derived from the workflow** (the `script-selftests` pattern) rather than a `tests/plugin/*` loop. This is the load-bearing choice: a derived row cannot pick up a test wired to no CI step, so the known-red test stays out of the local sweep. `probe-channels` needed nothing — the derived `scripts/*.test.sh` family already covered it. My previous literal `plugin-twin-tests` row is superseded by this one. - **`docs/meta-factory/prior-art-evaluations.md`** — `Last reviewed` on #248 bumped to 2026-08-17 in the same commit, per the PR-template requirement for a matched entry. ## Prior-art consult - [x] `Prior-art: prior-art-evaluations.md#248` (**ADAPT**). #248 states our problem class verbatim — «which files does the CI *invocation list* never select?», a completeness claim over an external runner's hand-written argv against a declared population. Its DeepWiki + context7 + WebSearch pass established that nothing upstream reconciles the two, and that finding transfers unchanged. - [x] No new SSOT entry: the matched entry covers this class, and this commit only widens the population (vitest `include` → every tracked `*.test.sh`) while reusing in-repo arm structure from `38-vitest-include-ci-coverage.test.ts` and `meta-all-wired.test.sh`. No new engine, no new dependency, no new channel. - [x] `Last reviewed` updated on the matched entry in the same commit (`git log -p` on the SSOT confirms the touch). ## Test plan - [x] Principle 41 — **7/7**, with both seeded negatives confirmed firing (not merely present): deleting a real wiring step reports that test as unwired; a synthetic population member is reported. - [x] Full principles — **44 files / 419 passed, 1 skipped** under `--no-file-parallelism`, which dodges the pre-existing inter-file race diagnosed and controlled in #1437 (clean `origin/staging` reproduces that race; it is not from this branch). - [x] All 11 newly-wired tests green **under bash**. Method note worth recording: an earlier check reported RED because I ran the `for t in $ts` loop under zsh, which does no word-splitting — the sweep row is bash by shebang and green there. The defect was in my probe, not the row. - [x] Sweep coverage metatest **9 pass / 0 fail**; the derived list contains exactly the 11 wired tests and **excludes** the known-red one. - [x] `shellcheck` clean on both edited scripts — the single `SC2086` it reports is pre-existing (`SWEEP_DIFF_OVERRIDE`, 2 hits on untouched `staging`), deliberately not fixed here. - [x] YAML parses; `typecheck` and `prettier` clean; principle 08 (SSOT citation integrity) 9/9 after the `Last reviewed` edit. - [x] **Comment-stripping measured, not assumed.** 130 tracked tests are named in the raw workflow text but only 129 inside real steps — because audit-self.yml mentions the allowlisted red test in a `#` comment. Counting comments would mark it «wired» and the derived sweep row would run a knowingly-red test locally. Arm (f) pins this against the real file rather than a synthetic string, and I corrected the docblock that had claimed the two readings agree — they no longer do, precisely because of a comment I added in this PR. ## Provenance n/a — non-stage follow-up to observation (2) parked in #1437; no kickoff, no aif substrate, in-session. ## Review findings n/a — non-stage PR. Own cold read caught two defects in my own work before commit: a dead `workflowRegistry.length >= 0` condition in arm (f), replaced with the real comment-case assertion; and a docblock that had gone false the moment I added the comment it described. ## Fidelity verdict FIDELITY: skipped — non-stage infrastructure PR, no kickoff or stage acceptance contour applies to it. ## Parked questions **(1) Needs your decision — the ZCode rollout arm.** Two options, and I am deliberately not picking: **restore** the arm (it was real capability — multi-turn repeat-question suppression via the ZCode rollout — and would have to land in `.claude/hooks/end-of-turn-reminder.sh`, the source, so regeneration cannot eat it again), or **retire** the test (if the ZCode multi-turn channel is no longer wanted). Either way the `COVERAGE_ALLOWLIST` entry in principle 41 must be removed in the same commit; the gate then holds the whole population with no exemptions. **(2) Observation, not fixed here:** this is the second capability lost to twin regeneration in two PRs (#1437 was the agents twin, this is the ZCode arm). The pattern is «logic lives only in `plugin/`, source has none, twin becomes identity-generated, logic vanishes». A gate could plausibly catch it — e.g. asserting a twin's declared `@plugin-transform` mode accounts for every line the source lacks — but I have one measured shape, not three, so I am recording it rather than designing on a sample of one. **(3) Observation:** `meta-all-wired.test.sh` is now a strict subset of principle 41. Retiring another owner's gate is a separate decision; I left it in place. ## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files) ### §1.7 Forward-check applied **`.claude/rules/attention-is-not-a-mechanism.md:29`** is the rule this PR enforces: a test invoked by no channel is `#warning-nobody-reads` in its strongest form — its failure mode is that nobody ever looked, and the ZCode defect proves the cost concretely. §1 clause (a) is satisfied by a deterministic gate at the earliest reachable channel. **`.claude/rules/rule-enforcement-channel-selection.md:45`** — «Detectable? Mechanically yes → design the narrowest gate on the earliest reachable channel» (§3 step 1); breadth is repo-wide because the population is, and per that rule's own channel table (`:59`, principle test = gate, repo-wide, zero standing cost) `principles/` is the channel that fires at both pre-push and CI. **`README.md#why-this-exists`** invariant 4 is served rather than claimed: the gate runs at pre-push first, CI as backstop. **`.claude/rules/no-paid-llm-in-ci.md`** — file reads plus `git ls-files`; zero API-billed calls. **`.claude/rules/build-first-reuse-default.md:57`** — ADAPT per #248, and cheap by that section's own cost gate: no dependency, no standing infra. **`.claude/rules/ci-tool-pinning.md`** — the 11 new steps install nothing, so §1 Rule A does not fire. ### §1.7 Backward-check applied Class of this change = **populations of test artifacts reconciled against an invocation registry**. Enumerated by grep over `packages/core/principles/*.ts` and `tests/**`, not from the diff — four members, and three are surfaces this PR does not touch. `38-vitest-include-ci-coverage.test.ts` — SWEPT-CLEAN, the vitest half, untouched and re-run green. `tests/install-sh/meta-all-wired.test.sh:22-43` — SWEPT-CLEAN, still passes; now a strict subset of principle 41, recorded as parked question (3) rather than retired. `36-ci-needs-completeness.test.ts` — SWEPT-CLEAN, different axis (jobs ⊆ `ci-success.needs`), re-run green after 11 steps were added to an existing job. `scripts/run-local-ci-sweep-coverage.test.sh` — the *reverse* direction (a CI command the sweep never runs); GAP-FOUND by construction, since adding 11 CI steps would have made it red, and closed here with two family registrations. Adjacent surface checked and excluded: `27-prepush-copylist-complete.test.ts` reconciles the install copy-list, a different population with its own registry. No rule superseded, no owner's gate retired. --- Prior-art: prior-art-evaluations.md#248 (verdict ADAPT — #248 states this problem class verbatim: «which files does the CI *invocation list* never select?», a completeness claim over an external runner's hand-written argv against a declared population. Its DeepWiki + context7 + WebSearch pass established that nothing upstream reconciles the two, and that finding transfers unchanged. This commit widens the population from vitest `include` to every git-tracked `*.test.sh` and reuses the in-repo arm structure of 38-vitest-include-ci-coverage.test.ts plus tests/install-sh/meta-all-wired.test.sh. Last reviewed bumped to 2026-08-17 on the matched entry in the same commit. No new engine, no new dependency, no new channel.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Widens 6 PostToolUse gate matchers
Edit|Write→Edit|Write|MultiEdit(aMultiEditedit was silently bypassing them), then makes that class of drift executable-preventable at two channels:.claude/hooks/check-hook-marker.sh): two new self-declared invariants.@file-content-gatemarker → the hook's registered matcher must include Edit, Write and MultiEdit (for the 3 path-only content-gates whose only tool-filter is the matcher).@matcher-parity→ any hook with an internalcase "$TOOL" in <tools>)must have its matcher ⊇ that case-arm set (covers the 3 case-TOOL gates that carry no marker, incl.check-hook-markeritself). Self-calibrating:inject-memory-codification(A5 Write-only) stays green with no hardcoded MultiEdit demand.packages/core/hooks/check-hook-marker.test.ts): a population sweep that runs the real edit-time gate against every tracked hook against the livesettings.json— catches a matcher narrowed directly in the SSOT/settings.json even when no hook.shis edited (the vector Layer 1 can't see). Placed in the CC-config bucket, NOTtests/agnosticism/—MultiEditis CC-only (inert on other harnesses), so the check must not contaminate the harness-agnosticchannel-coverageprobe's PORTABLE verdict.Full design:
2026-07-17-matcher-widening-design.md(4 review rounds). Session forks/findings:2026-07-17-matcher-widening-decisions.md(D1–D5, F1–F2).§1.7 Forward-check applied
Class of this change = a new edit-time discipline gate enforcing matcher-completeness for file-edit hooks (recursive-self-application target: the gate is itself a
.claude/hooks/*.shsubject to its own invariants).2026-07-17-matcher-widening-design.md:65(«for the case-TOOL hooks the parity rule applies, matcher ⊇ case-arm set»); the kickoff's Layer-1-marker-only deferral was under-specified and is corrected here (decisions D4). The bucket relocation off the agnosticism probe is decisions D3.packages/core/hooks/check-hook-marker.test.tsships RED fixtures that must exit 1: the@file-content-gatePAIRED-NEGATIVE (check-hook-marker.test.ts:249), the@matcher-parityPARITY-NEGATIVE (check-hook-marker.test.ts:297), and the Layer-2 backstop RED fixtures (check-hook-marker.test.ts:433). A vacuous gate would let them exit 0 and the suite would fail. Suite = 18/18 edit-time + a real-tree population arm asserting all live hooks pass.check-hook-marker.shis itself a case-TOOL hook (case "$TOOL" in Edit | Write | MultiEdit)at.claude/hooks/check-hook-marker.sh:52); the parity rule applies to it and it passes (matcher EWM ⊇ case-arm EWM). Its extraction strips comment lines (check-hook-marker.sh:112) so its own@matcher-paritydoc prose is not mis-read as a code arm — a comment-immunity test arm proves it (check-hook-marker.test.ts:332).§1.7 Backward-check applied
Class = «a PostToolUse hook that gates file edits must have a matcher covering every edit-tool it handles». Swept every sibling surface where the class must hold:
case "$TOOL"arm verified matcher ⊇ case-arm:check-hook-marker,check-kickoff-traps(.claude/hooks/check-kickoff-traps.sh:35),check-worker-dispatch-channel(.claude/hooks/check-worker-dispatch-channel.sh:51),inject-matching-rule(EWM/EWM),inject-memory-codification(Write/Write). 3 path-only carry@file-content-gate:validate-prompt,check-doc-authority,check-doc-authority-header.inject-memory-codification) — SWEPT-CLEAN: parity{Write} ⊆ {Write}green; the rule does NOT force MultiEdit onto it, so A5 is preserved, not superseded..ai-factory/harness-model.json, CC.claude/settings.json, pluginplugin/hooks/hooks.json) — SWEPT-CLEAN: narrow=0 / wide=6 in each; plugin twins (warn-subagent-report-zcodeetc.) preserved through the rebase conflict resolution (decisions D5).check-doc-authority-header, absent from frameworksettings.json) — SWEPT: tolerated by Layer 1 (no framework matcher to read), enforced bytests/install-sh/gh-934-ship-doc-authority-hook.test.sh:56+ Layer 2.channel-coverage.shplan is replaced (agnosticism correctness, D3); thechannel-coverageprobe itself is untouched and still owns@dual-pair/@cc-only-rationalesweeping.Intentional-skip list (frozen / out-of-scope files NOT edited)
tests/fixtures/plugin-broken-manifest/plugin/hooks/hooks.json(intentional broken fixture) ·packages/core/hooks/apply-doc-fixes.test.ts:32,36+scripts/apply-doc-fixes.sh:61,66(frozen historical prose) ·packages/core/hooks/validate-prompt.test.ts:4(doc comment) ·setup.d/lib.sh:871(doc example) ·scripts/setup-cc-adoptions.sh:62(warning prose) ·.claude/orchestrator-prompts/launch-preannounce-track/s5-probes/probe-zcode-hooks.sh:52,59(frozen probe data) ·validate-prompt.sh:52pre-existing SC2295 (not CI-scope, not this PR's concern).Verification
cd packages/core && npx vitest run principles/ hooks/→ 1147/1147 green (rebased onto staging trunk-restore62d90304d; the 8 formerly-inherited reds were staging's, verified on a pristineorigin/stagingworktree — this PR adds zero red).byte-identical,render --check,shellcheck(setup.d + install.sh),lib-helpers/meta-all-wired/layer-units/refresh-covers-full-delivery/gh-934-ship-*→ all green.Prior-art: skipped — extends the existing
check-hook-marker.shgate + adds test arms; no new dependency, no new code module ≥50/80 LOC, no new subdirectory.