Skip to content

feat(hooks): S2b — the PreCompact residue writer, benched before it was wired (D8) - #1434

Merged
artyhoo merged 1 commit into
stagingfrom
claude/suspicious-goldberg-d15ff2
Aug 17, 2026
Merged

feat(hooks): S2b — the PreCompact residue writer, benched before it was wired (D8)#1434
artyhoo merged 1 commit into
stagingfrom
claude/suspicious-goldberg-d15ff2

Conversation

@artyhoo

@artyhoo artyhoo commented Aug 17, 2026

Copy link
Copy Markdown
Owner

What and why

Stage S2b of the pipeline-chips ADR — the D8 PreCompact residue writer. F8 closed 2026-08-17 (operator GO on the SSOT #108 PreCompact item, given in a live session), so the stage is unblocked; the bench ran first, per D8's own ordering.

A compacted session left nothing behind. The D7 context-arm's debounce flag stays spent across an auto-compact, so a session re-climbing after one gets no second nudge — and no record of what it had been doing. D8's shape is the one already registered in SSOT #108: the hook itself writes the state, deterministically. Round-1's alternative («remind the model to write a handoff before compaction») is undeliverable as specified — a non-blocking PreCompact hook gives the model no execution window.

The bench, and what it did not prove

D8 makes a liveness bench the gate and orders it before the writer. It ran, and its outcome is split three ways rather than summed — docs/meta-factory/research-patches/2026-08-17-precompact-liveness-bench.md:

  • Contract — PROVEN. 15 cases green; the suite is not vacuous (seeding select(.isSidechain != true)select(true) flipped exactly one case, then restored); run against one real 202-line CC transcript, not only fixtures; and end-to-end reader/writer agreement — the hook wrote to .claude/orchestrator-prompts/_residue-e2e-proof.md (gitignored, .gitignore:7) and the /pipeline §1 fence, run verbatim, printed it back.
  • Manual delivery — NOT FIRED. Registration needs .claude/settings.json, whose own deny-list carries Write(.claude/settings.json); hooks snapshot at session start; /compact is an operator action.
  • Auto delivery — NOT OBSERVED, and not observable from history either: 0 files matched "isCompactSummary":true across the 60 most-recent transcripts. Consistent with a 1M window, but per T14 that is «coverage insufficient», never «works».

D8 pre-accepted this split («an honest auto path unverified, manual path proven is an acceptable bench outcome»). What ships is one notch below even that — manual is proven at the CONTRACT level, unfired at the DELIVERY level — so it is stated rather than rounded up.

Two deviations from D8-as-written (recorded in the ADR, not silent)

  • The auto matcher is dropped; the hook registers on all triggers. With auto alone the only half a human can trigger on demand is unfireable by construction, and a manual /compact discards the same context. Bench finding B-2; the writer is matcher-independent (asserted).
  • The renderer surface is emitPlugin, not emitClaude. emitClaude already passes any event through, so there was nothing to change there. The real gap: emitPlugin continued silently past every event outside ZCODE_EVENTS, so a hook on an inexpressible event vanished with no trace — the #warning-nobody-reads shape inverted. It now names each dropped event in a note op (fires today on SubagentStart/SubagentStop).

Changes

  • .claude/hooks/precompact-residue.sh — new. Extracts the ai-title anchor (fallback: first user turn) and the last recap block, marker sourced from the lang pack; fallback is the last main-thread assistant excerpt. select(.isSidechain != true) is load-bearing (subagent turns share the transcript). Writes even with no transcript — «a session existed here and was compacted» is itself the fact the reader needs. Every path exits 0 and stdout stays empty: PreCompact can block on exit 2, and blocking compaction on a residue-write failure would strand a session at a full window.
  • packages/core/hooks/precompact-residue.test.ts — 15 cases. Every case pins AIF_HOOK_LANG: the same fixture scored recap under en and excerpt under the ambient ru, caught by a bash -x trace, so an unpinned suite is a coin-flip.
  • scripts/render-harness-config.mjs — the off-ZCODE_EVENTS skip becomes a note op.
  • .claude/rules/zcode-parity-doctrine.md — census row 21 (cc-only), rollup Total = 20 → 21 and cc-only 2 → 3, §4 rationale, §5 tier-table counts.
  • .claude/skills/pipeline/SKILL.md — §1 Step-1 injection extended inside the existing line (the file sits at 599/600). The line still opens head -200 "$(bash …print-orch-home.sh…) because principle 39's paired-negative seeds its hardcode by matching that prefix — reshaping the line broke the test, so the shape was kept rather than the test edited.
  • docs/meta-factory/prior-art-evaluations.mddocs: reconcile roadmap status — N6a + N4a already merged #108 Last reviewed → 2026-08-17, PreCompact item marked BUILT with both deviations from its sketch.

Registration is NOT in this PR — and why it cannot be

.claude/settings.json is tracked and rendered from .ai-factory/harness-model.json, with the drift gate checking the real tree every CI run. Editing only the SSOT was tried in a sandbox copy of both files: ✗ harness-config drift: - .claude/settings.json: drift vs SSOT, exit 1. So the two files move in ONE commit — and one of them is agent-blocked. The hand-off block is below.

Verification

  • npx vitest run packages/core/hooks/precompact-residue.test.ts → 15/15; seeded-break → 1 failed | 14 passed; restored → 15/15.
  • npx vitest run packages/core/principles/ → 402 (401 pass, 1 skip), 42 files. Principle 39 caught the fence reshape and is green after the fix.
  • node scripts/render-harness-config.mjs --check✓ harness-config up-to-date, with the new note firing.
  • bash tests/plugin/hook-paths.test.sh → PASS=82 FAIL=0. SNAPSHOT_MODE=compare bash tests/install-sh/snapshot.sh → 15 pass / 0 fail (byte-identical; the hook is framework-internal, not installer-delivered). npm run format:check, npm run typecheck, npm run check:skill-drift → green.
  • Worth flagging for the next session in this worktree: node_modules was empty on arrival (the unprovisioned-worktree class), which made three unrelated suites fail with spawn -1 before bash scripts/worktree-node-modules.sh --apply fixed them.

Operator hand-off — registration (agent-blocked, per CLAUDE.md «settings.json uncommittable»)

Idempotent, backs up first, validates before moving, and touches both files so the drift gate stays green. Paste as one block:

( set -euo pipefail
  cd /Users/art/code/rules-as-tests-aif || exit 1
  H='bash "$CLAUDE_PROJECT_DIR/.claude/hooks/precompact-residue.sh"'
  test -f .claude/hooks/precompact-residue.sh || { echo "hook missing on this checkout — merge the S2b PR first"; exit 1; }
  if jq -e --arg c "$H" 'any(.hooks.PreCompact[]?.hooks[]?; .command == $c)' .claude/settings.json >/dev/null; then
    echo "already registered — nothing to do"; exit 0
  fi
  cp .claude/settings.json ".claude/settings.json.bak.$(date +%Y%m%dT%H%M%S)"
  cp .ai-factory/harness-model.json ".ai-factory/harness-model.json.bak.$(date +%Y%m%dT%H%M%S)"
  jq --arg c "$H" '.hooks.PreCompact = [{hooks:[{type:"command",command:$c}]}]' .claude/settings.json > /tmp/s.json
  jq -e . /tmp/s.json >/dev/null && mv /tmp/s.json .claude/settings.json
  jq --arg c "$H" '.hooks.PreCompact = [{command:$c}]' .ai-factory/harness-model.json > /tmp/m.json
  jq -e . /tmp/m.json >/dev/null && mv /tmp/m.json .ai-factory/harness-model.json
  node scripts/render-harness-config.mjs --check && echo "OK — registered, drift-clean. Commit BOTH files together."
)

No matcher on purpose (bench finding B-2 — an auto-only matcher makes the manual half unfireable).

Then the residual bench, which only you can run: start a fresh session in this repo, run /compact, and check that _residue-<session>.md appeared under .claude/orchestrator-prompts/. The auto half closes itself the first time a session reaches the ceiling. Until that fire is observed, D8's delivery leg is honestly unverified; the bench file is where the observation gets recorded.

Prior-art: prior-art-evaluations.md#108 (Claude Code orchestrator mechanical-gate hooks, verdict ADOPT) — this is that row's PreCompact item, built rather than re-derived: the «save wave-state before compaction» shape is taken from the entry, and the two places this implementation departs from its sketch (per-session file in the resolved orch-home instead of one clobbered .claude/session-state.md; the hook writes rather than reminds) are recorded in the row itself, whose Last reviewed date is updated in the same commit. The row's other four items are untouched, and its DECISION-NEEDED note is why registration stays operator-landed.

Fidelity verdict

FIDELITY: skipped — spec-homed contour with no orchestrator-prompts umbrella; an in-session stage authored against the merged ADR, not a deliverable against a kickoff. Same basis as S3 (#1426).

§1.7 Forward-check applied

attention-is-not-a-mechanism.md:1 — the residue has a NAMED reader (.claude/skills/pipeline/SKILL.md:85), not «someone will find the file»; and the renderer change exists precisely because a silent skip is #warning-nobody-reads (scripts/render-harness-config.mjs:421). dual-implementation-discipline.md:6 — the hook carries @cc-only-rationale with a real reason (PreCompact ∉ ZCODE_EVENTS, scripts/render-harness-config.mjs:46) plus @dual-pair: hook-lang-i18n for the pack it sources; §3 audience triage is done explicitly (framework-internal, not installer-delivered) rather than defaulted. no-paid-llm-in-ci.md:1 — deterministic bash + jq, zero inference. language-discipline.md:1 — the residue's own prose is English (category 1); the recap marker stays lang-pack-sourced (category 3), and one test asserts the ru/en split in both directions. build-first-reuse-default.md:1 — the capability is REUSE-of-verdict: SSOT #108 already carries ADOPT for this hook class, so no new BUILD verdict was minted; the two departures from its sketch are recorded in the row. doc-authority-hierarchy.md:2 — the new research-patch carries a Type/Owner/Feeds header.

§1.7 Backward-check applied

Class of this change = «a CC hook is added on an event ZCode cannot express». Surfaces where that class occurs, enumerated from .ai-factory/harness-model.json + ZCODE_EVENTS rather than from this PR's own diff: (a) SubagentStartinject-subagent-digest, census row 16 — already classified cc-only with a §4 rationale and a documented PreToolUse:Agent fallback, SWEPT-CLEAN, and it is now also named by the new renderer note; (b) SubagentStopwarn-subagent-report, row 19 — has a 4D-hybrid parity variant (#1046), so the note naming it is informational, not a gap, SWEPT-CLEAN; (c) WorktreeCreateworktree-setup, row 20 — cc-only, not in default settings, SWEPT-CLEAN; (d) the rollup arithmetic in .claude/rules/zcode-parity-doctrine.md:105 and the tier-table counts at :114/:115 — both restate the census totals and both were stale the moment row 21 landed, GAP-FOUND → fixed in this PR (Total 20→21, cc-only 2→3, «all 20 hooks»→21, «Two rows»→«Three rows», three CC-only events→four); (e) the plugin-twin population, tests/plugin/hook-paths.test.sh — generation correctly produced no twin for the new hook (ls plugin/hooks/ | grep -i precompact → empty), which is the right answer for an event the plugin channel cannot carry, SWEPT-CLEAN; (f) the install-sh baselines — the hook is framework-internal, so the fingerprints stayed byte-identical (15/15), SWEPT-CLEAN. No surface superseded.

Parked

Should the residue writer ship to consumers? It is framework-internal today (audience triage recorded in the hook header). The consumer-facing consequence is that the /pipeline §1 line, which IS shipped, reads a residue a consumer never gets — inert and harmless (2>/dev/null), but asymmetric. Widening is a separate decision with its own installer + baseline consequences, not a side effect of this stage.

Still open in the contour: the first live chip click has still never happened — it would close F3 and F9 by observation alone. F4 and F7 remain open.

…as wired (D8)

A compacted session used to leave nothing behind: the D7 context-arm's debounce
flag stays spent across an auto-compact, so a session re-climbing after one got no
second nudge and no record of what it had been doing. D8's answer is a hook that
WRITES the residue itself — a non-blocking PreCompact gives the model no execution
window, so "remind the model to write a handoff" is undeliverable as specified.

The bench ran FIRST, per D8's ordering, and its honest outcome is recorded rather
than rounded up: the contract is proven (15 cases, a seeded-break paired-negative,
one real CC transcript, and end-to-end reader/writer agreement), while delivery is
unfired on BOTH halves — registration is agent-blocked by settings.json's own
deny-list, hooks snapshot at session start, /compact is an operator action, and
auto-compaction cannot be induced (0 hits for isCompactSummary across 60 sampled
transcripts). D8 pre-accepted exactly this split.

Two deviations from D8-as-written, both recorded in the ADR rather than silent:

- The `auto` matcher is dropped. Registering on auto alone makes the only half a
  human can trigger on demand unfireable by construction, and a manual /compact
  discards the same context. The writer is matcher-independent (asserted).
- The renderer surface turned out to be emitPlugin, not emitClaude. emitClaude
  already passes any event through; the real gap was that emitPlugin `continue`d
  silently past every event outside ZCODE_EVENTS, so a hook on an inexpressible
  event vanished with no trace. It now names each dropped event in a note.

The residue has a NAMED reader — a residue nobody reads is #warning-nobody-reads.
It rides the /pipeline §1 Step-1 injection, and writer and reader resolve the
directory through the SAME print-orch-home.sh, so they agree by construction
rather than by two copies of one rule.

Registration is deliberately NOT in this PR. settings.json and the SSOT model must
move in ONE commit (measured: editing only the SSOT gives "drift vs SSOT", exit 1),
and settings.json is agent-uncommittable — so that commit is the operator's.

Prior-art: prior-art-evaluations.md#108 (Claude Code orchestrator mechanical-gate
hooks, verdict ADOPT) — this is that row's PreCompact item, built rather than
re-derived: the "save wave-state before compaction" shape is taken from the entry,
and the two places this implementation departs from its sketch (per-session file
in the resolved orch-home instead of one clobbered .claude/session-state.md; the
hook writes rather than reminds) are recorded in the row itself, whose Last
reviewed date is updated in this same commit. The row's other four items are
untouched, and its DECISION-NEEDED note is why registration stays operator-landed.
@artyhoo
artyhoo merged commit 2ffd0e5 into staging Aug 17, 2026
46 checks passed
@artyhoo
artyhoo deleted the claude/suspicious-goldberg-d15ff2 branch August 17, 2026 11:52
artyhoo added a commit that referenced this pull request Aug 17, 2026
…ry agents edit went CI-red (#1437)

## Summary

`plugin/agents/*.md` must be byte-identical to its `agents/` source — asserted by `packages/core/principles/24-plugin-manifest-integrity.test.ts:267` in CI. But `scripts/generate-plugin-twins.sh` only ever walked `.claude/hooks/*.sh`, so the agents twins were maintained by remembering to run `cp`, and `.husky/pre-commit:169-174` auto-regenerated only the hooks half. **Detection was never the gap; regeneration was** — every `agents/*.md` edit went CI-red until someone did the copy by hand. PR #1430 hit it live. This adds the agents population to the generator, gives it real coverage, and wires that coverage into CI.

## Changes

- **`scripts/generate-plugin-twins.sh`** — second pass: `agents/*.md` → `plugin/agents/*.md` as a plain byte-copy. Skips sources with no existing twin (16 of 19 agents are deliberately untwinned, so the generator never invents one) and skips the write when already identical, so a clean tree is a true no-op rather than an mtime churn on every pre-commit.
- **`tests/plugin/twin-generation.test.sh`** — sandbox arms exercising the new pass: drift gets re-synced, an untwinned agent is left alone. Runs against a temp tree via `CLAUDE_PROJECT_DIR`, so the real tree is untouched.
- **`scripts/run-local-ci-sweep.sh`** — registers the gate in the local sweep's `gate_table()`. **CI caught this, I did not:** the first push went red on `run-local-ci-sweep-coverage.test.sh`, which reconciles the sweep's gate table against this workflow and named the uncovered command verbatim — so wiring a CI step while leaving the sweep blind made its «green locally ⇒ green in CI» promise false. Registered **literally** rather than as a `for t in tests/plugin/*.test.sh` loop, so a future test added to that dir but wired to no CI step stays out: the sweep predicts CI, it does not invent gates (same reasoning as the derived `script-selftests` row). Its `agents/` trigger is load-bearing beyond prediction — the suite runs the generator, so a sweep after an `agents/*.md` edit re-syncs the twins, making it a *partial* stand-in for the pre-commit arm below (partial: a sweep is invoked deliberately, pre-commit fires on its own).
- **`.github/workflows/audit-self.yml`** — **wires that test into CI at all.** It had existed since the S6 generator shipped while being invoked by no workflow step and no pre-push section: the generator's only coverage was a test nobody ran. Placed last in `principles-meta-tests` because the suite mutates the working tree by design (arm 1 runs the generator), so no later step in the job can observe a half-regenerated tree.

**No marker, no header for the agents pass — both are consequences, not omissions.** Principle 24(d) compares *bytes*, so `sed`/`manual` modes are unreachable by construction and a marker would be dead code whose only effect is a red gate; and these are markdown files whose YAML frontmatter must open on line 1, so injecting an `AUTO-GENERATED` comment would corrupt them. Widening that contract means editing 24(d) first. The rationale is in the script header so nobody "restores symmetry" with hooks later.

**One arm was written, measured, and deleted rather than shipped.** An in-tree byte-identity check looked obvious, but arm (1) runs the generator on the real tree *first*, so the check can never fail: seeding drift into `agents/review-sidecar.md` and running the file reports «byte-identical» and silently repairs the tree (verified, not reasoned). That is decoration — the shape of a check with no failing input. Real-tree drift detection stays with 24(d), where it *can* fail. The comment at `tests/plugin/twin-generation.test.sh` records why, so the dead arm does not come back.

## Prior-art consult

- [x] `Prior-art: skipped` — this extends an existing in-repo generator to a second population that already had a CI contract. No new capability, no new dependency, no new file under `packages/`, no engine introduced.
- [x] No new capability area, so no SSOT entry and no `Last reviewed` bump.

## Test plan

- [x] `tests/plugin/twin-generation.test.sh` — **23/23 pass**, and it goes **RED (22 pass / 1 fail)** when the agents `cp` is stubbed out. The negative is end-to-end through the generator, not a predicate assertion.
- [x] Generator idempotent: a run on a clean tree leaves `git status` empty; a seeded drift in `agents/review-sidecar.md` is re-synced to byte-identical on the next run.
- [x] `shellcheck` clean on both shell files. `audit-self.yml` parses as YAML; my step verified to be the last in its job, and `principles-meta-tests` is already in `ci-success.needs`.
- [x] Principles **36** (ci-needs completeness) and **38** (vitest↔CI coverage) green — a step was added to an already-needed job and no vitest surface was added.
- [x] Full `test:principles` with **file parallelism disabled**, post-merge — **43 files / 409 passed, 1 skipped**. See the flake note below for why that qualifier is the honest one.
- [x] `.husky/pre-push` green on the successful push (lychee 336 OK / 0 errors, `check-skill-drift` PASS). Recorded for honesty: an earlier push attempt on the same code failed on this very flake plus a `spawnSync npm ETIMEDOUT`; I re-ran it after diagnosing the cause, not before.
- [x] Post-merge integrity re-checked, not assumed: the new CI step survived at `audit-self.yml:518` and is still last in its job, `principles-meta-tests` is still in `ci-success.needs`, and `SNAPSHOT_MODE=compare` is 15 pass / 0 fail.
- [x] **A pre-existing flake was diagnosed rather than waved off, and the diagnosis is controlled.** Full runs with default parallelism intermittently fail on `05-manifest-ssot` (renders RULES.md), `21-agnosticism-conformance` (writes `conformance-record.tsv`), `31-rule-channel-declaration` (glob parity), `11-build-first-reuse-default` F1 (walks git history) — a failure count that moved 2 → 3 → 4 across runs of *identical* code. Each passes in isolation (`05`+`21`+`31` → 39/39; `11` → 14/14), `--no-file-parallelism` is fully green, and **a control run on clean `origin/staging` with no part of this diff applied is also red** (`21`, 1 failed / 42 passed). So: a pre-existing race between test files over shared artifacts, not a regression here. Falsifier, stated plainly: wrong if a repeated control on untouched `staging` comes back reliably green while this branch stays red.
- [x] **Not fixed here, deliberately:** `vitest.config.ts` is untouched. Setting `fileParallelism: false` would change suite-wide behaviour for one PR's convenience, and the actual culprit pair is not yet identified — that wants its own diagnosis, recorded as an observation below rather than patched in passing.
- [x] Two method corrections worth recording, since both produced wrong intermediate readings: one count was taken off an **unfinished** vitest run (`❯` is a live list, not a summary), and two counts came from runs I had made concurrent with a targeted run — interference I created myself, not the suite's.
- [x] **Merged `origin/staging` forward** (5 commits: #1425, #1431-#1434) — the control run surfaced that this branch's base had gone stale (staging had 43 principle files / 410 tests vs this branch's 42 / 402). Clean merge, no conflicts. Re-verified afterwards: `twin-generation` 24/24 (the merged-in `.claude/hooks/precompact-residue.sh` has no twin, so the generator correctly skips it), generator still idempotent.
- [x] `audit-self.yml` was **already** prettier-unclean in staging and sits outside the `shipped-prettier` gate's scope — deliberately not reformatted, to keep this diff to one concern.

## Provenance

n/a — non-stage follow-up to the observation parked in PR #1430; no kickoff, no aif substrate, in-session.

## Review findings

n/a — non-stage PR. Own cold read before commit found the decorative arm described above; the twin drift that started all of this was found by principle 24(d), not by reading.

## Fidelity verdict

FIDELITY: skipped — non-stage infrastructure PR, no kickoff or stage acceptance contour applies to it.

## Parked questions

**(1) The `.husky/pre-commit` arm is NOT in this PR, and that is the piece that makes regeneration fully hands-off.** `.husky/**` is the enforcement layer — maintainer-owned and read-only for session agents per the CLAUDE.md Artifact Ownership Contract. Until a maintainer applies the patch below, the workflow is one command (`bash scripts/generate-plugin-twins.sh`) instead of a hand-copy, and 24(d) still backstops in CI:

```diff
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
 # ── Plugin twin regeneration ──────────────────────────────────────────
-# When .claude/hooks/*.sh changes, regenerate plugin twins and re-stage.
+# When .claude/hooks/*.sh or agents/*.md changes, regenerate plugin twins and re-stage.
 # Spec: .ai-factory/plans/zcode-parity-s6-twin-generator.md (Stage 6).
-HOOKS_STAGED=$(echo "$CHANGED" | grep '^\.claude/hooks/.*\.sh$' || true)
-if [ -n "$HOOKS_STAGED" ]; then
+#
+# The agents/ arm was added 2026-08-17: plugin/agents/*.md twins were guarded only AFTER
+# the fact by principle 24(d) (byte-identity, CI) while plugin/hooks/ regenerated here
+# automatically — so every agents/*.md edit went CI-red until someone remembered a manual
+# copy. PR #1430 hit exactly that. Detection was never the gap; regeneration was.
+HOOKS_STAGED=$(echo "$CHANGED" | grep '^\.claude/hooks/.*\.sh$' || true)
+AGENTS_STAGED=$(echo "$CHANGED" | grep '^agents/.*\.md$' || true)
+if [ -n "$HOOKS_STAGED" ] || [ -n "$AGENTS_STAGED" ]; then
   if bash "$REPO_ROOT/scripts/generate-plugin-twins.sh"; then
+    # Re-stage both twin dirs regardless of which source fired: the generator is a single
+    # idempotent pass over both populations, and staging a dir it did not touch is a no-op.
+    # Narrowing to the fired arm would drop a twin when one commit edits a hook AND an agent.
     git add plugin/hooks/ 2>/dev/null || true
+    git add plugin/agents/ 2>/dev/null || true
   else
     echo "❌ generate-plugin-twins.sh failed"
     fail=1
   fi
 fi
```

**(2) Observation, deliberately NOT fixed here (no drive-by):** there is no gate reconciling `tests/**/*.sh` against CI invocation. Principle 38 covers the vitest↔CI pair only, so a stranded shell test is invisible to it — which is exactly how this one sat unrun since S6. This PR wires the one file it needed; the general gap wants its own decision.

**(3) Observation — the principles suite has a real inter-file race, measured here and untouched:** with default parallelism, `05-manifest-ssot` / `21-agnosticism-conformance` / `31-rule-channel-declaration` / `11-build-first-reuse-default` fail intermittently in varying combinations; all pass in isolation; `--no-file-parallelism` is green; and clean `origin/staging` reproduces it. Since `05` renders RULES.md and `21` writes `conformance-record.tsv`, the likely shape is two files racing on the same on-disk artifact, but I did not identify the exact pair. This is load-bearing beyond convenience: it makes `.husky/pre-push` fail non-deterministically (it hit this PR's own push, alongside a `spawnSync npm ETIMEDOUT`), which trains people to re-run the gate rather than read it. Worth its own task.

## §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 is really about: an unrun test is `#warning-nobody-reads` in its purest form — a check whose failure mode is «nobody looked», since no channel could ever surface it. §1 requires a load-bearing check to be a deterministic gate at the *earliest reachable channel*; wiring it into `audit-self.yml` supplies clause (a), and deleting the decorative in-tree arm removes a check that could not fail. **`README.md#why-this-exists`** invariant 4 (earliest reachable channel, CI = last resort) is partially served and honestly short: the earliest channel for regeneration is pre-commit, which I cannot edit — hence parked question (1) rather than a silent claim of full coverage. **`.claude/rules/no-paid-llm-in-ci.md`** — the new CI step is `bash`, zero API-billed calls. **`.claude/rules/build-first-reuse-default.md:57`** — REUSE, and cheap by that section's own cost gate: no dependency, no code-module, no standing infra; the generator and its test already existed. **`.claude/rules/dual-implementation-discipline.md:191`** — `#two-prompts-drift` is the anti-pattern the twin contract exists against; this PR strengthens its mechanical side rather than adding a second prompt. **`.claude/rules/ci-tool-pinning.md`** — no tool install added, so §1 Rule A does not fire.

### §1.7 Backward-check applied

Class of this change = **populations in `plugin/` carrying a byte-identity twin contract**. Enumerated mechanically, not from the diff: `ls -d plugin/*/` yields five, and I checked each rather than assuming two. `plugin/hooks/` (21 files) — SWEPT-CLEAN, already generated, and its `manual`-marker escape (`packages/core/principles/24-plugin-manifest-integrity.test.ts:278`, the `inject-matching-rule` core-logic arm) is untouched by this change. `plugin/agents/` (3 files) — GAP-FOUND → generated here. `plugin/skills/` (3) and `plugin/commands/` (1) — **verified NOT twins**: none of `getff`, `installing-enforcement`, `using-getff` has a same-named `.claude/skills/<n>/SKILL.md` source, so they are independent plugin artefacts checked only for frontmatter validity (24 V3/V5), and the absence of a generator arm there is correct rather than a missed sibling. `plugin/install/` (1) — `fetch-and-wire.sh`, gated on `RAT_PLUGIN_VERSION` matching `package.json`, not a copy of anything. Adjacent surface confirmed out of scope: the vendored `runtime-bridge` copies are byte-identical *by construction* per the CLAUDE.md capability-commit carve-out (`CLAUDE.md:36`), with no source-of-truth to regenerate from. No rule superseded; principle 24 is read, not edited.

---

Prior-art: skipped — extends an existing in-repo generator to a second population that already carried a CI byte-identity contract; no new capability, no new dependency, no new file under packages/, and no engine introduced.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant