Skip to content

research(mutation-discipline): Stage 1 audit — Stryker scores + B/C/D verdict - #219

Merged
artyhoo merged 3 commits into
stagingfrom
research/mutation-discipline-audit-v2
May 25, 2026
Merged

research(mutation-discipline): Stage 1 audit — Stryker scores + B/C/D verdict#219
artyhoo merged 3 commits into
stagingfrom
research/mutation-discipline-audit-v2

Conversation

@artyhoo

@artyhoo artyhoo commented May 25, 2026

Copy link
Copy Markdown
Owner

Summary

Stage 1 R-phase of the mutation-discipline-umbrella (kickoff at .claude/orchestrator-prompts/mutation-discipline-umbrella/kickoff.md). Closes Stage 1 → Stage 2 gate.

Single file added: docs/meta-factory/research-patches/2026-05-25-mutation-discipline-audit.md (251 LOC, 2 commits — original + a Stop-hook-surfaced fix for §A.3.4 off-by-one).

Stage 1 deliverable = audit-only. No mechanism, no code, no dependency. The patch records per-file Stryker mutation scores and recommends B/C/D admissibility.

Findings (one-line each)

  • §A.1 — 1214 mutants across 8 TS files, 88.6 % aggregate kill rate; 2 ESLint files strictly <80 % (no-unsafe-zod-parse.ts 56.5 %, no-direct-time-randomness.ts 75.9 %) + 1 file just above the threshold (require-otel-span.ts 81.1 %) which §A.2.3 calls out as a Stage 3 C candidate.
  • §A.2 — survived-mutant categorisation: ~3–5 real-gap (the // audit:exempt early-return branch is incompletely covered across multiple selectors); ~40+ T-MUT-B equivalent mutations on ESLint metadata + message text. Low kill rates are largely a structural artifact of mutating ESLint rule files, not a critical-coverage emergency.
  • §A.3 — T10 full sweep: 31 .sh files repo-wide (kickoff §3 implied 9). All 9 .claude/hooks/*.sh already have M.4 paired-negative tests; none are continuously mutation-tested. Stage 4 D-targets (5 new + 1 dup) untested, 422 code-LOC × 62 branches.
  • §A.4 verdict:
    • B (bash mutator) = GO, conditional on Stage 2 B.1 prior-art search (DeepWiki + WebSearch) BEFORE any line of bash-mutator.ts. Default verdict if upstream surfaces: ADOPT/ADAPT, not BUILD.
    • C (principle 02 content-level extension) = GO, conditional on B merged (so new bash tests inherit content-strength check from day 1).
    • D (cover remaining .sh) = MIXED — GO for D.1–D.5 + D.6 R-step. Do NOT expand to the 16 A.3.4 future-work .sh files (arithmetic: 31 total − 9 .claude/hooks/*.sh − 6 Stage 4 D.1–D.6 = 16).
  • §A.5 — recursive self-application (T15): all numeric claims sourced from snapshotted JSON reports, not paraphrased.
  • F1 stop-condition NOT met → umbrella proceeds.

Self-correction in this PR (Stop-hook-surfaced)

  • Commit 1 (893766d): original Stage 1 patch.
  • Commit 2 (5c88421): fixes §A.3.4 off-by-one (header + recommendation line both said "17", bullet list enumerates 16) and corrects the corresponding line in this PR body. No verdict change (the count error was in the out-of-scope future-work section, not a verdict input).
  • The PR body also previously said "3 ESLint files <80 %" while listing require-otel-span.ts at 81.2 %; corrected above to "2 strictly <80 % + 1 just above". §A.2.3 of the patch was always correct on this — only the summary needed alignment.

Stage 1 → Stage 2 gate (per kickoff §3)

$ ls docs/meta-factory/research-patches/2026-05-25-mutation-discipline-audit.md
docs/meta-factory/research-patches/2026-05-25-mutation-discipline-audit.md
$ echo "Stage 1 finding doc exists -> Stage 2 admissible"
Stage 1 finding doc exists -> Stage 2 admissible

Method notes (T-discipline applied)

  • T1 (sampling shallow) — both Stryker configs ran on FULL declared scope (8 TS files, 1214 mutants).
  • T3 (no prose-only) — every numeric claim cites /tmp/stryker-A-report.json or /tmp/stryker-audit-report.json (snapshots) + corresponding raw log; per-file survival breakdown in §A.2.1 is a line-by-line read of no-unsafe-zod-parse.ts against the survived-mutant locations from jq extraction.
  • T10 (full population) — §A.3 enumerates find . -name "*.sh" = 31 files BEFORE any coverage claim; kickoff Stage 4's 5 targets are a subset.
  • T11 (prior art) — Stage 1 does NOT pre-empt Stage 2 B.1's prior-art question. §A.4.1 explicitly defers it.
  • T-MUT-B (high score ≠ bug-free test) — §A.2 distinguishes equivalent-mutant from real-gap rather than treating «<80 %» as automatic theatre.
  • T15 (self-application) — §A.5 explicit; commit 2 is itself a T15 demonstration (Stop-hook re-verification caught the off-by-one).

Test plan

  • Stryker config A green at 90.69 % (eslint-rules + hooks, 57 s wall)
  • Stryker config B green at 87.25 % (audit-self, 45 s wall)
  • Numeric sanity: jq '[.files[].mutants | length] | add' on snapshot = 720 (matches §A.1.1 footer); killed = 649 (matches table)
  • Baseline npm test -ws --if-present = 904 / 909 passing + 1 pre-existing fail at hooks/pre-push.test.ts:59 (memory-documented, unrelated)
  • Markdownlint clean (2 fenced-code-language errors fixed pre-push)
  • Pre-push hook green on both commits (Stage 1 → Stage 2 gate verified; principle tests + audit-self passed)
  • Stop-hook re-verification on every numeric claim before close (count of 8 TS / 3 ESLint / 31 .sh / 6 M.4 tests / line content of pre-push.test.ts:59 all re-fetched at author-time, surfaced the 2 off-by-one's now fixed)
  • Reviewer cold-pass over Stage 1 findings

§1.7 Forward/Backward-check applied

Forward — the patch's §1.7 section cites compliance with .claude/rules/no-paid-llm-in-ci.md §1, build-first-reuse-default.md §1, phase-research-coverage.md §1.7, ai-laziness-traps.md §2, parallel-subwave-isolation.md §1, doc-authority-hierarchy.md §3, recommendation-laziness-discipline.md §3. Worktree at /Users/art/code/rules-as-tests-aif-mutation-audit-2 per parallel-subwave-isolation.md §1.

Backward — the patch's §1.7 section confirms M.4 PRs #195/#196/#197/#198/#199/#200 are not silently superseded, Stryker config files are untouched, and T-MUT-A/T-MUT-B remain kickoff-scoped (not yet promoted to ai-laziness-traps.md §2 canonical catalogue — needs ≥2 wave-specific equivalents per §5 promotion criterion).

Diff scope

  • docs/meta-factory/research-patches/2026-05-25-mutation-discipline-audit.md — new file, 251 LOC across 2 commits. Folder-level authority per doc-authority-hierarchy.md §5 applies; per-file header included as escape hatch (research-patches/ folder explicitly permits per-file headers).

No other files touched. Not a capability commit per CLAUDE.md definition (no new dependency, no new file ≥50 LOC under new subdirectory, file is documentation not code).

Prior-art trailer on both commits: skipped — R-phase research patch + a follow-up off-by-one correction; no new capability or dependency shipped; admissibility verdict only per §A.4. Stage 2 B.1 will register prior-art for any bash-mutator candidate.

artyhoo added 3 commits May 25, 2026 11:22
… verdict

Stage 1 R-phase of the mutation-discipline-umbrella (kickoff at
.claude/orchestrator-prompts/mutation-discipline-umbrella/kickoff.md).

Adds docs/meta-factory/research-patches/2026-05-25-mutation-discipline-audit.md:

  §A.1 — per-file Stryker scores from both configs (8 TS files, 1214 mutants,
         88.6% aggregate kill rate; 3 ESLint files <80% but mostly equivalent-
         mutant noise on metadata)
  §A.2 — categorise <80% survived mutants: ~3-5 real-gap (audit:exempt branch
         on multi-selector rules), ~40+ T-MUT-B equivalent on ESLint metadata
  §A.3 — full T10 .sh population (31 files repo-wide; 9 hooks all M.4-tested
         but NO continuous mutation protection; 5 untested Stage 4 targets)
  §A.4 — VERDICT: B = GO (gated on B.1 prior-art), C = GO conditional on B
         merged, D = MIXED (GO for D.1-D.5 + D.6 R-step; do NOT expand to
         A.3.4 future-work surface)
  §A.5 — recursive self-application (T15)
  §1.7 — forward+backward check
  F1 stop-condition NOT met → umbrella proceeds

Snapshots of Stryker JSON reports + raw logs in /tmp/ (ephemeral); numeric
claims verified against /tmp/stryker-A-report.json (sanity: 720 total / 649
killed matches §A.1.1 footer).

Prior-art: skipped — R-phase research patch documenting per-file Stryker mutation scores; no new capability or dependency shipped; admissibility verdict only per §A.4. Stage 2 B.1 will register prior-art for any bash-mutator candidate.
…metic check

Self-verification surfaced an off-by-one error in §A.3.4: the bullet list
enumerates 16 files (2 install scripts + 1 audit-ai-docs.sh + 2 husky
templates + 1 preset audit + 2 ci-success-gate + 5 tests/hooks + 3 skill
helpers = 16), but the header + recommendation line both said "17".

Fixed both occurrences (header line 164, recommendation line 176) and added
an explicit arithmetic check: 31 total − 9 .claude/hooks/*.sh − 6 Stage 4
D.1–D.6 targets = 16. Verdicts in §A.4 are unchanged (this is a population
count error in the out-of-scope future-work section, not a verdict input).

Prior-art: skipped — small fix-up commit correcting an off-by-one in §A.3.4 of the R-phase research patch; no new capability, no dependency, no shipping artefact.
…attribution + ArrayDeclaration wording

MAJOR (cold-review #219):
- §A.2.1 L32:48 row: was misattributed to '// audit:exempt' StringLiteral. Actually `?? ''`
  defensive fallback on line 32 (the audit:exempt branch on line 33 is Killed per Stryker JSON).
- §A.2.2 L64 / L44:64: was described as lineExempt early-return ConditionalExpression. Actually
  line 64 is the `typeof node.source.value !== 'string'` typeguard on the ImportDeclaration
  selector; NoCov 44:64 is the StringLiteral on the `?? ''` fallback in lineExempt arrow body.
- Net A.2 finding: behaviour-relevant gap reframed from "exempt-branches" to "defensive `?? ''`
  fallbacks on out-of-bounds line index + ImportDeclaration typeguards".

MINOR (cold-review #219):
- M-1: ArrayDeclaration `defaultOptions: []` rows reworded from "no-op" to "behavioral-equivalent"
  with explanation (Stryker replaces with `["Stryker was here"]` literal; rule doesn't read
  `context.options` so behaviour unchanged).
- M-2: §A.5 caveat added — paraphrasing-vs-re-reading gap acknowledged, mitigation = Stage 2 B.4
  dogfood scope should run mutator on test files themselves.

UNCHANGED (per kickoff): §A.1 numbers, §A.3 .sh counts, §A.4 B/C/D verdicts.

Source verification commands embedded in updated table rows.

Prior-art: skipped — research-patch text correction, no new capability commit
@artyhoo
artyhoo merged commit 4e2703b into staging May 25, 2026
22 checks passed
artyhoo added a commit that referenced this pull request Jun 2, 2026
Umbrella closure per CLAUDE.md "Umbrella closure convention" — Final PR #383
(D.6 drift-check, last residual) merged to staging. All stages shipped:
A audit #219 / B bash-mutator #366+#373 (dogfood gate fired) / C tests-of-tests
#378 / D #381 + D.6 dup R-phase #382 + D.6 drift-check I-phase #383.

done.md is the priority-score.sh Layer C3 completion marker (gitignore:14
un-ignores */done.md).

Prior-art: skipped — completion marker (done.md), no new capability/dependency/code.
artyhoo added a commit that referenced this pull request Jun 13, 2026
…+ runtime-bridge (134 commits) (#488)

* feat(hooks): SubagentStop REPORT-schema WARN (non-blocking, #108 candidate-3) (#339)

Ship .claude/hooks/warn-subagent-report.sh — scans the finishing subagent's
output for canonical REPORT sections (VERIFY, Confidence, ATTN); emits a
⚠ stderr warning + exit 0 (non-blocking) when any are missing.

Maintainer chose WARN over block (exit 2) — judgment target, not gateable
(#gate-where-judgment-needed, rule-enforcement-channel-selection.md §1).

Contract verified DUAL-CHANNEL 2026-06-01:
  Channel 1 — WebFetch code.claude.com/docs/en/hooks: SubagentStop fields
    include agent_type; exit 0 = non-blocking; stderr shown to user.
  Channel 2 — DeepWiki anthropics/claude-code: agent_transcript_path +
    last_assistant_message (since CC 2.1.47) confirmed by name + quoted.

Two read paths (B1 — scan-nothing trap avoidance):
  1. last_assistant_message if non-empty.
  2. agent_transcript_path JSONL, reusing end-of-turn-reminder.sh:30-41.
Noise guard: skip Explore agent_type + require standalone REPORT-cue label.

Paired test: 6 cases (positive-A/B + negative + negative-2 Explore +
non-report prose + exit-code invariant). All 6 green. Pre-existing staging
failures (pre-push.test.ts + plan-currency-check.test.ts) unaffected.

settings.json wiring snippet in PR body (maintainer lands, not committed).

Prior-art: prior-art-evaluations.md#108 (SubagentStop REPORT-schema gate — candidate-3 of #108, shipped as WARN per maintainer decision 2026-06-01; block dropped — judgment target, #gate-where-judgment-needed).

* docs(research-patches): satellite-feature-harvest — own-stack-first harvest + reverse-sweep (form B) (#328)

Own-stack-first sweep: Claude Code natively ships the remote/scheduled/parallel-session
cluster (Remote Control, Routines, Channels, agent-view, Slack) that prior SSOT rows
credited to companions. 15 capability clusters triaged → 4 adopt-now (Remote Control,
Routines via /schedule, Channels(Telegram), background agent-view) + 3 defer + 8 skip.
Reverse sweep: 4 criterion-zero relationships, no shipped artefact retired (T-SFH-D —
runtime-bridge questions.ts kept; Channels is session-open-only). +1 SSOT row #100.

Prior-art: skipped — research-patch only, no new capability (the BFR own-stack-first survey is the patch content itself).

* chore(hooks): wire pending CC hooks in settings.json (SubagentStart/Stop + runtime-bridge) (#340)

Activates three already-merged, already-committed hook scripts that were
running locally but never wired in the shared config:
- PostToolUse Write|Edit|MultiEdit -> runtime-bridge-dispatch.sh (#313)
- SubagentStart -> inject-subagent-digest.sh (#108 companion-adoption-iphase)
- SubagentStop -> warn-subagent-report.sh (#108 candidate-3, #339)

All hook scripts already exist in staging; this only activates them.
Config-only wiring, not a capability commit.

* docs(book): SubagentStop block-vs-warn — flip open-node DECISION-NEEDED → решён=WARN (#339) (#341)

#339 (SubagentStop REPORT-schema WARN, non-blocking) merged to staging as
0596a64 — the block-vs-warn decision is resolved in favour of WARN. Hook
remains INERT (no SubagentStop in settings.json) until the maintainer lands
the snippet, so the node stays half-open: decided, not yet wired.

- v1 (technical): chapter XXII + epilogue June-1 open-nodes list — cite #339
- v2/v3 (literary): keep allegory, no PR number — «нрав выбрали, в строй не поставили»

Prior-art: skipped — doc edit only, no new capability (book open-node status sync after #339 merge).

* refactor(hooks): remove end-of-turn factual-claim detector — net-negative sentry, dropped by evidence (#342)

The claim-scan (numeric / file:line / negative-existence → re-verify nudge) in
end-of-turn-reminder.sh measured recall ≈0.43 + precision ≈0.20-0.25 (cry-wolf)
in the instruction-compliance pilot (2026-05-21). A sentry that misses most real
claims and false-fires on ~75% of what it catches is the #discipline-theatre the
project hunts. Maintainer decision 2026-06-01: «не нужен раз не работает».

Same class + outcome as the recommendation-laziness narrow-B stop-scan dropped at
FP 84% (#210): drop on FP evidence, keep the always-on H1 reminder.

Surfaces:
- hook: remove claim-scan block + has_claims/claim_count refs in idle-guard,
  trigger, glance-line, Branch D, append block. Trigger = long_text||asked;
  branches A/B/C only. Recap + question-check survive. bash -n + shellcheck clean.
- tests/hooks/eot-claim-scan.test.sh: deleted (185 LOC, all claim-scan) + CI step.
- packages/core/hooks/end-of-turn-reminder.test.ts: drop Branch-D + code-fence
  precision cases, add paired-negative «short claim turn now silent». 13/13 green.
- research-patch documenting the evidence-based removal (scope annotation, §1.7).
- book epilogue ×3: the «искатель утверждений» debt closed by removal, not fix.

A short factual-report turn with no question now stays silent (was Branch D) —
the intended consequence; that nudge was the cry-wolf.

Prior-art: skipped — removal of an own-built net-negative mechanism, no new capability (cites instruction-compliance-empirical pilot + narrow-b-benchmark #210 precedent in the research-patch).

* feat(runtime-bridge): harvest — deterministic aif-task to PR egress leg (#343)

aif-handoff ends a task at "committed on a local feature branch" with NO push
and NO PR-creation in its autonomous (HANDOFF) path — verified 2026-06-01
against the running containers (zero PR-create / branch-push anywhere in its
autonomous flow; its commit/push lives only in an LLM-driven /aif-commit
runtime). So committed work strands inside aif's checkout, never reaching a
reviewable PR on the trunk. harvest is the missing return leg.

- src/harvest.ts: pure DI'd core harvestTask — guards (terminal status,
  branchName present) BEFORE any side-effect; ordered push then createPr then
  auto-merge, fail-fast (PR error never arms auto-merge).
- src/cli/harvest.ts: real wiring — docker-exec push from aif's container
  (holds the commit + working creds), host PR-create against staging, native
  auto-merge; graceful printed manual-fallback if docker absent.
- test/harvest.test.ts: paired-negative (non-terminal / no-branchName must NOT
  push or PR; PR-fail must NOT arm auto-merge).
- aifHttp.ts: expose branchName on AifTaskFull (read back by harvest).

Zero LLM by construction (plain git+gh+docker) — unlike aif's own commit flow
which spends a paid claude -p query just to run git. Complies with
no-paid-llm-in-ci.md.

Prior-art: prior-art-evaluations.md#27/#28/#30/#67 — aif-handoff's own runtime
(HANDOFF_MODE / paused / Planner-Implementer-Reviewer / Kanban) is the from-side
this leg bridges, NOT an egress tool: verified it has zero push/PR in its
autonomous path, which is exactly why harvest exists. No upstream
harvest-autonomous-agent-branch-to-PR analog; thin deterministic glue over
gh/git/docker, no PR-creation reimplemented. BFR verdict: BUILD (bridge egress leg).

* feat(meta-orch): run-helper.sh completion-barrier wrapper (TDD, stage 1) (#344)

Build .claude/skills/meta-orchestrator/helpers/run-helper.sh — a trivial bash
exec-wrapper that runs a target helper as a child and ALWAYS appends a terminal
END-trailer to stdout:

    === <helper-name>: END rc=<exit-code> (lines=<stdout-line-count>) ===

so a session reading a slow helper's stdout in the background can distinguish
"finished (rc=N)" from "still running / crashed". The trailer is appended by the
parent wrapper, so it fires even when the child exits non-zero or dies
mid-output (a child-side `echo END` would be skipped on `set -e` abort / SIGKILL
/ timeout). Fixes the orchestrator reading a header-only partial file as
"zero results".

HARD CONSTRAINTS satisfied (§2): exit-propagation (capture rc first, exit rc
last — trailer echo never clobbers child status); trailer-on-crash; stdout
verbatim pass-through; `=== `-prefix kept so classify-each-candidate.sh:52
`awk '... && !/^=== /'` strips it (never a spurious candidate).

TDD: paired-negative test at packages/core/skills/run-helper.test.ts (positive
clean-exit + negative exit-1 + SIGKILL crash-mid-output + parse-safety) — 4/4
green. No regression: `git stash -u` A/B shows the 5 neighbouring helper tests
produce an identical pre-existing 18-fail set (env-dependent gh/git harnesses)
with and without this file.

T-BGB-A: the END trailer proves the child FINISHED + its exit code, NOT
semantic content-completeness — kept in test names/comments.

Prior-art: prior-art-evaluations.md — escape hatch: trivial bash exec-wrapper, no upstream analog worth a dependency (BFR BUILD, pre-reasoned). Nearest SSOT neighbour #54 (Aider run_cmd runner) is a different problem-class — exit-code routing in a TS pre-push gate vs. appending a structural completion trailer for a background reader (T16: similar "runner" name, distinct capability).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* bg-helper-completion-barrier-stage2-aif (#345)

* feat(meta-orch): route bg-helper call-sites through run-helper.sh + Layer-1 read-rule (stage 2)

Wire the two background-helper !-fences in meta-orchestrator/SKILL.md
through the already-merged run-helper.sh completion-barrier wrapper:
- §2 Step 1 priority-score.sh
- §2.5 Step 2 dup-detect.sh + inflight-check.sh

Add the Layer-1 read-rule prose after both fences: parse a slow helper's
output only after its task-notification or the `=== <helper>: END rc=<n> ===`
trailer; a header-only read means "still running", not "zero results".
Origin: incident 2026-06-01 (priority-score.sh read at header-only state →
false "zero candidates").

Helper code untouched; the 5 helper tests show identical baseline (18 failed
/ 34 passed both with and without this edit — failures are environmental,
`gh` absent). Wrapped fences valid bash; classify-each-candidate.sh:52 still
strips the `=== `-prefixed trailer.

Prior-art: skipped — SKILL.md call-site wiring only, no new capability (routes existing helpers through the already-merged run-helper.sh wrapper from PR #344).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(meta-orch): SKILL.md back to 600 lines — fold read-rule into §2 Step 1 (DRY)

aif's Stage 2 added two verbatim read-rule blockquotes (+4 lines), tipping
SKILL.md from 598 to 604 over the 600-line mechanical-check cap (real CI fail
on #345). Phase -1 reviewer fix: fold the read-rule once into the §2 Step 1
intro with 'applies to every background-helper fence below', drop the §2.5
duplicate. Both run-helper.sh wraps intact; read-rule discipline preserved, now
DRY. Not a capability commit (markdown edit).

Prior-art: skipped — markdown CI-fix (line-cap trim + DRY), no new capability.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(closure): bg-helper-completion-barrier done.md (final PR #345) (#347)

Last-stage merge marker per CLAUDE.md umbrella-closure convention — priority-score.sh
Layer C3 reads this for DONE detection.

Prior-art: skipped — closure marker file, no new capability (convention bookkeeping).

* feat(coordination): symlink-to-canonical cross-worktree sync (SSOT #110) (#346)

Build scripts/link-coordination.sh (per-file symlinks, adopt-then-link,
conflict-loud, seed on empty CANON), wire it into both worktree helpers
replacing the J5 rsync block, add link-coordination.test.ts (7 tests:
symlink, git-clean, conflict, write-back, paired-negative, adopt, seed),
reconcile worktree-setup-hydration.test.ts (drop rsync NON-DESTRUCTIVE,
retarget paired-negative regex to link-coordination.sh call, assert
symlinks), append SSOT row #110.

Umbrella count confirmed: 124 (kickoff said 123 — stale).

Prior-art: prior-art-evaluations.md#110 (cross-worktree gitignored-SSOT sync via symlink-to-canonical, verdict ADAPT) + research-patch 2026-05-17-cross-worktree-coord-doc-sync.md §5 — I-phase implementation of the settled verdict.

* docs(runtime-bridge): per-command CLI reference README (Option B) (#348)

Operator-facing reference manual for the six runtime-bridge CLI commands
(answer, await, dispatch, harvest, park, questions), one section each with
Usage / Flags / Example, derived from each cli/<cmd>.ts header docblock.
Resolves the parked README-structure fork → Option B (per-command reference).
Markdown only, 285 lines (< 600-line cap), no code changes.

Prior-art: skipped — documentation only, no new capability (operator-facing CLI reference derived from existing docblocks)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(discipline): surface genuine forks via AskUserQuestion (autonomous-default, human-gate on ambiguous only) (#349)

Operating model (maintainer decision 2026-06-01): autonomous by default;
the human-gate fires ONLY on a genuine ambiguous fork. A clear call is
decided and reported, not routed through a question.

- recommendation-laziness-discipline.md §3: fork-surfacing companion to the
  evidence rule — ambiguous forks MUST be surfaced via AskUserQuestion, never
  decided by a silent direct action; ask-question-reminder.sh is the
  discriminator; reworded 'default=surface' -> 'autonomous-default, surface
  only genuine forks'.
- recommendation-laziness-discipline.md §5: new anti-pattern
  #fork-decided-by-silent-action (incident: PR #348 battle-test, an
  explicitly-50/50 README fork auto-decided by running answer.ts directly).
  Judgment rule -> injection-only enforcement, never a gate (#gate-where-judgment-needed).
- end-of-turn-reminder.sh Branch-A: backstop reminder for the
  'decided a fork silently' path (the inverse of the existing offload check).

Hook tests 20/20 green. Origin: 2026-06-01 question-loop battle-test dialogue.

§1.7: forward — complies with rule-enforcement-channel-selection (judgment->injection),
no-paid-llm-in-ci (deterministic .sh + prose), doc-authority (no new heading); backward —
new anti-pattern scope = any-session fork behaviour, no existing rule superseded
(reviewer-discipline §2 is reviewer-scoped), enforced by two already-shipped hooks.

* feat(hook): orchestration-mode aware reminders + brainstorm cue (qloop items 2+6) (#350)

* docs(spec): hook-nudge orchestration-mode design (brainstorm item 2+6)

Design for orchestration-mode-aware behaviour of the two reminder hooks:
marker-gated Bug A fix (drop low-precision fork-regex in-mode) + recap
threshold lowering (b) + brainstorm nudge in ask-question-reminder.sh.
Normal mode stays byte-for-byte. Origin: 2026-06-01 qloop battle-test brainstorm.

Prior-art: skipped — design doc only, no new capability (markdown spec under docs/superpowers/specs/, no dependency/code-module).

* docs(plan): hook-nudge orchestration-mode implementation plan (item 2+6, TDD)

Prior-art: skipped — planning doc only, no new capability.

* docs(spec): add §3.4 aif-pull-channel brainstorm nudge (formatHuman footer)

Recursive-self-application gap from maintainer dialogue: ALL aif-parked
questions (not just I-ask-via-AUQ) must get the brainstorm-first nudge.
Footer ships in item 4's questions.ts PR (file-locality).

Prior-art: skipped — design doc only, no new capability.

* feat(hook): orchestration-mode gating — drop fork-regex (Bug A) + lower recap threshold (recap-b)

Marker .claude/orchestration-mode (mtime TTL); normal mode byte-for-byte.
Bug A: decision-MENTIONS stop false-firing the fork-challenge in-mode (regex
gated on orch_mode=false); real Qs (?/AUQ) still caught. recap-b: recap fires
on short structured status in-mode (threshold 500->200) keeping the markdown
gate so chatter stays silent. 7 new tests + 13 pre-existing green (20/20).
Spec: docs/superpowers/specs/2026-06-01-hook-nudge-orchestration-mode-design.md.

Prior-art: skipped — internal CC hook tuning, no new capability or dependency.

* feat(hook): brainstorm cue in ask-question-reminder (item 6)

Design/strategy forks -> superpowers:brainstorming, not a bare AUQ card.
Always-on prose nudge (judgment -> injection, never a gate). 1 new test (8/8).

Prior-art: skipped — internal CC hook tuning, no new capability or dependency.

* fix(runtime-bridge): surface mid-flight park in questions.ts + brainstorm footer (qloop item 4) (#351)

isParked gains the mid-flight-park case: a task whose blockedReason was wiped
by implementing→review still surfaces via the durable conjunction
(paused===true AND the plan carries the OPEN_QUESTION_ANCHOR). New shared
constant openQuestion.ts is the single SSOT for that anchor (writer park.ts,
reader questions.ts) so the wording cannot drift (dual-implementation-discipline
#two-prompts-drift).

formatHuman appends the §3.4 brainstorm-first footer on a non-empty parked list
(aif-pull-channel companion to ask-question-reminder.sh §3.3); empty case
unchanged. Reproduced from the verified container fix (branch
feature/qloop-realfix-d7585d), host-side, per handoff item 4.

Design SSOT: docs/superpowers/specs/2026-06-01-hook-nudge-orchestration-mode-design.md (§3.4).

Prior-art: skipped — bug fix on existing CLI + 17-LOC shared constant, no new capability/dependency.

* feat(runtime-bridge): commit aif rework leg in harvest (qloop item 3) (#352)

aif commits only on its approve_done && commitOnApprove path; the
request_changes→implementing→done rework path leaves the work uncommitted
(dirty tree, branch == base HEAD), so harvest had nothing to push. Extend the
egress: hasUncommittedChanges → commitAll (templated message, ZERO LLM) before
push. No-op on the normal path (tree already clean). Commit failure is fail-fast
— nothing pushed, operator gets the printed fallback. Real dep guards that the
container checkout is on the task branch before committing.

aif-handoff source untouched (foreign runtime, SSOT #27) — fix lives entirely
on our egress leg, matching harvest's graceful-degradation design.

Prior-art: skipped — extends existing harvest CLI, deterministic git only, no new capability/dependency.

* fix(principles): isolate p13 fixtures to tmpdir (kills p10↔p13 race) + exempt qloop-ux-probe probe (p12) (#353)

Two local pre-push false-blocks found by qloop-ux-probe (neither is a real violation):

F1 — principle 13 wrote its synthetic + c3-decoration fixtures into the REAL
docs/meta-factory/research-patches/ dir, then unlinked them. Under vitest's parallel
file run, principle 10's readdirSync globbed the transient file and ENOENT'd when 13
deleted it → a cross-file race (10 alone = 5/5 pass; full run = flaky ENOENT). Fix:
write the fixtures to os.tmpdir() — checkSection reads content only, PATCHES_DIR
membership was never required. Verified race-free across 3 consecutive full-trio runs.

F2 — principle 12 flagged the gitignored qloop-ux-probe kickoff (a 2-question resume
*test fixture*, not a work-delegating orchestration kickoff) for no AI-traps citation,
failing every local push. It already skips in CI (KICKOFFS_AVAILABLE false). Added to
EXEMPT_LIST with documented rationale.

Full principle suite: 157/157.

* fix(runtime-bridge): park.ts resolves API_BASE_URL so agent can park from inside aif container (#354)

park.ts is the only CLI run from inside the aif agent container, which exposes the
service as API_BASE_URL=http://api:3009 and does NOT set RUNTIME_BRIDGE_AIF_URL.
The old localhost-only default was unreachable there (connection refused) -> park()
got 'fetch failed' -> the agent could not park itself, so park-don't-guess (SSOT #109,
meta-orchestrator §4c) was non-functional on the real autonomous path.

New precedence: RUNTIME_BRIDGE_AIF_URL ?? API_BASE_URL ?? http://localhost:3009.
Host orchestrator (sets neither) still gets localhost; container gets api:3009.

Found by qloop-ux-probe Часть 2 live dispatch (taskId 5e32fcb9): agent correctly
surfaced-not-hacked the park failure, but the task slid implement->review->done at
$3.17 having parked nothing. resolveAifBaseUrl() extracted + unit-tested incl. a
negative guard proven to go red on regression.

* fix(runtime-bridge): don't record dedup on ManualBackend fallback + add --force (Finding B) (#355)

dispatch.ts recorded a dedup entry unconditionally after Step 4 — even when the real
backend threw (e.g. dirty_worktree 409) and it fell back to ManualBackend. That manual
entry (which created no autonomous task) then blocked a legitimate real retry for the
full 24h TTL, with no escape. qloop-ux-probe hit exactly this: after fixing the dirty
worktree, the re-dispatch was refused as "already dispatched (backend=manual)".

Fix:
- shouldRecordDedup(backendName): record only for a real backend success, never 'manual'.
- --force: skip the dedup check to re-dispatch the same kickoff deliberately.
- kickoff path resolved as the first non-flag arg, so --force may sit anywhere.

Three pure helpers, unit-tested incl. a negative guard. Full runtime-bridge suite 102 pass.

* fix(runtime-bridge): questions.ts recovers mid-flight-park question from plan anchor (Finding E) (#356)

A mid-flight park (paused:true + OPEN_QUESTION_ANCHOR in plan, blockedReason wiped by
implementing→review) surfaced in the list — but parkedReason() rendered "(no reason
recorded)" because it only read blockedReason + reviewComments, never the question text
park.ts appended under the plan anchor. So the chat channel showed "there is a question"
with no idea WHAT it asks (qloop-ux-probe Finding E, task ccebe39a).

Fix: extractOpenQuestion(plan) pulls the text after the last OPEN_QUESTION_ANCHOR line;
parkedReason precedence is now blockedReason → plan OPEN QUESTION → reviewComments →
"(no reason recorded)". Unit-tested incl. multi-anchor (last wins) + a negative guard.
Full runtime-bridge suite 100 pass.

* fix(runtime-bridge): park.ts probes reachable base URL — agent env scrubs API_BASE_URL (Finding C-2) (#357)

Finding C (#354) made park.ts READ API_BASE_URL, but the qloop-ux-probe live re-run proved
that insufficient: the aif agent runs park.ts in a Bash-tool subprocess whose env is
SCRUBBED of API_BASE_URL (the container's PID-1 env has it; the agent's exec env does not).
So resolveAifBaseUrl() still fell back to localhost:3009 → unreachable inside the container
→ "fetch failed" → the task slid to done at $3.45 without parking, exactly as before.

Fix: resolveReachableBaseUrl() probes candidate URLs and uses the first reachable one —
[RUNTIME_BRIDGE_AIF_URL?, API_BASE_URL?, http://api:3009 (docker service), http://localhost:3009
(host)], de-duplicated. Works from BOTH the agent container (api:3009) and the host
orchestrator (localhost) even when no env survives. main() now awaits the probe.

candidateBaseUrls + resolveReachableBaseUrl unit-tested (injected fetch) incl. a negative
guard proving it skips an unreachable first candidate. Full runtime-bridge suite 117 pass.

* feat(runtime-bridge): bridge-health.sh ($0 container-side preflight) + bridge-cleanup.sh (junk sweep) (#358)

Operator pain: "каждую сессию приходится чинить" + "мусор постоянно остаётся". Both are
structural — the recurring bugs live on the host↔container↔aif boundary that unit/CI
can't cover (a full agent run costs LLM tokens, no-paid-llm-in-ci), so they were only
found live MID-TASK; and probe/smoke runs leave orphan tasks + /tmp files behind.

bridge-health.sh — $0 deterministic CONTAINER-side preflight (docker+curl+grep, no task,
no tokens). Each check maps to a real qloop-ux-probe finding: container checkout clean
(A), park.ts carries the resolveReachableBaseUrl probe (C-2), a park candidate URL is
reachable from inside the container (C/C-2), dedup store has no stale manual entries (B).
Run at session start → "bridge red, fix first" instead of discovering breakage live.
Complements verify-bridge.sh (host-side dispatch smoke). Live-dogfooded — caught + fixed
a false-positive in its own reachability check (000 connection-fail vs real HTTP status).

bridge-cleanup.sh — one-command sweep of test/probe junk: aif tasks matching a test-title
allowlist (UXPROBE/smoke/probe — real work never matched), old /tmp ManualBackend kickoffs
+ dedup backups. Reports a container stash, never drops it (operator's git). --dry-run.

Prior-art: extends own verify-bridge.sh operator-smoke pattern (packages/runtime-bridge/scripts) — no external prior art applies; project-specific aif-bridge container diagnostic + test-task sweep, not a general capability with an upstream analog.

* fix(runtime-bridge): self-cleaning junk producers — junk no longer accumulates (no manual/AI sweep) (#359)

Operator: "придумать как быть с мусором — его не должен чистить каждый раз ИИ". A manual
cleanup script is still "someone cleans each time". The real fix is that each producer
cleans up after itself, automatically, with zero AI/manual involvement.

idempotency.ts — recordDispatch was append-only (file grew forever; a stale manual-fallback
entry blocked retries for the full TTL). Now it PRUNES-ON-WRITE: rewrites the log keeping
only within-TTL entries + the new one. Self-bounding, and stale entries (incl. Finding B's
manual fallback) auto-expire by age — no --force needed after TTL, no sweep.

ManualBackend.ts — dispatch wrote /tmp/runtime-bridge-<id>.md on every fallback and never
cleaned them (days of accumulation). Now each dispatch first prunes its OWN stale artefacts
(runtime-bridge-*.md older than 7d). Scope-guarded: only matches our files, only when stale,
best-effort (never throws into the dispatch path).

Pure cores (pruneStaleEntries / parseEntries / isStaleArtifact) unit-tested incl. negative
guards (stale entry must not survive; unrelated old .md must not be pruned). Full suite 126 pass.

This complements (does not replace) bridge-health.sh; bridge-cleanup.sh stays as a rare
manual "nuke everything" escape hatch, not the routine mechanism.

* fix(runtime-bridge): park.ts refuses to park at status=review (Finding F guard) (#361)

A park that happens AFTER the implement→review transition left the task
status=review, paused=true. On resume, aif runs the review pipeline straight to
`done` WITHOUT re-implementing the injected answer — and the next chain question
is never parked (live: task ba3b4bf6 answered c1 → done, c2 silently lost).
Root cause is upstream-intentional: aif's HUMAN_ACTIONS_BY_STATUS.review = []
(stateMachine.ts) — there is no human re-entry from `review` into implement.

Option B (per 2026-06-01-aif-task-isolation.md §4, maintainer-selected): a cheap
our-side guard. parkTask already GETs the task, so the status is in hand — refuse
the park at status=review and throw a clear message (park before review, or wait
for done and use answer.ts request_changes). Turns the silent loss into a loud,
actionable error. Does NOT add mid-flight re-implement (Option C, deferred).

Paired-negative test: GUARD refuses + issues NO PUT at status=review; CONTROL still
parks normally at a pre-review status (plan_ready). 19→21 tests green.

Prior-art: skipped — bug-fix guard on existing park capability, no new dependency or capability commit.

* docs: R-phase research-patch — aif task-isolation (Findings A + F) (#360)

* docs(runtime-bridge): R-phase research-patch — aif task-isolation Findings A + F

Finding A (dirty_worktree recurs): verdict config-fix. Per-task worktrees are gated by
a 3-condition AND in planner.ts:191-195 — env AIF_TASK_WORKTREES_ENABLED=true is only
gate 1; gate 2 project.parallelEnabled (DB parallel_enabled, web-UI toggle) is 0 and was
never set, so worktrees are never created and the shared checkout is reused in-place →
dirties → next dispatch 409s. Fix: flip parallel_enabled (no restart).

Finding F (mid-flight park resumes review->done, never re-implements): verdict
aif-upstream-issue (intentional design) + our-side-guard. stateMachine.ts:106
HUMAN_ACTIONS_BY_STATUS.review=[] -> no human re-entry into implement; request_changes
only from done. DECISION-NEEDED for I-phase: Option B (park.ts guard rejecting parks at
status=review) vs Option C (auto request_changes loop, ~4h).

R-phase only — no production code changed. Evidence: parallel_enabled=0 (live DB query),
worktree_path null across 10 tasks, reflog >=6 in-place checkout cycles, planner 3-gate +
stateMachine review:[] source-verified. Scope annotation (principle 10) + §1.7 (principle 13).

* docs(runtime-bridge): record Finding F resolution — Option B selected + shipped (#361)

§4.1 + §7.3: DECISION-NEEDED-F resolved 2026-06-01. Maintainer selected the
hard-guard path (surfaced as "Option B"). Records the exact shipped behavior
honestly: park rejects only at status=review (the §4.1 Option-A semantics, NOT
the stricter implementing-only Option B) — pre-implement parks (plan_ready) stay
legitimate. Cross-refs PR #361. Option C deferred.

* feat(runtime-bridge): ensure-parallel guard — self-heal Finding A on any instance (#362)

aif creates a per-task git worktree only when project.parallelEnabled=1 (gate 2
of 3 in planner.ts). That flag is a DB field with NO env/config.yaml knob — only
the web UI or raw DB sets it — so a freshly-provisioned instance has it 0, every
task runs in-place on the shared checkout, and the next dispatch 409s on
dirty_worktree (research-patch 2026-06-01-aif-task-isolation.md §1-§2; the live
config-fix applied to this instance does not propagate to other instances).

ensureParallelEnabled(baseUrl, projectId): GET /projects → if parallelEnabled is
already true, no-op; else PUT /projects/:id with parallelEnabled=true. Wired
best-effort into AifHandoffBackend.dispatch() Step 0 (warns + proceeds on failure,
never blocks dispatch) and shipped as a standalone CLI (--project / env).

Anti-clobber (load-bearing): aif has no targeted parallelEnabled write (only
PATCH /:id/auto-queue-mode), and the full PUT NULLs any omitted *MaxBudgetUsd
(@aif/data updateProject: `x ?? null`). So the guard round-trips the FULL field
set read back from GET — flipping ONLY parallelEnabled — to preserve a UI-set
budget. Paired-negative test pins this: a set budget MUST survive the PUT.

Cleaner long-term fix = an upstream PATCH /projects/:id/parallel-enabled mirroring
the existing auto-queue-mode PATCH (file against lee-to/aif-handoff); this guard
is the our-side stopgap. 6 new tests green; runtime-bridge typecheck clean.

Prior-art: prior-art-evaluations.md#109 (park.ts BUILD — same class: thin our-side runtime-bridge consumer CLI over aif REST where no upstream targeted primitive exists; here parallelEnabled-reconcile, no upstream PATCH beyond auto-queue-mode; cleaner fix REFERENCEd to upstream, this is the stopgap).

* coordination-persistence-fix (#363)

* feat(coordination): link-coordination.sh --on-conflict=canon|worktree|skip (default skip)

Adds --on-conflict flag with three modes resolving the real-file-in-both
conflict deterministically: skip (default, exit 1, never clobber), canon
(canonical wins, relink), worktree (worktree wins, adopt into CANON).
Invalid values exit 2. TDD: 4 tests in link-coordination.test.ts.

Prior-art: skipped — extends existing scripts/link-coordination.sh (SSOT #110), no new capability/dependency; arg-parse + branch logic only, no new file/dep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(coordination): link-coordination.sh shares root _plan-cache.md + _master-backlog-delta.json (Part-2, SSOT #110)

Adds a ROOT-FILE adopt-then-link + link pair so the two root-level
gitignored coordination files (_plan-cache.md, _master-backlog-delta.json)
are shared across worktrees — previously unmatched because the loops only
iterated umbrella subdirs */. Tracked root README.md untouched.
TDD: 3 tests in link-coordination.test.ts.

Prior-art: skipped — extends existing scripts/link-coordination.sh (SSOT #110), no new capability/dependency; mirrors existing umbrella adopt/link loops for root files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(hooks): i18n reminder payload — en canonical / ru operator (+ principle-12 exempt hygiene) (#364)

* feat(hooks): i18n — split reminder payload into lang packs (en canonical / ru operator)

The two reminder hooks (ask-question-reminder, end-of-turn-reminder) emitted
Russian payload prose inline. Splits logic from payload: hooks keep all logic
and source a language pack (.claude/hooks/lang/{en,ru}.sh) selected by
AIF_HOOK_LANG (default en — canonical, for the public repo). Operator sets
AIF_HOOK_LANG=ru once globally in ~/.claude/settings.json.

- lang/en.sh + lang/ru.sh: aif_msg_* functions (${anchor} resolved at call time
  via dynamic scope) + AIF_RECAP_MARKER (recap heading; guard and message read
  the same var so they stay language-consistent).
- lang/check-parity.sh: deterministic, no-LLM guard against #two-prompts-drift —
  asserts both packs expose the same key set. Local/reviewer step, not a blocking
  gate (hooks are @cc-only, not shipped).
- Tests run the existing cases under AIF_HOOK_LANG=ru (RU-pack contract; Russian
  transcripts + assertions unchanged) + add an en-pack smoke per hook.
- RU payload byte-identical to before; EN is a faithful translation. Behaviour
  preserved (30/30 hook tests green).

Out of scope (surfaced): Russian code comments in the two hooks; other 11 hooks;
questions.ts strings. Spec: docs/superpowers/specs/2026-06-01-hook-lang-i18n-design.md

Prior-art: skipped — i18n refactor of existing internal hooks, no new capability (string externalization to lang packs, no dependency, no packages/ code module)

* test(principle-12): drop stale 'qloop-ux-probe' from EXEMPT_LIST

The qloop-ux-probe question-loop fixture dir (gitignored, added to EXEMPT_LIST
2026-06-01 by Finding F2) is gone — the positive-guard test correctly flagged it
as a stale exempt entry ("exempted but now absent"). Remove it per the test's own
instruction; 'aif-ssot-corrections' remains the sole exempt entry.

Surfaced while unblocking the hook-i18n PR's local pre-push (principle 12 runs on
gitignored kickoffs locally, skips in CI). Maintainer-invited in-session.

Prior-art: skipped — principle-test allowlist hygiene, no new capability (one stale entry removed from EXEMPT_LIST + doc comment)

* fix(runtime-bridge): bridge-cleanup §4 prunes MERGED container git junk (#365)

Enabling per-task worktrees (parallel_enabled, #362) makes aif RETAIN worktrees
post-terminal (research-patch 2026-06-01-aif-task-isolation.md §2.1), and the old
in-place task checkouts had already left 14 stale feature/* branches on the shared
checkout. The session janitor (#358/#359) deliberately did NOT touch container git.

§4 extends it to prune ONLY container git merged into origin/staging: git worktree
prune + remove retained non-main worktrees on merged branches + delete merged
feature/* branches. SAFE BY CONSTRUCTION — `git branch --merged` filtering AND
`git branch -d` (refuses unmerged) double-guard; unmerged in-flight work is never
touched. Also fixes the docker-exec stdin bug (needs -i for `sh -s` heredoc).

Completes the #359 "no manual/AI sweep" principle for container git state. This is
operator/co-located tooling (docker exec); the portable fix is upstream auto-removal
of retained worktrees (worth filing against lee-to/aif-handoff).

Live-verified on the real container: dry-run + real run deleted 7 merged branches
and preserved all 6 unmerged (+ the checked-out one). No unit test — matches the 4
untested sibling janitor scripts; safety is by-construction + live paired-evidence.

Prior-art: skipped — extends an existing janitor script (no new dependency, no new >=80 LOC file, not a capability commit); the parallel-worktree capability it cleans up after is #362/#109.

* feat(audit-self): bash-hook mutation testing — bash.rules + run-bash-mutation.sh (universalmutator ADAPT, local/on-demand) (#366)

Stage 2 B.2 of mutation-discipline-umbrella. ADAPTs universalmutator (MIT,
deterministic regexp engine + analyze_mutants harness — no LLM) to
mutation-test .claude/hooks/*.sh against their paired-negative vitest
tests. The only project-authored artefacts are bash.rules (operators), a
thin wrapper, and the wrapper's own paired-negative test. Delivery =
session-bound / local on-demand (NOT CI), mirroring Stryker-as-devDep;
the original kickoff B.3 CI job is dropped. universalmutator is a dev-time
tool, never consumer-shipped.

Dogfood gate (mandatory) surfaced real, undocumented test gaps:
end-of-turn-reminder 52% / validate-prompt 56% (both below the 60% floor)
— the &&/|| mutation of their suppress/skip conditions survives uncaught.
Recursive bootstrap: the wrapper mutated by its own operators is killed
13/13 by its own test. Found + fixed a /*-in-comment regexp-engine
limitation (bash globs in comments falsely trigger C-block-comment mode)
via a line-count-preserving sanitiser.

Evidence: docs/meta-factory/research-patches/2026-06-02-bash-mutator-b2-shipped.md

Prior-art: prior-art-evaluations.md#91 (universalmutator ADAPT — engine ADOPTed verbatim, only bash.rules operators authored; problem-class ~85% match per B.1 §B.1.5; mutahunter REJECTed on no-paid-llm-in-ci §1).

* feat(coordination): persistence-fix tail — SW-B symlink-aware helpers + G/B/C installer (#368)

* docs(coordination): design — cross-worktree persistence fix (4-channel: git-hook agnostic floor + CC + Superset + skill)

Brainstorm output for coordination-persistence-fix umbrella. T16 corrections:
- channel C substrate = Superset projects/<uuid>/config.json setup-array (rsync = root cause), NOT ~/.superset/hooks/
- adds git post-checkout (.husky) as the harness-agnostic floor per maintainer 'works everywhere' requirement
- Q3 atomicity already satisfied (mktemp+mv verified); SSOT #110 same anchor

Prior-art: skipped — design doc only, no new capability (extends link-coordination.sh SSOT #110).

* docs(coordination): correct §2.4 — helpers atomic but NOT symlink-preserving (mv replaces symlink)

Planning-phase finding (verified empirically): mv tmp <symlink> replaces the
symlink with a real file → Part-2 cross-worktree share lost on first cache write.
SW-B upgraded from 'confirm test' to 'make 3 helpers symlink-aware (write-through
resolved target)'. Atomicity preserved (temp+mv in target dir), symlink preserved.

Prior-art: skipped — design doc correction only, no new capability.

* docs(coordination): implementation plan — 4 sub-waves (A linker / B symlink-aware helpers / C wiring / D self-apply)

Prior-art: skipped — plan doc only, no new capability (implements SSOT #110 extension).

* feat(coordination): one-click install-coordination-wiring.sh (G+B+C, idempotent, self-test)

Applies all 3 wiring channels in one terminal command (maintainer runs from central repo):
  G  .husky/post-checkout            (agnostic floor)
  B  .claude/settings.json SessionStart
  C  ~/.superset/projects/*/config.json setup (replaces rsync root-cause)
--self-test (sandbox) + --dry-run modes; idempotent re-runs. Replaces the manual runbook.

Prior-art: skipped — scripts/ helper, not a capability commit (no dep, not under packages/); wires existing link-coordination.sh SSOT #110.

* fix(coordination): 3 cache/delta helpers symlink-aware (SW-B)

resolve_target() follows a symlink to its real $CANON target so the atomic
temp-then-mv write writes THROUGH the link instead of replacing it. Without this,
the first cache/delta write breaks the cross-worktree share (mv tmp <symlink> →
real file). +3 paired-negative tests (symlink preserved + canon written through);
18/18 green. Done locally (not via aif — container serialized on doc-audit task).

Prior-art: skipped — symlink-preservation fix to existing helpers, no new capability; extends link-coordination.sh SSOT #110 share semantics.

* docs(coordination): drop channel A (skill self-heal) — redundant after G git-hook

B (CC SessionStart) fires at every session start ⊇ 'when meta-orchestrator runs',
and G (git post-checkout) is the real agnostic floor A was standing in for.
Final channel set = G + B + C, wired via install-coordination-wiring.sh.

Prior-art: skipped — design doc scope-reduction, no new capability.

* fix(runtime-bridge): bridge-cleanup §4 skips while aif has live tasks (no ref-lock contention) (#367)

§4 (container git prune, #365) runs git fetch / worktree prune / branch -d in the
SHARED .git. A task running concurrently touches the same ref + packed-refs locks
(amplified by parallel_enabled keeping multiple tasks active) → a collision yields
"cannot lock ref" and can fail the live task's git step.

Guard: GET /tasks, count non-terminal (not done/verified/cancelled); if >0, skip §4
with a clear note ("re-run when idle"). Deterministic, no AI. Fail-open (count unknown
→ 0 → proceed) — §4 is already merged-only safe-by-construction, so this is defence-in-
depth; worst case of a false 0 is a transient lock retry, never corruption or data loss.

Live-verified: with 2 non-terminal tasks running, `--dry-run` §4 printed "skipped — 2 live".

Prior-art: skipped — edits an existing janitor script (no new dependency, no new file, not a capability commit); guards the #365 sweep against the #362 parallel_enabled concurrency window.

* feat(coordination): commit channels G + B (post-checkout + CC SessionStart) (#369)

Makes the two machine-agnostic wiring channels durable + shared (were applied
locally by install-coordination-wiring.sh). Both call link-coordination.sh (SSOT #110):
  G  .husky/post-checkout       — agnostic floor, fires on git worktree add
  B  .claude/settings.json      — CC SessionStart re-heal
Channel C (Superset setup-array, per-UUID) stays installer-only. SW-D liveness:
G verified live — fresh worktree auto-linked 287 files.

Prior-art: skipped — wiring of existing link-coordination.sh SSOT #110 into hook channels, no new capability.

* docs(research-patches): doc-audit-ship-boundary Stage 2 findings + gitignore stryker sandbox (#371)

Autonomous aif audit (task bcb2c6c2): full doc-vs-reality + vs-goal reconciliation,
Stage 2.0 mutation-run fuel, DECISION-NEEDED batch (DN-1..4), §A self-classification.
Audit-only — no maintainer-owned edits landed. Also gitignore packages/core/.stryker-tmp/
so mutation runs never pollute the tree (root cause of PR #370 contamination).

* test(hooks): close mutation gaps in end-of-turn-reminder + validate-prompt (B.2 dogfood follow-up) (#373)

The Stage 2 B.2 bash mutator (#366) surfaced two hook tests below the 60% kill
floor: end-of-turn-reminder (52%) and validate-prompt (56%). Add targeted cases
that kill every NON-equivalent surviving mutant:

- end-of-turn-reminder 52% → 69%: cover the previously-untested idle-suppress
  re-ping guard (hook:135-156) — suppress-on-reping, fire-on-new-question,
  long-answer-overrides-suppress, the B2 "an AskUserQuestion turn is never
  idle-suppressed" contract — and the bare non-question tool-call early exit
  (hook:53-54). The 10 residual survivors are equivalent mutants: 7× `|| true`
  → `&& true` inside `$(…)` command-substitutions (stdout unchanged, exit
  ignored under set -uo, no -e), the transcript-validity guard (both paths exit
  0/empty for the only diverging input), `set -e`→`set +e` (hook never relies on
  errexit aborting), and the inner re-ping AND→OR (divergence requires
  prev==last+recap, which trips the self-recap guard first). All 22 killable
  mutants are killed.
- validate-prompt 56% → 78%: deterministically exercise the tsx-unavailable
  graceful-skip (hook:26-27, isolated-root copy) and the jq-unavailable
  graceful-skip (hook:14-16, fake-bin PATH without jq). The 2 residual survivors
  are gh+validator-coupled (hook:21 off-path-proceeds-to-validator, hook:33
  validator-result) — killable only with a live-gh 404 test, which is
  non-hermetic; left per T-MUT-B rather than shipping a flaky test.

Test-only; no production hook modified. Follow-up to #366
(mutation-discipline-umbrella Stage 2 B.2).

* docs(doc-audit): apply DN-1/2/4 doc-staleness fixes + ship the fixer tooling (#374)

doc-audit-ship-boundary Stage 2 — maintainer decisions 2026-06-02, applied.

Applied (maintainer-directed, DN-1/2/4):
- .claude/rules/rule-enforcement-channel-selection.md: 'Activation pending' →
  'Activation confirmed — settings.json:114' (Class header + §4 prose); the
  PostToolUse Edit|Write hook is wired (verified settings.json:114).
- README.md badges: ?branch=main → ?branch=staging (main=prod, CI runs on staging).
- README.md enforcement chain: drop false 'Stryker' from the CI channel —
  mutation tests are local/session-bound, NOT a CI gate (no workflow runs Stryker).

Tooling shipped alongside:
- scripts/apply-doc-fixes.sh: idempotent, terminal-runnable fixer (re-applies the
  above; --check dry-run). packages/core/hooks/apply-doc-fixes.test.ts: 5 green.
- docs/meta-factory/mutation-run-placement.md: DN-4 placement note (mutation local;
  ps/jq/universalmutator reqs).

Prior-art: skipped — maintainer-directed doc-staleness fixes + their fixer tool +
a placement note; no new runtime capability, no dependency, no packages/ code module.

* test(principle-12): raise population-sentinel bounds 10..100 → 100..300 (#375)

The principle-12 population sentinel capped local kickoff dirs at 100. The project
legitimately accumulated >100 umbrellas of history, mirrored locally by the
coordination-persistence channel (G, .husky/post-checkout → link-coordination.sh),
so the ≤100 guess tripped the pre-push on every worktree. Raise to ≤300 (still
catches a runaway glob) and ≥100 (doubles as a 'mirror present' guard). The
sentinel is skipped in CI (KICKOFFS_AVAILABLE) — it only gates local pre-push.

Maintainer-directed 2026-06-02. Does NOT resolve the separate citation-check vs
symlink-mirror interaction (the broader principle-12-vs-G item) — bound only.

Prior-art: skipped — adjusts a sentinel bound in an existing principle test to
match reality; no new capability, no dependency, no new module.

* test(principle-12): citation check ignores coordination-mirror kickoffs (#376)

Citation check re-ran on every umbrella mirrored in by channel G (post-checkout →
link-coordination.sh symlinks gitignored kickoff.md into $CANON), failing on
historical umbrellas this worktree never authored (principle-12-vs-G). Fix: check
only REAL (non-symlink) kickoff.md = locally authored. Population sentinel still
counts the FULL set as a mirror-present guard. Pairs with #375 (bound→100..300).

Prior-art: skipped — narrows an existing principle-test filter to exclude symlink
mirrors; no new capability, no dependency.

* docs(spec): aif parallel autonomous dispatch — verified model + open test (brainstorm 2026-06-02) (#377)

Captures the full session conclusions so they survive: the depth-2 architecture insight
(subagents don't spawn subagents → quality parallel needs separate top-level sessions, meta-
orchestrator decides), the verified aif model (1 kickoff/meta-launch → 1 task; each task = top-
level session; flat task model; two parallelism layers L1 cross-task worktrees / L2 within-task
use_subagents→implement-coordinator; use_subagents=0 everywhere = why never parallelized;
worktree-skip is documented per docs/configuration.md), the observable test signal
(agent_activity_log 'Subagent: <name> started', subagentQuery.ts:793), the decision tree, the
OPEN question gated on one live test (use_subagents top-level launch), an honest flip-flop
corrections log, and decisions (planner-owns-plan, COORDINATOR_MAX knob, park preserved).

NOT a final spec — gated on the §5 live test on a genuinely-parallel task (D.1-D.5).

* chore(harvest): commit reworked aif task 5767f7df-7425-4a78-a76f-98402c1647d0 — mutation-discipline-stage-c-meta-launch (#378)

* docs(spec): aif parallel dispatch §5 RESOLVED — YES via use_subagents (live proof) + M3-bash timeout fix (#380)

* docs(spec): aif parallel dispatch §5 RESOLVED — YES, self-parallelizes via use_subagents (live 2026-06-02)

Live test (task a37ee2f5): use_subagents=true on a real 5-task plan (mutation
Stage 4 D.1-D.5) → implement-coordinator launched top-level (--agent) → fanned
out 5 concurrent implement-worker subagents, each worktree-isolated. Proof:
5x `Tool: Agent {subagent_type:implement-worker}` spawns + git worktree list
1→6 (5 locked worker worktrees coexisting, ~2min all-5 overlap) + 5 distinct
real test files written simultaneously.

Also corrects §3.7 observable: `Subagent:` log line is SDK-transport-only
(onSubagentStart wired only via SDK SubagentStart hook); on transport=cli it
stays 0 even under full parallelism — a false-negative trap. Valid CLI-transport
observables = git worktree growth + `Tool: Agent` activity entries.

Design consequence = the tiny change: bridge sets use_subagents=true at dispatch
for depth-2-sufficient umbrellas + meta-orchestrator emits plan-structured
kickoffs (independent numbered Task items, no Phase headings).

Prior-art: skipped — design-doc update only, no new capability/dependency/code-module.

* test(principle-02): give M3-bash mutation gate a 60s timeout (was default 5000ms)

The [M3-bash] local/on-demand gate (describe.skipIf(!HAS_UM)) calls
runBashMutator → spawns `npx vitest` once per mutant, ~9.5s on a loaded host.
The default 5000ms vitest timeout false-fails it at pre-push (blocking unrelated
pushes; observed this session). Add an explicit 60_000ms timeout — mirrors the
Stryker precedent (principle 11 testTimeout 5s→30s). No assertion/semantics
change; CI still skips it (no universalmutator).

Prior-art: skipped — test-timeout fix for an existing gate, no new capability/dependency.

* test(mutation-discipline-stage-d): paired-negative tests for 5 remaining .sh helpers (#381)

Stage 4 D of the mutation-discipline umbrella — content-level paired-negative
vitest tests (M.4 pattern) for the .sh files that lacked them:
- priority-score.sh        → priority-score-synthetic.test.ts   (20 it, 53 expect)
- plan-currency-check.sh   → plan-currency-check-kickoff.test.ts ( 6 it, kickoff-existence angle; non-redundant w/ existing)
- launch-table-generator.sh→ launch-table-generator.test.ts     (11 it)
- check-skill-drift.sh     → check-skill-drift.test.ts          ( 6 it)
- pre-push.fallback.sh     → pre-push.fallback.test.ts          (20 it)

Produced by the aif self-parallel-dispatch live test (5 concurrent implement-workers,
use_subagents=true) and verified by an independent 5-way adversarial cold-review
(T19): all real-script-invoked (spawnSync bash on the actual .sh), content-level
assertions (exit code + stdout/stderr, no shape-only .toBeDefined), anti-tautology
confirmed. Fixed one over-reaching T12-compliance comment in priority-score
(claimed 8 surfaces verified; only 5 are — now honest + deferred-gap note).

Known deferred coverage gaps (honestly disclosed, follow-up): launch-table
fallback+no-keyword path; pre-push 2 of 3 discipline-file patterns;
priority-score surfaces (c)/(d)/(h). 52/52 tests green.

Prior-art: skipped — test additions for existing .sh capabilities, no new runtime capability/dependency.

* research(mutation-discipline-d6): verdict on deps-hash-check.sh duplication (#382)

D.6 R-phase (mutation-discipline umbrella). Investigates the two byte-identical
deps-hash-check.sh copies. Verified roles (T3, file:line): packages/core/hooks/
= shipped SOURCE (install.sh:261), .claude/hooks/ = this repo's DOGFOOD copy
(settings.json:74). Key finding: deps-hash-check.test.ts:34 tests the DOGFOOD
copy, not source → silent drift possible (test stays green if source diverges).

Verdict (b) keep-both + drift-check: add @dual-pair markers + a byte-identity
assertion test — REUSE the existing dual-implementation @dual-pair convention
(SSOT #87) rather than BUILD. Symlink (a) rejected (install.sh --force regression
+ Windows). Concrete I-phase fix path + falsifiers included. R-phase only — no
edits to the .sh files / settings.json / install.sh.

Produced autonomously by aif (use_subagents=0, single R-phase task) + cold-reviewed.

Prior-art: skipped — research-patch (markdown), no new capability/dependency/code.

* fix(hooks): drift-check for deps-hash-check.sh source/dogfood pair (D.6 I-phase) (#383)

Implements #382 §6 verdict (b) keep-both + drift-check. Adds
@dual-pair: deps-hash-check-dogfood markers (identical block, preserving
byte-identity) to both the shipped SOURCE (packages/core/hooks/) and this
repo's DOGFOOD copy (.claude/hooks/), plus a byte-identity assertion in
deps-hash-check.test.ts.

Closes the silent-drift hole the D.6 R-phase confirmed: the functional tests
exercise only the dogfood copy (HOOK), so they stayed green even if the source
diverged. Falsifier verified — breaking one copy turns the new test red.

Corrected #382 §6's asymmetric-marker spec (4-line block to dogfood vs 1-line
to source), which would itself have broken the byte-identity the test asserts;
both copies carry the identical block.

Last residual of the mutation-discipline umbrella; closure (done.md + memory)
follows on merge.

Prior-art: prior-art-evaluations.md#87 (worktree-create-setup dual-pair, SSOT BUILD — this commit extends the @dual-pair pattern with a second pair; no new mechanism introduced).

* docs: R-phase — why per-task worktrees never materialize (corrects #360 Finding A) (#372)

* docs(runtime-bridge): R-phase — why per-task worktrees never materialize (corrects #360 Finding A)

Live falsifier of #360 Finding A: 0 tasks ever got a worktree despite parallel_enabled=1.
Root cause (verified source, correct containers): the bridge dispatches via a planner-SKIP —
POST /tasks/:id/events {event:'accept_existing_plan'} — which transitions backlog→plan_ready
SKIPPING the `planning` status (stateMachine.ts case accept_existing_plan, api container).
The coordinator runs runPlanner ONLY for the planning stage (coordinator.ts PIPELINE
from:["planning"], agent container). The ONLY worktree-creation code lives in runPlanner
(planner.ts:191-213). ⇒ bridge tasks never run the planner → never create a worktree,
regardless of parallel_enabled / AIF_TASK_WORKTREES_ENABLED / plannerMode.
Secondary: handleAcceptExistingPlan also persists branchName (taskEvents.ts:236+) so even if
the planner ran, planner branch-2 (in-place restore) would pre-empt branch-3 (worktree).

Verdict: parallel_enabled=1 (#360/#362) is NECESSARY BUT NOT SUFFICIENT. Fix options:
B = drop accept_existing_plan from bridge dispatch + let the coordinator run the planner;
C = accept serial in-place as the operating mode with the existing rollback guard.
Both deferred to a follow-up I-phase (DECISION-NEEDED). gate-3 passes; mode-change is NOT
the fix (server already forces full). Effort + falsifier per finding in the patch.

R-phase only — no production code changed. DeepWiki x3 (converged) + verified file:line in
both aif containers. Scope annotation (principle 10) + §1.7 (principle 13).

* docs(runtime-bridge): §0.1 — worktree-skip is DOCUMENTED aif behaviour (docs/configuration.md), not a trap; no upstream issue

DeepWiki x3 (converged): docs/configuration.md ties worktree creation to full-mode
planning by design; docs/architecture.md documents the serial-forcing. So planner-skip
forgoing the worktree is a documented consequence, not a defect — no upstream issue.
Records the search-coverage lesson: the #360 verdict was avoidable by reading aif's own
config docs. The corrected fix (run the planner) is the documented isolation+parallel path.

* docs(wave-plan): reconcile §0 → 2026-06-02 (#298→#382) + codify how §0 stays current (#384)

Manual status reconciliation per the design loop (detect via /meta-orchestrator,
write by human-directed session — Direction A auto-write rejected at R-phase).

- §0 + Status dates 2026-05-29 → 2026-06-02
- correct misleading aif-handoff DEFER-ALL row: add `runtime-bridge BUILD` row
  (minimal-REST bridge shipped #312/#313 + ecosystem; supersedes-in-practice)
- +8 umbrella rows for work merged since last reconcile (runtime-bridge,
  aif task-isolation, companion adoption, coordination-persistence, qloop,
  mutation-discipline, worktree dual-channel, doc-audit-ship-boundary)
- add "Open frontier" block (live: #372 open, inject-layer-extension,
  one-click-installer, f2-aif-fire-backend) — distinct from ~80 closed candidates
- add "How §0 stays current (by design)" note to Status block so future
  sessions don't try to automate the deliberately-manual reconciliation

Prior-art: skipped — doc-only status reconciliation of an existing planning doc, no new capability or dependency introduced.

* chore(orchestrator): mark mutation-discipline-umbrella DONE (#385)

Umbrella closure per CLAUDE.md "Umbrella closure convention" — Final PR #383
(D.6 drift-check, last residual) merged to staging. All stages shipped:
A audit #219 / B bash-mutator #366+#373 (dogfood gate fired) / C tests-of-tests
#378 / D #381 + D.6 dup R-phase #382 + D.6 drift-check I-phase #383.

done.md is the priority-score.sh Layer C3 completion marker (gitignore:14
un-ignores */done.md).

Prior-art: skipped — completion marker (done.md), no new capability/dependency/code.

* fix(principle-12): skip anti-tautology mutation test when no real (non-mirror) kickoffs exist (#386)

The anti-tautology sub-test mutates a REAL compliant kickoff to verify the
compound-citation detector actually catches absence. After the coordination
symlink migration (#346 + post-checkout link-coordination.sh), every local
kickoff.md is a SYMLINK into $CANON — a coordination mirror that
getNonExemptEntries() deliberately excludes (isCoordinationMirror). With all
kickoffs mirrored, getNonExemptEntries() is empty → `compliant` is undefined →
`expect(compliant).toBeDefined()` failed on every host push (reproduced on
staging HEAD, independent of any feature branch).

The #376 mirror-exclusion fix updated the main check + population sentinel but
missed this meta-test. Guard it with HAS_REAL_NONEXEMPT_KICKOFF: when there is
no real kickoff to mutate, skip — detector correctness is already covered by the
pure-logic anti-tautology tests (blank file / each pattern), which need no files.

Unblocks all host pushes (pre-push runs the principle suite). No behaviour change
to the citation enforcement itself.

* fix(runtime-bridge): dispatch via planner-run so each task gets its own worktree (parallel) (#387)

The bridge dispatched tasks with `accept_existing_plan`, which transitions
backlog -> plan_ready SKIPPING the `planning` stage. runPlanner (planner.ts,
the ONLY code that creates a per-task git worktree) runs only at `planning`,
so 0 tasks ever got a worktree -> all dispatched tasks forced serial. Verified
+ live-proven in #372 (research-patches/2026-06-02-aif-worktree-gap.md).

Fix (Option B, live-verified 2026-06-03):
- Drop the `accept_existing_plan` event. The task stays at `backlog`; the
  auto-queue advances it through `planning` -> runPlanner -> per-task worktree.
- Carry the kickoff in `description` (the planner INPUT spec, planner.ts:246),
  NOT in `plan` (the planner OUTPUT slot @planPath, which it overwrites). The
  planner plans the *how* from our kickoff *what*.

Live acceptance: two tasks dispatched concurrently each got a DISTINCT
worktree and both ran in `planning` simultaneously (N agents x N workspaces,
not within-task subagents). Throwaway probe tasks + worktrees cleaned up.

Tests: rewrote the dispatch sequence test for the 2-step flow (asserts
description=kickoff, no accept_existing_plan, no plan push) and moved the
rollback test's failure point to the unpause step. 8/8 green.

Prior-art: skipped — modifies existing bridge file, no new capability/dependency/module.

* research(aif-operator-asset-access): R-phase — gap verification + A-E verdict + recommended composition (#388)

Five gap rows re-confirmed with file:line evidence. Existing skill-context/ and agents/*.md
mechanisms audited (T16 problem-class check) — none cover orchestrator/reviewer discipline.

Recommended composition: B+D (in-repo agnostic worker discipline, primary) + A (operator
bind-mount, immediate) + C (buildKickoffSpec sibling inlining, follow-on). E rejected.
DN-1/2/3 surfaced; DN-1/2 resolved inline (B scope = minimum viable worker discipline;
reviewer-discipline = separate concern covered by review-sidecar.md).

Prior-art: prior-art-evaluations.md#67 (aif-handoff, ADOPT — this umbrella extends it with
operator-asset bridging); prior-art-evaluations.md#50 (skill-context/aif-review SSOT,
ADOPT channel — D reuses this delivery pattern).

* fix(meta-orchestrator): Stage 2 correctness — C1.1/C1.2/C1.3/M4/F2/F7 (#389)

Six confirmed bugs fixed in the meta-orchestrator skill:

C1.3 — SKILL.md:449: stale repo-root mirror claim removed; replaced
  with "Ships directly from .claude/skills/meta-orchestrator/ via
  install.sh (single source of truth; no repo-root mirror — Item 12
  closure 2026-05-25). Install pattern at install.sh:236-255."

C1.2 — SKILL.md:508: state.md section names corrected from stale
  "§2 Launch-table / §3 Dispatch log" to actual template names
  "§2 Decisions / §3 Phase -1 verdict" (state.md.template:26,35).

C1.1 — master-backlog-delta.md:49-52: "Body owns arrays — inline-jq"
  claim removed (inline jq was removed in F.3 PR #261 2026-05-28);
  delta-write-from-state.sh correctly attributed as array owner.

M4 — SKILL.md:521-525: delta two-helper ordering fixed. update-delta.sh
  (bootstraps schema) now listed first, matching delta-write-from-state.sh:19.

F2 — SKILL.md:71: head -200 raised to head -400; wave-sequencing-plan.md
  is 293 lines so head -200 dropped 93 lines incl. §6 dependency-matrix.

F7 — run-helper.sh:26: timeout wrapped (MO_HELPER_TIMEOUT env, default 120s);
  END trailer still fires on expiry (rc=124). failures.md: F9 added.

Prior-art: skipped — correctness fixes to existing prose/helper, no new capability

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(aif-worker-discipline): portable worker discipline agent + AIF skill-context delivery (#390)

* feat(aif-worker-discipline): portable worker discipline agent + AIF skill-context delivery

Artifacts 1 (B) and 2 (D) from aif-operator-asset-access I-phase.

- agents/orchestrator-worker-discipline.md: portable AI-agnostic worker
  discipline (REPORT schema, park-vs-proceed, stage-gate, §1.7 PR body)
  for aif-dispatched workers. Class B. @dual-pair: aif-orchestrator-discipline.

- packages/core/templates/shared/skill-context/aif-orchestrator-discipline/SKILL.md:
  AIF-native delivery of the same content. YAML from aif-review/SKILL.md (SSOT #50).
  @dual-pair + spec: per dual-implementation-discipline.md §5.

- packages/core/principles/09-doc-authority-hierarchy.ts: added
  aif-orchestrator-discipline/SKILL.md to REQUIRED_HEADER_DOCS (shippedSubset 16→17).
  Upper sentinel 60→65; Wave-4 length sentinel 16→17.

- install.sh: aif-orchestrator-discipline/SKILL.md added to SHIPPED_DOCS.

§1.7: forward — agents/orchestrator-worker-discipline.md:9 authority header per
doc-authority-hierarchy.md §3; install.sh:103 in sync with REQUIRED_HEADER_DOCS
per 09-doc-authority-hierarchy.test.ts:164 Wave 4 invariant. backward —
SKILL.md:10-11 carries @dual-pair + spec: per dual-implementation-discipline.md §7.

Prior-art: prior-art-evaluations.md#50 (skill-context/aif-review, ADOPT channel pattern).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(runtime-bridge): add operator convenience bind-mount section for global skills

Adds '## Operator convenience: mount global skills' section at the end of
docs/runtime-bridge-setup.md documenting how to bind-mount operator-side
global skills (~/.claude/skills/orchestrator/, Superpowers plugins) into
the aif container via docker-compose.override.yml.

Operator-axis only — consumers of the shipped framework are NOT required to
have these paths. The shipped in-repo discipline works without the mount.

Artifact 3 (A-docs) from aif-operator-asset-access I-phase.

Prior-art: skipped — doc addition, no new capability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(meta-orchestrator): add harvest egress step to autonomous dispatch loop (#391)

aif does NOT push or open PRs by design (documented in harvest.ts:11-14).
Nothing in the dispatch loop said "after status=done → call harvest" — this
was the gap that caused the aif-operator-asset-access I-phase to sit stuck
in the container with work done but no PR opened (incident 2026-06-03).

Fix: add egress gate to SKILL.md §5 autonomous dispatch row and to
meta-kickoff.template.md §4c so every generated kickoff carries the
harvest call as a mandatory post-done step.

Anti-pattern #autonomous-done-no-harvest added to SKILL.md §5.

Prior-art: skipped — doc-only change to .claude/skills/ and templates/,
no new capability (refactor only, harvest.ts already shipped).

* chore(orchestrator): mark aif-operator-asset-access DONE (#392)

* docs(meta-orchestrato…
artyhoo added a commit that referenced this pull request Jun 14, 2026
* Promote staging → main — consumer-install-hardening + guard-liveness + runtime-bridge (134 commits) (#488)

* feat(hooks): SubagentStop REPORT-schema WARN (non-blocking, #108 candidate-3) (#339)

Ship .claude/hooks/warn-subagent-report.sh — scans the finishing subagent's
output for canonical REPORT sections (VERIFY, Confidence, ATTN); emits a
⚠ stderr warning + exit 0 (non-blocking) when any are missing.

Maintainer chose WARN over block (exit 2) — judgment target, not gateable
(#gate-where-judgment-needed, rule-enforcement-channel-selection.md §1).

Contract verified DUAL-CHANNEL 2026-06-01:
  Channel 1 — WebFetch code.claude.com/docs/en/hooks: SubagentStop fields
    include agent_type; exit 0 = non-blocking; stderr shown to user.
  Channel 2 — DeepWiki anthropics/claude-code: agent_transcript_path +
    last_assistant_message (since CC 2.1.47) confirmed by name + quoted.

Two read paths (B1 — scan-nothing trap avoidance):
  1. last_assistant_message if non-empty.
  2. agent_transcript_path JSONL, reusing end-of-turn-reminder.sh:30-41.
Noise guard: skip Explore agent_type + require standalone REPORT-cue label.

Paired test: 6 cases (positive-A/B + negative + negative-2 Explore +
non-report prose + exit-code invariant). All 6 green. Pre-existing staging
failures (pre-push.test.ts + plan-currency-check.test.ts) unaffected.

settings.json wiring snippet in PR body (maintainer lands, not committed).

Prior-art: prior-art-evaluations.md#108 (SubagentStop REPORT-schema gate — candidate-3 of #108, shipped as WARN per maintainer decision 2026-06-01; block dropped — judgment target, #gate-where-judgment-needed).

* docs(research-patches): satellite-feature-harvest — own-stack-first harvest + reverse-sweep (form B) (#328)

Own-stack-first sweep: Claude Code natively ships the remote/scheduled/parallel-session
cluster (Remote Control, Routines, Channels, agent-view, Slack) that prior SSOT rows
credited to companions. 15 capability clusters triaged → 4 adopt-now (Remote Control,
Routines via /schedule, Channels(Telegram), background agent-view) + 3 defer + 8 skip.
Reverse sweep: 4 criterion-zero relationships, no shipped artefact retired (T-SFH-D —
runtime-bridge questions.ts kept; Channels is session-open-only). +1 SSOT row #100.

Prior-art: skipped — research-patch only, no new capability (the BFR own-stack-first survey is the patch content itself).

* chore(hooks): wire pending CC hooks in settings.json (SubagentStart/Stop + runtime-bridge) (#340)

Activates three already-merged, already-committed hook scripts that were
running locally but never wired in the shared config:
- PostToolUse Write|Edit|MultiEdit -> runtime-bridge-dispatch.sh (#313)
- SubagentStart -> inject-subagent-digest.sh (#108 companion-adoption-iphase)
- SubagentStop -> warn-subagent-report.sh (#108 candidate-3, #339)

All hook scripts already exist in staging; this only activates them.
Config-only wiring, not a capability commit.

* docs(book): SubagentStop block-vs-warn — flip open-node DECISION-NEEDED → решён=WARN (#339) (#341)

#339 (SubagentStop REPORT-schema WARN, non-blocking) merged to staging as
0596a64 — the block-vs-warn decision is resolved in favour of WARN. Hook
remains INERT (no SubagentStop in settings.json) until the maintainer lands
the snippet, so the node stays half-open: decided, not yet wired.

- v1 (technical): chapter XXII + epilogue June-1 open-nodes list — cite #339
- v2/v3 (literary): keep allegory, no PR number — «нрав выбрали, в строй не поставили»

Prior-art: skipped — doc edit only, no new capability (book open-node status sync after #339 merge).

* refactor(hooks): remove end-of-turn factual-claim detector — net-negative sentry, dropped by evidence (#342)

The claim-scan (numeric / file:line / negative-existence → re-verify nudge) in
end-of-turn-reminder.sh measured recall ≈0.43 + precision ≈0.20-0.25 (cry-wolf)
in the instruction-compliance pilot (2026-05-21). A sentry that misses most real
claims and false-fires on ~75% of what it catches is the #discipline-theatre the
project hunts. Maintainer decision 2026-06-01: «не нужен раз не работает».

Same class + outcome as the recommendation-laziness narrow-B stop-scan dropped at
FP 84% (#210): drop on FP evidence, keep the always-on H1 reminder.

Surfaces:
- hook: remove claim-scan block + has_claims/claim_count refs in idle-guard,
  trigger, glance-line, Branch D, append block. Trigger = long_text||asked;
  branches A/B/C only. Recap + question-check survive. bash -n + shellcheck clean.
- tests/hooks/eot-claim-scan.test.sh: deleted (185 LOC, all claim-scan) + CI step.
- packages/core/hooks/end-of-turn-reminder.test.ts: drop Branch-D + code-fence
  precision cases, add paired-negative «short claim turn now silent». 13/13 green.
- research-patch documenting the evidence-based removal (scope annotation, §1.7).
- book epilogue ×3: the «искатель утверждений» debt closed by removal, not fix.

A short factual-report turn with no question now stays silent (was Branch D) —
the intended consequence; that nudge was the cry-wolf.

Prior-art: skipped — removal of an own-built net-negative mechanism, no new capability (cites instruction-compliance-empirical pilot + narrow-b-benchmark #210 precedent in the research-patch).

* feat(runtime-bridge): harvest — deterministic aif-task to PR egress leg (#343)

aif-handoff ends a task at "committed on a local feature branch" with NO push
and NO PR-creation in its autonomous (HANDOFF) path — verified 2026-06-01
against the running containers (zero PR-create / branch-push anywhere in its
autonomous flow; its commit/push lives only in an LLM-driven /aif-commit
runtime). So committed work strands inside aif's checkout, never reaching a
reviewable PR on the trunk. harvest is the missing return leg.

- src/harvest.ts: pure DI'd core harvestTask — guards (terminal status,
  branchName present) BEFORE any side-effect; ordered push then createPr then
  auto-merge, fail-fast (PR error never arms auto-merge).
- src/cli/harvest.ts: real wiring — docker-exec push from aif's container
  (holds the commit + working creds), host PR-create against staging, native
  auto-merge; graceful printed manual-fallback if docker absent.
- test/harvest.test.ts: paired-negative (non-terminal / no-branchName must NOT
  push or PR; PR-fail must NOT arm auto-merge).
- aifHttp.ts: expose branchName on AifTaskFull (read back by harvest).

Zero LLM by construction (plain git+gh+docker) — unlike aif's own commit flow
which spends a paid claude -p query just to run git. Complies with
no-paid-llm-in-ci.md.

Prior-art: prior-art-evaluations.md#27/#28/#30/#67 — aif-handoff's own runtime
(HANDOFF_MODE / paused / Planner-Implementer-Reviewer / Kanban) is the from-side
this leg bridges, NOT an egress tool: verified it has zero push/PR in its
autonomous path, which is exactly why harvest exists. No upstream
harvest-autonomous-agent-branch-to-PR analog; thin deterministic glue over
gh/git/docker, no PR-creation reimplemented. BFR verdict: BUILD (bridge egress leg).

* feat(meta-orch): run-helper.sh completion-barrier wrapper (TDD, stage 1) (#344)

Build .claude/skills/meta-orchestrator/helpers/run-helper.sh — a trivial bash
exec-wrapper that runs a target helper as a child and ALWAYS appends a terminal
END-trailer to stdout:

    === <helper-name>: END rc=<exit-code> (lines=<stdout-line-count>) ===

so a session reading a slow helper's stdout in the background can distinguish
"finished (rc=N)" from "still running / crashed". The trailer is appended by the
parent wrapper, so it fires even when the child exits non-zero or dies
mid-output (a child-side `echo END` would be skipped on `set -e` abort / SIGKILL
/ timeout). Fixes the orchestrator reading a header-only partial file as
"zero results".

HARD CONSTRAINTS satisfied (§2): exit-propagation (capture rc first, exit rc
last — trailer echo never clobbers child status); trailer-on-crash; stdout
verbatim pass-through; `=== `-prefix kept so classify-each-candidate.sh:52
`awk '... && !/^=== /'` strips it (never a spurious candidate).

TDD: paired-negative test at packages/core/skills/run-helper.test.ts (positive
clean-exit + negative exit-1 + SIGKILL crash-mid-output + parse-safety) — 4/4
green. No regression: `git stash -u` A/B shows the 5 neighbouring helper tests
produce an identical pre-existing 18-fail set (env-dependent gh/git harnesses)
with and without this file.

T-BGB-A: the END trailer proves the child FINISHED + its exit code, NOT
semantic content-completeness — kept in test names/comments.

Prior-art: prior-art-evaluations.md — escape hatch: trivial bash exec-wrapper, no upstream analog worth a dependency (BFR BUILD, pre-reasoned). Nearest SSOT neighbour #54 (Aider run_cmd runner) is a different problem-class — exit-code routing in a TS pre-push gate vs. appending a structural completion trailer for a background reader (T16: similar "runner" name, distinct capability).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* bg-helper-completion-barrier-stage2-aif (#345)

* feat(meta-orch): route bg-helper call-sites through run-helper.sh + Layer-1 read-rule (stage 2)

Wire the two background-helper !-fences in meta-orchestrator/SKILL.md
through the already-merged run-helper.sh completion-barrier wrapper:
- §2 Step 1 priority-score.sh
- §2.5 Step 2 dup-detect.sh + inflight-check.sh

Add the Layer-1 read-rule prose after both fences: parse a slow helper's
output only after its task-notification or the `=== <helper>: END rc=<n> ===`
trailer; a header-only read means "still running", not "zero results".
Origin: incident 2026-06-01 (priority-score.sh read at header-only state →
false "zero candidates").

Helper code untouched; the 5 helper tests show identical baseline (18 failed
/ 34 passed both with and without this edit — failures are environmental,
`gh` absent). Wrapped fences valid bash; classify-each-candidate.sh:52 still
strips the `=== `-prefixed trailer.

Prior-art: skipped — SKILL.md call-site wiring only, no new capability (routes existing helpers through the already-merged run-helper.sh wrapper from PR #344).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(meta-orch): SKILL.md back to 600 lines — fold read-rule into §2 Step 1 (DRY)

aif's Stage 2 added two verbatim read-rule blockquotes (+4 lines), tipping
SKILL.md from 598 to 604 over the 600-line mechanical-check cap (real CI fail
on #345). Phase -1 reviewer fix: fold the read-rule once into the §2 Step 1
intro with 'applies to every background-helper fence below', drop the §2.5
duplicate. Both run-helper.sh wraps intact; read-rule discipline preserved, now
DRY. Not a capability commit (markdown edit).

Prior-art: skipped — markdown CI-fix (line-cap trim + DRY), no new capability.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(closure): bg-helper-completion-barrier done.md (final PR #345) (#347)

Last-stage merge marker per CLAUDE.md umbrella-closure convention — priority-score.sh
Layer C3 reads this for DONE detection.

Prior-art: skipped — closure marker file, no new capability (convention bookkeeping).

* feat(coordination): symlink-to-canonical cross-worktree sync (SSOT #110) (#346)

Build scripts/link-coordination.sh (per-file symlinks, adopt-then-link,
conflict-loud, seed on empty CANON), wire it into both worktree helpers
replacing the J5 rsync block, add link-coordination.test.ts (7 tests:
symlink, git-clean, conflict, write-back, paired-negative, adopt, seed),
reconcile worktree-setup-hydration.test.ts (drop rsync NON-DESTRUCTIVE,
retarget paired-negative regex to link-coordination.sh call, assert
symlinks), append SSOT row #110.

Umbrella count confirmed: 124 (kickoff said 123 — stale).

Prior-art: prior-art-evaluations.md#110 (cross-worktree gitignored-SSOT sync via symlink-to-canonical, verdict ADAPT) + research-patch 2026-05-17-cross-worktree-coord-doc-sync.md §5 — I-phase implementation of the settled verdict.

* docs(runtime-bridge): per-command CLI reference README (Option B) (#348)

Operator-facing reference manual for the six runtime-bridge CLI commands
(answer, await, dispatch, harvest, park, questions), one section each with
Usage / Flags / Example, derived from each cli/<cmd>.ts header docblock.
Resolves the parked README-structure fork → Option B (per-command reference).
Markdown only, 285 lines (< 600-line cap), no code changes.

Prior-art: skipped — documentation only, no new capability (operator-facing CLI reference derived from existing docblocks)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(discipline): surface genuine forks via AskUserQuestion (autonomous-default, human-gate on ambiguous only) (#349)

Operating model (maintainer decision 2026-06-01): autonomous by default;
the human-gate fires ONLY on a genuine ambiguous fork. A clear call is
decided and reported, not routed through a question.

- recommendation-laziness-discipline.md §3: fork-surfacing companion to the
  evidence rule — ambiguous forks MUST be surfaced via AskUserQuestion, never
  decided by a silent direct action; ask-question-reminder.sh is the
  discriminator; reworded 'default=surface' -> 'autonomous-default, surface
  only genuine forks'.
- recommendation-laziness-discipline.md §5: new anti-pattern
  #fork-decided-by-silent-action (incident: PR #348 battle-test, an
  explicitly-50/50 README fork auto-decided by running answer.ts directly).
  Judgment rule -> injection-only enforcement, never a gate (#gate-where-judgment-needed).
- end-of-turn-reminder.sh Branch-A: backstop reminder for the
  'decided a fork silently' path (the inverse of the existing offload check).

Hook tests 20/20 green. Origin: 2026-06-01 question-loop battle-test dialogue.

§1.7: forward — complies with rule-enforcement-channel-selection (judgment->injection),
no-paid-llm-in-ci (deterministic .sh + prose), doc-authority (no new heading); backward —
new anti-pattern scope = any-session fork behaviour, no existing rule superseded
(reviewer-discipline §2 is reviewer-scoped), enforced by two already-shipped hooks.

* feat(hook): orchestration-mode aware reminders + brainstorm cue (qloop items 2+6) (#350)

* docs(spec): hook-nudge orchestration-mode design (brainstorm item 2+6)

Design for orchestration-mode-aware behaviour of the two reminder hooks:
marker-gated Bug A fix (drop low-precision fork-regex in-mode) + recap
threshold lowering (b) + brainstorm nudge in ask-question-reminder.sh.
Normal mode stays byte-for-byte. Origin: 2026-06-01 qloop battle-test brainstorm.

Prior-art: skipped — design doc only, no new capability (markdown spec under docs/superpowers/specs/, no dependency/code-module).

* docs(plan): hook-nudge orchestration-mode implementation plan (item 2+6, TDD)

Prior-art: skipped — planning doc only, no new capability.

* docs(spec): add §3.4 aif-pull-channel brainstorm nudge (formatHuman footer)

Recursive-self-application gap from maintainer dialogue: ALL aif-parked
questions (not just I-ask-via-AUQ) must get the brainstorm-first nudge.
Footer ships in item 4's questions.ts PR (file-locality).

Prior-art: skipped — design doc only, no new capability.

* feat(hook): orchestration-mode gating — drop fork-regex (Bug A) + lower recap threshold (recap-b)

Marker .claude/orchestration-mode (mtime TTL); normal mode byte-for-byte.
Bug A: decision-MENTIONS stop false-firing the fork-challenge in-mode (regex
gated on orch_mode=false); real Qs (?/AUQ) still caught. recap-b: recap fires
on short structured status in-mode (threshold 500->200) keeping the markdown
gate so chatter stays silent. 7 new tests + 13 pre-existing green (20/20).
Spec: docs/superpowers/specs/2026-06-01-hook-nudge-orchestration-mode-design.md.

Prior-art: skipped — internal CC hook tuning, no new capability or dependency.

* feat(hook): brainstorm cue in ask-question-reminder (item 6)

Design/strategy forks -> superpowers:brainstorming, not a bare AUQ card.
Always-on prose nudge (judgment -> injection, never a gate). 1 new test (8/8).

Prior-art: skipped — internal CC hook tuning, no new capability or dependency.

* fix(runtime-bridge): surface mid-flight park in questions.ts + brainstorm footer (qloop item 4) (#351)

isParked gains the mid-flight-park case: a task whose blockedReason was wiped
by implementing→review still surfaces via the durable conjunction
(paused===true AND the plan carries the OPEN_QUESTION_ANCHOR). New shared
constant openQuestion.ts is the single SSOT for that anchor (writer park.ts,
reader questions.ts) so the wording cannot drift (dual-implementation-discipline
#two-prompts-drift).

formatHuman appends the §3.4 brainstorm-first footer on a non-empty parked list
(aif-pull-channel companion to ask-question-reminder.sh §3.3); empty case
unchanged. Reproduced from the verified container fix (branch
feature/qloop-realfix-d7585d), host-side, per handoff item 4.

Design SSOT: docs/superpowers/specs/2026-06-01-hook-nudge-orchestration-mode-design.md (§3.4).

Prior-art: skipped — bug fix on existing CLI + 17-LOC shared constant, no new capability/dependency.

* feat(runtime-bridge): commit aif rework leg in harvest (qloop item 3) (#352)

aif commits only on its approve_done && commitOnApprove path; the
request_changes→implementing→done rework path leaves the work uncommitted
(dirty tree, branch == base HEAD), so harvest had nothing to push. Extend the
egress: hasUncommittedChanges → commitAll (templated message, ZERO LLM) before
push. No-op on the normal path (tree already clean). Commit failure is fail-fast
— nothing pushed, operator gets the printed fallback. Real dep guards that the
container checkout is on the task branch before committing.

aif-handoff source untouched (foreign runtime, SSOT #27) — fix lives entirely
on our egress leg, matching harvest's graceful-degradation design.

Prior-art: skipped — extends existing harvest CLI, deterministic git only, no new capability/dependency.

* fix(principles): isolate p13 fixtures to tmpdir (kills p10↔p13 race) + exempt qloop-ux-probe probe (p12) (#353)

Two local pre-push false-blocks found by qloop-ux-probe (neither is a real violation):

F1 — principle 13 wrote its synthetic + c3-decoration fixtures into the REAL
docs/meta-factory/research-patches/ dir, then unlinked them. Under vitest's parallel
file run, principle 10's readdirSync globbed the transient file and ENOENT'd when 13
deleted it → a cross-file race (10 alone = 5/5 pass; full run = flaky ENOENT). Fix:
write the fixtures to os.tmpdir() — checkSection reads content only, PATCHES_DIR
membership was never required. Verified race-free across 3 consecutive full-trio runs.

F2 — principle 12 flagged the gitignored qloop-ux-probe kickoff (a 2-question resume
*test fixture*, not a work-delegating orchestration kickoff) for no AI-traps citation,
failing every local push. It already skips in CI (KICKOFFS_AVAILABLE false). Added to
EXEMPT_LIST with documented rationale.

Full principle suite: 157/157.

* fix(runtime-bridge): park.ts resolves API_BASE_URL so agent can park from inside aif container (#354)

park.ts is the only CLI run from inside the aif agent container, which exposes the
service as API_BASE_URL=http://api:3009 and does NOT set RUNTIME_BRIDGE_AIF_URL.
The old localhost-only default was unreachable there (connection refused) -> park()
got 'fetch failed' -> the agent could not park itself, so park-don't-guess (SSOT #109,
meta-orchestrator §4c) was non-functional on the real autonomous path.

New precedence: RUNTIME_BRIDGE_AIF_URL ?? API_BASE_URL ?? http://localhost:3009.
Host orchestrator (sets neither) still gets localhost; container gets api:3009.

Found by qloop-ux-probe Часть 2 live dispatch (taskId 5e32fcb9): agent correctly
surfaced-not-hacked the park failure, but the task slid implement->review->done at
$3.17 having parked nothing. resolveAifBaseUrl() extracted + unit-tested incl. a
negative guard proven to go red on regression.

* fix(runtime-bridge): don't record dedup on ManualBackend fallback + add --force (Finding B) (#355)

dispatch.ts recorded a dedup entry unconditionally after Step 4 — even when the real
backend threw (e.g. dirty_worktree 409) and it fell back to ManualBackend. That manual
entry (which created no autonomous task) then blocked a legitimate real retry for the
full 24h TTL, with no escape. qloop-ux-probe hit exactly this: after fixing the dirty
worktree, the re-dispatch was refused as "already dispatched (backend=manual)".

Fix:
- shouldRecordDedup(backendName): record only for a real backend success, never 'manual'.
- --force: skip the dedup check to re-dispatch the same kickoff deliberately.
- kickoff path resolved as the first non-flag arg, so --force may sit anywhere.

Three pure helpers, unit-tested incl. a negative guard. Full runtime-bridge suite 102 pass.

* fix(runtime-bridge): questions.ts recovers mid-flight-park question from plan anchor (Finding E) (#356)

A mid-flight park (paused:true + OPEN_QUESTION_ANCHOR in plan, blockedReason wiped by
implementing→review) surfaced in the list — but parkedReason() rendered "(no reason
recorded)" because it only read blockedReason + reviewComments, never the question text
park.ts appended under the plan anchor. So the chat channel showed "there is a question"
with no idea WHAT it asks (qloop-ux-probe Finding E, task ccebe39a).

Fix: extractOpenQuestion(plan) pulls the text after the last OPEN_QUESTION_ANCHOR line;
parkedReason precedence is now blockedReason → plan OPEN QUESTION → reviewComments →
"(no reason recorded)". Unit-tested incl. multi-anchor (last wins) + a negative guard.
Full runtime-bridge suite 100 pass.

* fix(runtime-bridge): park.ts probes reachable base URL — agent env scrubs API_BASE_URL (Finding C-2) (#357)

Finding C (#354) made park.ts READ API_BASE_URL, but the qloop-ux-probe live re-run proved
that insufficient: the aif agent runs park.ts in a Bash-tool subprocess whose env is
SCRUBBED of API_BASE_URL (the container's PID-1 env has it; the agent's exec env does not).
So resolveAifBaseUrl() still fell back to localhost:3009 → unreachable inside the container
→ "fetch failed" → the task slid to done at $3.45 without parking, exactly as before.

Fix: resolveReachableBaseUrl() probes candidate URLs and uses the first reachable one —
[RUNTIME_BRIDGE_AIF_URL?, API_BASE_URL?, http://api:3009 (docker service), http://localhost:3009
(host)], de-duplicated. Works from BOTH the agent container (api:3009) and the host
orchestrator (localhost) even when no env survives. main() now awaits the probe.

candidateBaseUrls + resolveReachableBaseUrl unit-tested (injected fetch) incl. a negative
guard proving it skips an unreachable first candidate. Full runtime-bridge suite 117 pass.

* feat(runtime-bridge): bridge-health.sh ($0 container-side preflight) + bridge-cleanup.sh (junk sweep) (#358)

Operator pain: "каждую сессию приходится чинить" + "мусор постоянно остаётся". Both are
structural — the recurring bugs live on the host↔container↔aif boundary that unit/CI
can't cover (a full agent run costs LLM tokens, no-paid-llm-in-ci), so they were only
found live MID-TASK; and probe/smoke runs leave orphan tasks + /tmp files behind.

bridge-health.sh — $0 deterministic CONTAINER-side preflight (docker+curl+grep, no task,
no tokens). Each check maps to a real qloop-ux-probe finding: container checkout clean
(A), park.ts carries the resolveReachableBaseUrl probe (C-2), a park candidate URL is
reachable from inside the container (C/C-2), dedup store has no stale manual entries (B).
Run at session start → "bridge red, fix first" instead of discovering breakage live.
Complements verify-bridge.sh (host-side dispatch smoke). Live-dogfooded — caught + fixed
a false-positive in its own reachability check (000 connection-fail vs real HTTP status).

bridge-cleanup.sh — one-command sweep of test/probe junk: aif tasks matching a test-title
allowlist (UXPROBE/smoke/probe — real work never matched), old /tmp ManualBackend kickoffs
+ dedup backups. Reports a container stash, never drops it (operator's git). --dry-run.

Prior-art: extends own verify-bridge.sh operator-smoke pattern (packages/runtime-bridge/scripts) — no external prior art applies; project-specific aif-bridge container diagnostic + test-task sweep, not a general capability with an upstream analog.

* fix(runtime-bridge): self-cleaning junk producers — junk no longer accumulates (no manual/AI sweep) (#359)

Operator: "придумать как быть с мусором — его не должен чистить каждый раз ИИ". A manual
cleanup script is still "someone cleans each time". The real fix is that each producer
cleans up after itself, automatically, with zero AI/manual involvement.

idempotency.ts — recordDispatch was append-only (file grew forever; a stale manual-fallback
entry blocked retries for the full TTL). Now it PRUNES-ON-WRITE: rewrites the log keeping
only within-TTL entries + the new one. Self-bounding, and stale entries (incl. Finding B's
manual fallback) auto-expire by age — no --force needed after TTL, no sweep.

ManualBackend.ts — dispatch wrote /tmp/runtime-bridge-<id>.md on every fallback and never
cleaned them (days of accumulation). Now each dispatch first prunes its OWN stale artefacts
(runtime-bridge-*.md older than 7d). Scope-guarded: only matches our files, only when stale,
best-effort (never throws into the dispatch path).

Pure cores (pruneStaleEntries / parseEntries / isStaleArtifact) unit-tested incl. negative
guards (stale entry must not survive; unrelated old .md must not be pruned). Full suite 126 pass.

This complements (does not replace) bridge-health.sh; bridge-cleanup.sh stays as a rare
manual "nuke everything" escape hatch, not the routine mechanism.

* fix(runtime-bridge): park.ts refuses to park at status=review (Finding F guard) (#361)

A park that happens AFTER the implement→review transition left the task
status=review, paused=true. On resume, aif runs the review pipeline straight to
`done` WITHOUT re-implementing the injected answer — and the next chain question
is never parked (live: task ba3b4bf6 answered c1 → done, c2 silently lost).
Root cause is upstream-intentional: aif's HUMAN_ACTIONS_BY_STATUS.review = []
(stateMachine.ts) — there is no human re-entry from `review` into implement.

Option B (per 2026-06-01-aif-task-isolation.md §4, maintainer-selected): a cheap
our-side guard. parkTask already GETs the task, so the status is in hand — refuse
the park at status=review and throw a clear message (park before review, or wait
for done and use answer.ts request_changes). Turns the silent loss into a loud,
actionable error. Does NOT add mid-flight re-implement (Option C, deferred).

Paired-negative test: GUARD refuses + issues NO PUT at status=review; CONTROL still
parks normally at a pre-review status (plan_ready). 19→21 tests green.

Prior-art: skipped — bug-fix guard on existing park capability, no new dependency or capability commit.

* docs: R-phase research-patch — aif task-isolation (Findings A + F) (#360)

* docs(runtime-bridge): R-phase research-patch — aif task-isolation Findings A + F

Finding A (dirty_worktree recurs): verdict config-fix. Per-task worktrees are gated by
a 3-condition AND in planner.ts:191-195 — env AIF_TASK_WORKTREES_ENABLED=true is only
gate 1; gate 2 project.parallelEnabled (DB parallel_enabled, web-UI toggle) is 0 and was
never set, so worktrees are never created and the shared checkout is reused in-place →
dirties → next dispatch 409s. Fix: flip parallel_enabled (no restart).

Finding F (mid-flight park resumes review->done, never re-implements): verdict
aif-upstream-issue (intentional design) + our-side-guard. stateMachine.ts:106
HUMAN_ACTIONS_BY_STATUS.review=[] -> no human re-entry into implement; request_changes
only from done. DECISION-NEEDED for I-phase: Option B (park.ts guard rejecting parks at
status=review) vs Option C (auto request_changes loop, ~4h).

R-phase only — no production code changed. Evidence: parallel_enabled=0 (live DB query),
worktree_path null across 10 tasks, reflog >=6 in-place checkout cycles, planner 3-gate +
stateMachine review:[] source-verified. Scope annotation (principle 10) + §1.7 (principle 13).

* docs(runtime-bridge): record Finding F resolution — Option B selected + shipped (#361)

§4.1 + §7.3: DECISION-NEEDED-F resolved 2026-06-01. Maintainer selected the
hard-guard path (surfaced as "Option B"). Records the exact shipped behavior
honestly: park rejects only at status=review (the §4.1 Option-A semantics, NOT
the stricter implementing-only Option B) — pre-implement parks (plan_ready) stay
legitimate. Cross-refs PR #361. Option C deferred.

* feat(runtime-bridge): ensure-parallel guard — self-heal Finding A on any instance (#362)

aif creates a per-task git worktree only when project.parallelEnabled=1 (gate 2
of 3 in planner.ts). That flag is a DB field with NO env/config.yaml knob — only
the web UI or raw DB sets it — so a freshly-provisioned instance has it 0, every
task runs in-place on the shared checkout, and the next dispatch 409s on
dirty_worktree (research-patch 2026-06-01-aif-task-isolation.md §1-§2; the live
config-fix applied to this instance does not propagate to other instances).

ensureParallelEnabled(baseUrl, projectId): GET /projects → if parallelEnabled is
already true, no-op; else PUT /projects/:id with parallelEnabled=true. Wired
best-effort into AifHandoffBackend.dispatch() Step 0 (warns + proceeds on failure,
never blocks dispatch) and shipped as a standalone CLI (--project / env).

Anti-clobber (load-bearing): aif has no targeted parallelEnabled write (only
PATCH /:id/auto-queue-mode), and the full PUT NULLs any omitted *MaxBudgetUsd
(@aif/data updateProject: `x ?? null`). So the guard round-trips the FULL field
set read back from GET — flipping ONLY parallelEnabled — to preserve a UI-set
budget. Paired-negative test pins this: a set budget MUST survive the PUT.

Cleaner long-term fix = an upstream PATCH /projects/:id/parallel-enabled mirroring
the existing auto-queue-mode PATCH (file against lee-to/aif-handoff); this guard
is the our-side stopgap. 6 new tests green; runtime-bridge typecheck clean.

Prior-art: prior-art-evaluations.md#109 (park.ts BUILD — same class: thin our-side runtime-bridge consumer CLI over aif REST where no upstream targeted primitive exists; here parallelEnabled-reconcile, no upstream PATCH beyond auto-queue-mode; cleaner fix REFERENCEd to upstream, this is the stopgap).

* coordination-persistence-fix (#363)

* feat(coordination): link-coordination.sh --on-conflict=canon|worktree|skip (default skip)

Adds --on-conflict flag with three modes resolving the real-file-in-both
conflict deterministically: skip (default, exit 1, never clobber), canon
(canonical wins, relink), worktree (worktree wins, adopt into CANON).
Invalid values exit 2. TDD: 4 tests in link-coordination.test.ts.

Prior-art: skipped — extends existing scripts/link-coordination.sh (SSOT #110), no new capability/dependency; arg-parse + branch logic only, no new file/dep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(coordination): link-coordination.sh shares root _plan-cache.md + _master-backlog-delta.json (Part-2, SSOT #110)

Adds a ROOT-FILE adopt-then-link + link pair so the two root-level
gitignored coordination files (_plan-cache.md, _master-backlog-delta.json)
are shared across worktrees — previously unmatched because the loops only
iterated umbrella subdirs */. Tracked root README.md untouched.
TDD: 3 tests in link-coordination.test.ts.

Prior-art: skipped — extends existing scripts/link-coordination.sh (SSOT #110), no new capability/dependency; mirrors existing umbrella adopt/link loops for root files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(hooks): i18n reminder payload — en canonical / ru operator (+ principle-12 exempt hygiene) (#364)

* feat(hooks): i18n — split reminder payload into lang packs (en canonical / ru operator)

The two reminder hooks (ask-question-reminder, end-of-turn-reminder) emitted
Russian payload prose inline. Splits logic from payload: hooks keep all logic
and source a language pack (.claude/hooks/lang/{en,ru}.sh) selected by
AIF_HOOK_LANG (default en — canonical, for the public repo). Operator sets
AIF_HOOK_LANG=ru once globally in ~/.claude/settings.json.

- lang/en.sh + lang/ru.sh: aif_msg_* functions (${anchor} resolved at call time
  via dynamic scope) + AIF_RECAP_MARKER (recap heading; guard and message read
  the same var so they stay language-consistent).
- lang/check-parity.sh: deterministic, no-LLM guard against #two-prompts-drift —
  asserts both packs expose the same key set. Local/reviewer step, not a blocking
  gate (hooks are @cc-only, not shipped).
- Tests run the existing cases under AIF_HOOK_LANG=ru (RU-pack contract; Russian
  transcripts + assertions unchanged) + add an en-pack smoke per hook.
- RU payload byte-identical to before; EN is a faithful translation. Behaviour
  preserved (30/30 hook tests green).

Out of scope (surfaced): Russian code comments in the two hooks; other 11 hooks;
questions.ts strings. Spec: docs/superpowers/specs/2026-06-01-hook-lang-i18n-design.md

Prior-art: skipped — i18n refactor of existing internal hooks, no new capability (string externalization to lang packs, no dependency, no packages/ code module)

* test(principle-12): drop stale 'qloop-ux-probe' from EXEMPT_LIST

The qloop-ux-probe question-loop fixture dir (gitignored, added to EXEMPT_LIST
2026-06-01 by Finding F2) is gone — the positive-guard test correctly flagged it
as a stale exempt entry ("exempted but now absent"). Remove it per the test's own
instruction; 'aif-ssot-corrections' remains the sole exempt entry.

Surfaced while unblocking the hook-i18n PR's local pre-push (principle 12 runs on
gitignored kickoffs locally, skips in CI). Maintainer-invited in-session.

Prior-art: skipped — principle-test allowlist hygiene, no new capability (one stale entry removed from EXEMPT_LIST + doc comment)

* fix(runtime-bridge): bridge-cleanup §4 prunes MERGED container git junk (#365)

Enabling per-task worktrees (parallel_enabled, #362) makes aif RETAIN worktrees
post-terminal (research-patch 2026-06-01-aif-task-isolation.md §2.1), and the old
in-place task checkouts had already left 14 stale feature/* branches on the shared
checkout. The session janitor (#358/#359) deliberately did NOT touch container git.

§4 extends it to prune ONLY container git merged into origin/staging: git worktree
prune + remove retained non-main worktrees on merged branches + delete merged
feature/* branches. SAFE BY CONSTRUCTION — `git branch --merged` filtering AND
`git branch -d` (refuses unmerged) double-guard; unmerged in-flight work is never
touched. Also fixes the docker-exec stdin bug (needs -i for `sh -s` heredoc).

Completes the #359 "no manual/AI sweep" principle for container git state. This is
operator/co-located tooling (docker exec); the portable fix is upstream auto-removal
of retained worktrees (worth filing against lee-to/aif-handoff).

Live-verified on the real container: dry-run + real run deleted 7 merged branches
and preserved all 6 unmerged (+ the checked-out one). No unit test — matches the 4
untested sibling janitor scripts; safety is by-construction + live paired-evidence.

Prior-art: skipped — extends an existing janitor script (no new dependency, no new >=80 LOC file, not a capability commit); the parallel-worktree capability it cleans up after is #362/#109.

* feat(audit-self): bash-hook mutation testing — bash.rules + run-bash-mutation.sh (universalmutator ADAPT, local/on-demand) (#366)

Stage 2 B.2 of mutation-discipline-umbrella. ADAPTs universalmutator (MIT,
deterministic regexp engine + analyze_mutants harness — no LLM) to
mutation-test .claude/hooks/*.sh against their paired-negative vitest
tests. The only project-authored artefacts are bash.rules (operators), a
thin wrapper, and the wrapper's own paired-negative test. Delivery =
session-bound / local on-demand (NOT CI), mirroring Stryker-as-devDep;
the original kickoff B.3 CI job is dropped. universalmutator is a dev-time
tool, never consumer-shipped.

Dogfood gate (mandatory) surfaced real, undocumented test gaps:
end-of-turn-reminder 52% / validate-prompt 56% (both below the 60% floor)
— the &&/|| mutation of their suppress/skip conditions survives uncaught.
Recursive bootstrap: the wrapper mutated by its own operators is killed
13/13 by its own test. Found + fixed a /*-in-comment regexp-engine
limitation (bash globs in comments falsely trigger C-block-comment mode)
via a line-count-preserving sanitiser.

Evidence: docs/meta-factory/research-patches/2026-06-02-bash-mutator-b2-shipped.md

Prior-art: prior-art-evaluations.md#91 (universalmutator ADAPT — engine ADOPTed verbatim, only bash.rules operators authored; problem-class ~85% match per B.1 §B.1.5; mutahunter REJECTed on no-paid-llm-in-ci §1).

* feat(coordination): persistence-fix tail — SW-B symlink-aware helpers + G/B/C installer (#368)

* docs(coordination): design — cross-worktree persistence fix (4-channel: git-hook agnostic floor + CC + Superset + skill)

Brainstorm output for coordination-persistence-fix umbrella. T16 corrections:
- channel C substrate = Superset projects/<uuid>/config.json setup-array (rsync = root cause), NOT ~/.superset/hooks/
- adds git post-checkout (.husky) as the harness-agnostic floor per maintainer 'works everywhere' requirement
- Q3 atomicity already satisfied (mktemp+mv verified); SSOT #110 same anchor

Prior-art: skipped — design doc only, no new capability (extends link-coordination.sh SSOT #110).

* docs(coordination): correct §2.4 — helpers atomic but NOT symlink-preserving (mv replaces symlink)

Planning-phase finding (verified empirically): mv tmp <symlink> replaces the
symlink with a real file → Part-2 cross-worktree share lost on first cache write.
SW-B upgraded from 'confirm test' to 'make 3 helpers symlink-aware (write-through
resolved target)'. Atomicity preserved (temp+mv in target dir), symlink preserved.

Prior-art: skipped — design doc correction only, no new capability.

* docs(coordination): implementation plan — 4 sub-waves (A linker / B symlink-aware helpers / C wiring / D self-apply)

Prior-art: skipped — plan doc only, no new capability (implements SSOT #110 extension).

* feat(coordination): one-click install-coordination-wiring.sh (G+B+C, idempotent, self-test)

Applies all 3 wiring channels in one terminal command (maintainer runs from central repo):
  G  .husky/post-checkout            (agnostic floor)
  B  .claude/settings.json SessionStart
  C  ~/.superset/projects/*/config.json setup (replaces rsync root-cause)
--self-test (sandbox) + --dry-run modes; idempotent re-runs. Replaces the manual runbook.

Prior-art: skipped — scripts/ helper, not a capability commit (no dep, not under packages/); wires existing link-coordination.sh SSOT #110.

* fix(coordination): 3 cache/delta helpers symlink-aware (SW-B)

resolve_target() follows a symlink to its real $CANON target so the atomic
temp-then-mv write writes THROUGH the link instead of replacing it. Without this,
the first cache/delta write breaks the cross-worktree share (mv tmp <symlink> →
real file). +3 paired-negative tests (symlink preserved + canon written through);
18/18 green. Done locally (not via aif — container serialized on doc-audit task).

Prior-art: skipped — symlink-preservation fix to existing helpers, no new capability; extends link-coordination.sh SSOT #110 share semantics.

* docs(coordination): drop channel A (skill self-heal) — redundant after G git-hook

B (CC SessionStart) fires at every session start ⊇ 'when meta-orchestrator runs',
and G (git post-checkout) is the real agnostic floor A was standing in for.
Final channel set = G + B + C, wired via install-coordination-wiring.sh.

Prior-art: skipped — design doc scope-reduction, no new capability.

* fix(runtime-bridge): bridge-cleanup §4 skips while aif has live tasks (no ref-lock contention) (#367)

§4 (container git prune, #365) runs git fetch / worktree prune / branch -d in the
SHARED .git. A task running concurrently touches the same ref + packed-refs locks
(amplified by parallel_enabled keeping multiple tasks active) → a collision yields
"cannot lock ref" and can fail the live task's git step.

Guard: GET /tasks, count non-terminal (not done/verified/cancelled); if >0, skip §4
with a clear note ("re-run when idle"). Deterministic, no AI. Fail-open (count unknown
→ 0 → proceed) — §4 is already merged-only safe-by-construction, so this is defence-in-
depth; worst case of a false 0 is a transient lock retry, never corruption or data loss.

Live-verified: with 2 non-terminal tasks running, `--dry-run` §4 printed "skipped — 2 live".

Prior-art: skipped — edits an existing janitor script (no new dependency, no new file, not a capability commit); guards the #365 sweep against the #362 parallel_enabled concurrency window.

* feat(coordination): commit channels G + B (post-checkout + CC SessionStart) (#369)

Makes the two machine-agnostic wiring channels durable + shared (were applied
locally by install-coordination-wiring.sh). Both call link-coordination.sh (SSOT #110):
  G  .husky/post-checkout       — agnostic floor, fires on git worktree add
  B  .claude/settings.json      — CC SessionStart re-heal
Channel C (Superset setup-array, per-UUID) stays installer-only. SW-D liveness:
G verified live — fresh worktree auto-linked 287 files.

Prior-art: skipped — wiring of existing link-coordination.sh SSOT #110 into hook channels, no new capability.

* docs(research-patches): doc-audit-ship-boundary Stage 2 findings + gitignore stryker sandbox (#371)

Autonomous aif audit (task bcb2c6c2): full doc-vs-reality + vs-goal reconciliation,
Stage 2.0 mutation-run fuel, DECISION-NEEDED batch (DN-1..4), §A self-classification.
Audit-only — no maintainer-owned edits landed. Also gitignore packages/core/.stryker-tmp/
so mutation runs never pollute the tree (root cause of PR #370 contamination).

* test(hooks): close mutation gaps in end-of-turn-reminder + validate-prompt (B.2 dogfood follow-up) (#373)

The Stage 2 B.2 bash mutator (#366) surfaced two hook tests below the 60% kill
floor: end-of-turn-reminder (52%) and validate-prompt (56%). Add targeted cases
that kill every NON-equivalent surviving mutant:

- end-of-turn-reminder 52% → 69%: cover the previously-untested idle-suppress
  re-ping guard (hook:135-156) — suppress-on-reping, fire-on-new-question,
  long-answer-overrides-suppress, the B2 "an AskUserQuestion turn is never
  idle-suppressed" contract — and the bare non-question tool-call early exit
  (hook:53-54). The 10 residual survivors are equivalent mutants: 7× `|| true`
  → `&& true` inside `$(…)` command-substitutions (stdout unchanged, exit
  ignored under set -uo, no -e), the transcript-validity guard (both paths exit
  0/empty for the only diverging input), `set -e`→`set +e` (hook never relies on
  errexit aborting), and the inner re-ping AND→OR (divergence requires
  prev==last+recap, which trips the self-recap guard first). All 22 killable
  mutants are killed.
- validate-prompt 56% → 78%: deterministically exercise the tsx-unavailable
  graceful-skip (hook:26-27, isolated-root copy) and the jq-unavailable
  graceful-skip (hook:14-16, fake-bin PATH without jq). The 2 residual survivors
  are gh+validator-coupled (hook:21 off-path-proceeds-to-validator, hook:33
  validator-result) — killable only with a live-gh 404 test, which is
  non-hermetic; left per T-MUT-B rather than shipping a flaky test.

Test-only; no production hook modified. Follow-up to #366
(mutation-discipline-umbrella Stage 2 B.2).

* docs(doc-audit): apply DN-1/2/4 doc-staleness fixes + ship the fixer tooling (#374)

doc-audit-ship-boundary Stage 2 — maintainer decisions 2026-06-02, applied.

Applied (maintainer-directed, DN-1/2/4):
- .claude/rules/rule-enforcement-channel-selection.md: 'Activation pending' →
  'Activation confirmed — settings.json:114' (Class header + §4 prose); the
  PostToolUse Edit|Write hook is wired (verified settings.json:114).
- README.md badges: ?branch=main → ?branch=staging (main=prod, CI runs on staging).
- README.md enforcement chain: drop false 'Stryker' from the CI channel —
  mutation tests are local/session-bound, NOT a CI gate (no workflow runs Stryker).

Tooling shipped alongside:
- scripts/apply-doc-fixes.sh: idempotent, terminal-runnable fixer (re-applies the
  above; --check dry-run). packages/core/hooks/apply-doc-fixes.test.ts: 5 green.
- docs/meta-factory/mutation-run-placement.md: DN-4 placement note (mutation local;
  ps/jq/universalmutator reqs).

Prior-art: skipped — maintainer-directed doc-staleness fixes + their fixer tool +
a placement note; no new runtime capability, no dependency, no packages/ code module.

* test(principle-12): raise population-sentinel bounds 10..100 → 100..300 (#375)

The principle-12 population sentinel capped local kickoff dirs at 100. The project
legitimately accumulated >100 umbrellas of history, mirrored locally by the
coordination-persistence channel (G, .husky/post-checkout → link-coordination.sh),
so the ≤100 guess tripped the pre-push on every worktree. Raise to ≤300 (still
catches a runaway glob) and ≥100 (doubles as a 'mirror present' guard). The
sentinel is skipped in CI (KICKOFFS_AVAILABLE) — it only gates local pre-push.

Maintainer-directed 2026-06-02. Does NOT resolve the separate citation-check vs
symlink-mirror interaction (the broader principle-12-vs-G item) — bound only.

Prior-art: skipped — adjusts a sentinel bound in an existing principle test to
match reality; no new capability, no dependency, no new module.

* test(principle-12): citation check ignores coordination-mirror kickoffs (#376)

Citation check re-ran on every umbrella mirrored in by channel G (post-checkout →
link-coordination.sh symlinks gitignored kickoff.md into $CANON), failing on
historical umbrellas this worktree never authored (principle-12-vs-G). Fix: check
only REAL (non-symlink) kickoff.md = locally authored. Population sentinel still
counts the FULL set as a mirror-present guard. Pairs with #375 (bound→100..300).

Prior-art: skipped — narrows an existing principle-test filter to exclude symlink
mirrors; no new capability, no dependency.

* docs(spec): aif parallel autonomous dispatch — verified model + open test (brainstorm 2026-06-02) (#377)

Captures the full session conclusions so they survive: the depth-2 architecture insight
(subagents don't spawn subagents → quality parallel needs separate top-level sessions, meta-
orchestrator decides), the verified aif model (1 kickoff/meta-launch → 1 task; each task = top-
level session; flat task model; two parallelism layers L1 cross-task worktrees / L2 within-task
use_subagents→implement-coordinator; use_subagents=0 everywhere = why never parallelized;
worktree-skip is documented per docs/configuration.md), the observable test signal
(agent_activity_log 'Subagent: <name> started', subagentQuery.ts:793), the decision tree, the
OPEN question gated on one live test (use_subagents top-level launch), an honest flip-flop
corrections log, and decisions (planner-owns-plan, COORDINATOR_MAX knob, park preserved).

NOT a final spec — gated on the §5 live test on a genuinely-parallel task (D.1-D.5).

* chore(harvest): commit reworked aif task 5767f7df-7425-4a78-a76f-98402c1647d0 — mutation-discipline-stage-c-meta-launch (#378)

* docs(spec): aif parallel dispatch §5 RESOLVED — YES via use_subagents (live proof) + M3-bash timeout fix (#380)

* docs(spec): aif parallel dispatch §5 RESOLVED — YES, self-parallelizes via use_subagents (live 2026-06-02)

Live test (task a37ee2f5): use_subagents=true on a real 5-task plan (mutation
Stage 4 D.1-D.5) → implement-coordinator launched top-level (--agent) → fanned
out 5 concurrent implement-worker subagents, each worktree-isolated. Proof:
5x `Tool: Agent {subagent_type:implement-worker}` spawns + git worktree list
1→6 (5 locked worker worktrees coexisting, ~2min all-5 overlap) + 5 distinct
real test files written simultaneously.

Also corrects §3.7 observable: `Subagent:` log line is SDK-transport-only
(onSubagentStart wired only via SDK SubagentStart hook); on transport=cli it
stays 0 even under full parallelism — a false-negative trap. Valid CLI-transport
observables = git worktree growth + `Tool: Agent` activity entries.

Design consequence = the tiny change: bridge sets use_subagents=true at dispatch
for depth-2-sufficient umbrellas + meta-orchestrator emits plan-structured
kickoffs (independent numbered Task items, no Phase headings).

Prior-art: skipped — design-doc update only, no new capability/dependency/code-module.

* test(principle-02): give M3-bash mutation gate a 60s timeout (was default 5000ms)

The [M3-bash] local/on-demand gate (describe.skipIf(!HAS_UM)) calls
runBashMutator → spawns `npx vitest` once per mutant, ~9.5s on a loaded host.
The default 5000ms vitest timeout false-fails it at pre-push (blocking unrelated
pushes; observed this session). Add an explicit 60_000ms timeout — mirrors the
Stryker precedent (principle 11 testTimeout 5s→30s). No assertion/semantics
change; CI still skips it (no universalmutator).

Prior-art: skipped — test-timeout fix for an existing gate, no new capability/dependency.

* test(mutation-discipline-stage-d): paired-negative tests for 5 remaining .sh helpers (#381)

Stage 4 D of the mutation-discipline umbrella — content-level paired-negative
vitest tests (M.4 pattern) for the .sh files that lacked them:
- priority-score.sh        → priority-score-synthetic.test.ts   (20 it, 53 expect)
- plan-currency-check.sh   → plan-currency-check-kickoff.test.ts ( 6 it, kickoff-existence angle; non-redundant w/ existing)
- launch-table-generator.sh→ launch-table-generator.test.ts     (11 it)
- check-skill-drift.sh     → check-skill-drift.test.ts          ( 6 it)
- pre-push.fallback.sh     → pre-push.fallback.test.ts          (20 it)

Produced by the aif self-parallel-dispatch live test (5 concurrent implement-workers,
use_subagents=true) and verified by an independent 5-way adversarial cold-review
(T19): all real-script-invoked (spawnSync bash on the actual .sh), content-level
assertions (exit code + stdout/stderr, no shape-only .toBeDefined), anti-tautology
confirmed. Fixed one over-reaching T12-compliance comment in priority-score
(claimed 8 surfaces verified; only 5 are — now honest + deferred-gap note).

Known deferred coverage gaps (honestly disclosed, follow-up): launch-table
fallback+no-keyword path; pre-push 2 of 3 discipline-file patterns;
priority-score surfaces (c)/(d)/(h). 52/52 tests green.

Prior-art: skipped — test additions for existing .sh capabilities, no new runtime capability/dependency.

* research(mutation-discipline-d6): verdict on deps-hash-check.sh duplication (#382)

D.6 R-phase (mutation-discipline umbrella). Investigates the two byte-identical
deps-hash-check.sh copies. Verified roles (T3, file:line): packages/core/hooks/
= shipped SOURCE (install.sh:261), .claude/hooks/ = this repo's DOGFOOD copy
(settings.json:74). Key finding: deps-hash-check.test.ts:34 tests the DOGFOOD
copy, not source → silent drift possible (test stays green if source diverges).

Verdict (b) keep-both + drift-check: add @dual-pair markers + a byte-identity
assertion test — REUSE the existing dual-implementation @dual-pair convention
(SSOT #87) rather than BUILD. Symlink (a) rejected (install.sh --force regression
+ Windows). Concrete I-phase fix path + falsifiers included. R-phase only — no
edits to the .sh files / settings.json / install.sh.

Produced autonomously by aif (use_subagents=0, single R-phase task) + cold-reviewed.

Prior-art: skipped — research-patch (markdown), no new capability/dependency/code.

* fix(hooks): drift-check for deps-hash-check.sh source/dogfood pair (D.6 I-phase) (#383)

Implements #382 §6 verdict (b) keep-both + drift-check. Adds
@dual-pair: deps-hash-check-dogfood markers (identical block, preserving
byte-identity) to both the shipped SOURCE (packages/core/hooks/) and this
repo's DOGFOOD copy (.claude/hooks/), plus a byte-identity assertion in
deps-hash-check.test.ts.

Closes the silent-drift hole the D.6 R-phase confirmed: the functional tests
exercise only the dogfood copy (HOOK), so they stayed green even if the source
diverged. Falsifier verified — breaking one copy turns the new test red.

Corrected #382 §6's asymmetric-marker spec (4-line block to dogfood vs 1-line
to source), which would itself have broken the byte-identity the test asserts;
both copies carry the identical block.

Last residual of the mutation-discipline umbrella; closure (done.md + memory)
follows on merge.

Prior-art: prior-art-evaluations.md#87 (worktree-create-setup dual-pair, SSOT BUILD — this commit extends the @dual-pair pattern with a second pair; no new mechanism introduced).

* docs: R-phase — why per-task worktrees never materialize (corrects #360 Finding A) (#372)

* docs(runtime-bridge): R-phase — why per-task worktrees never materialize (corrects #360 Finding A)

Live falsifier of #360 Finding A: 0 tasks ever got a worktree despite parallel_enabled=1.
Root cause (verified source, correct containers): the bridge dispatches via a planner-SKIP —
POST /tasks/:id/events {event:'accept_existing_plan'} — which transitions backlog→plan_ready
SKIPPING the `planning` status (stateMachine.ts case accept_existing_plan, api container).
The coordinator runs runPlanner ONLY for the planning stage (coordinator.ts PIPELINE
from:["planning"], agent container). The ONLY worktree-creation code lives in runPlanner
(planner.ts:191-213). ⇒ bridge tasks never run the planner → never create a worktree,
regardless of parallel_enabled / AIF_TASK_WORKTREES_ENABLED / plannerMode.
Secondary: handleAcceptExistingPlan also persists branchName (taskEvents.ts:236+) so even if
the planner ran, planner branch-2 (in-place restore) would pre-empt branch-3 (worktree).

Verdict: parallel_enabled=1 (#360/#362) is NECESSARY BUT NOT SUFFICIENT. Fix options:
B = drop accept_existing_plan from bridge dispatch + let the coordinator run the planner;
C = accept serial in-place as the operating mode with the existing rollback guard.
Both deferred to a follow-up I-phase (DECISION-NEEDED). gate-3 passes; mode-change is NOT
the fix (server already forces full). Effort + falsifier per finding in the patch.

R-phase only — no production code changed. DeepWiki x3 (converged) + verified file:line in
both aif containers. Scope annotation (principle 10) + §1.7 (principle 13).

* docs(runtime-bridge): §0.1 — worktree-skip is DOCUMENTED aif behaviour (docs/configuration.md), not a trap; no upstream issue

DeepWiki x3 (converged): docs/configuration.md ties worktree creation to full-mode
planning by design; docs/architecture.md documents the serial-forcing. So planner-skip
forgoing the worktree is a documented consequence, not a defect — no upstream issue.
Records the search-coverage lesson: the #360 verdict was avoidable by reading aif's own
config docs. The corrected fix (run the planner) is the documented isolation+parallel path.

* docs(wave-plan): reconcile §0 → 2026-06-02 (#298→#382) + codify how §0 stays current (#384)

Manual status reconciliation per the design loop (detect via /meta-orchestrator,
write by human-directed session — Direction A auto-write rejected at R-phase).

- §0 + Status dates 2026-05-29 → 2026-06-02
- correct misleading aif-handoff DEFER-ALL row: add `runtime-bridge BUILD` row
  (minimal-REST bridge shipped #312/#313 + ecosystem; supersedes-in-practice)
- +8 umbrella rows for work merged since last reconcile (runtime-bridge,
  aif task-isolation, companion adoption, coordination-persistence, qloop,
  mutation-discipline, worktree dual-channel, doc-audit-ship-boundary)
- add "Open frontier" block (live: #372 open, inject-layer-extension,
  one-click-installer, f2-aif-fire-backend) — distinct from ~80 closed candidates
- add "How §0 stays current (by design)" note to Status block so future
  sessions don't try to automate the deliberately-manual reconciliation

Prior-art: skipped — doc-only status reconciliation of an existing planning doc, no new capability or dependency introduced.

* chore(orchestrator): mark mutation-discipline-umbrella DONE (#385)

Umbrella closure per CLAUDE.md "Umbrella closure convention" — Final PR #383
(D.6 drift-check, last residual) merged to staging. All stages shipped:
A audit #219 / B bash-mutator #366+#373 (dogfood gate fired) / C tests-of-tests
#378 / D #381 + D.6 dup R-phase #382 + D.6 drift-check I-phase #383.

done.md is the priority-score.sh Layer C3 completion marker (gitignore:14
un-ignores */done.md).

Prior-art: skipped — completion marker (done.md), no new capability/dependency/code.

* fix(principle-12): skip anti-tautology mutation test when no real (non-mirror) kickoffs exist (#386)

The anti-tautology sub-test mutates a REAL compliant kickoff to verify the
compound-citation detector actually catches absence. After the coordination
symlink migration (#346 + post-checkout link-coordination.sh), every local
kickoff.md is a SYMLINK into $CANON — a coordination mirror that
getNonExemptEntries() deliberately excludes (isCoordinationMirror). With all
kickoffs mirrored, getNonExemptEntries() is empty → `compliant` is undefined →
`expect(compliant).toBeDefined()` failed on every host push (reproduced on
staging HEAD, independent of any feature branch).

The #376 mirror-exclusion fix updated the main check + population sentinel but
missed this meta-test. Guard it with HAS_REAL_NONEXEMPT_KICKOFF: when there is
no real kickoff to mutate, skip — detector correctness is already covered by the
pure-logic anti-tautology tests (blank file / each pattern), which need no files.

Unblocks all host pushes (pre-push runs the principle suite). No behaviour change
to the citation enforcement itself.

* fix(runtime-bridge): dispatch via planner-run so each task gets its own worktree (parallel) (#387)

The bridge dispatched tasks with `accept_existing_plan`, which transitions
backlog -> plan_ready SKIPPING the `planning` stage. runPlanner (planner.ts,
the ONLY code that creates a per-task git worktree) runs only at `planning`,
so 0 tasks ever got a worktree -> all dispatched tasks forced serial. Verified
+ live-proven in #372 (research-patches/2026-06-02-aif-worktree-gap.md).

Fix (Option B, live-verified 2026-06-03):
- Drop the `accept_existing_plan` event. The task stays at `backlog`; the
  auto-queue advances it through `planning` -> runPlanner -> per-task worktree.
- Carry the kickoff in `description` (the planner INPUT spec, planner.ts:246),
  NOT in `plan` (the planner OUTPUT slot @planPath, which it overwrites). The
  planner plans the *how* from our kickoff *what*.

Live acceptance: two tasks dispatched concurrently each got a DISTINCT
worktree and both ran in `planning` simultaneously (N agents x N workspaces,
not within-task subagents). Throwaway probe tasks + worktrees cleaned up.

Tests: rewrote the dispatch sequence test for the 2-step flow (asserts
description=kickoff, no accept_existing_plan, no plan push) and moved the
rollback test's failure point to the unpause step. 8/8 green.

Prior-art: skipped — modifies existing bridge file, no new capability/dependency/module.

* research(aif-operator-asset-access): R-phase — gap verification + A-E verdict + recommended composition (#388)

Five gap rows re-confirmed with file:line evidence. Existing skill-context/ and agents/*.md
mechanisms audited (T16 problem-class check) — none cover orchestrator/reviewer discipline.

Recommended composition: B+D (in-repo agnostic worker discipline, primary) + A (operator
bind-mount, immediate) + C (buildKickoffSpec sibling inlining, follow-on). E rejected.
DN-1/2/3 surfaced; DN-1/2 resolved inline (B scope = minimum viable worker discipline;
reviewer-discipline = separate concern covered by review-sidecar.md).

Prior-art: prior-art-evaluations.md#67 (aif-handoff, ADOPT — this umbrella extends it with
operator-asset bridging); prior-art-evaluations.md#50 (skill-context/aif-review SSOT,
ADOPT channel — D reuses this delivery pattern).

* fix(meta-orchestrator): Stage 2 correctness — C1.1/C1.2/C1.3/M4/F2/F7 (#389)

Six confirmed bugs fixed in the meta-orchestrator skill:

C1.3 — SKILL.md:449: stale repo-root mirror claim removed; replaced
  with "Ships directly from .claude/skills/meta-orchestrator/ via
  install.sh (single source of truth; no repo-root mirror — Item 12
  closure 2026-05-25). Install pattern at install.sh:236-255."

C1.2 — SKILL.md:508: state.md section names corrected from stale
  "§2 Launch-table / §3 Dispatch log" to actual template names
  "§2 Decisions / §3 Phase -1 verdict" (state.md.template:26,35).

C1.1 — master-backlog-delta.md:49-52: "Body owns arrays — inline-jq"
  claim removed (inline jq was removed in F.3 PR #261 2026-05-28);
  delta-write-from-state.sh correctly attributed as array owner.

M4 — SKILL.md:521-525: delta two-helper ordering fixed. update-delta.sh
  (bootstraps schema) now listed first, matching delta-write-from-state.sh:19.

F2 — SKILL.md:71: head -200 raised to head -400; wave-sequencing-plan.md
  is 293 lines so head -200 dropped 93 lines incl. §6 dependency-matrix.

F7 — run-helper.sh:26: timeout wrapped (MO_HELPER_TIMEOUT env, default 120s);
  END trailer still fires on expiry (rc=124). failures.md: F9 added.

Prior-art: skipped — correctness fixes to existing prose/helper, no new capability

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(aif-worker-discipline): portable worker discipline agent + AIF skill-context delivery (#390)

* feat(aif-worker-discipline): portable worker discipline agent + AIF skill-context delivery

Artifacts 1 (B) and 2 (D) from aif-operator-asset-access I-phase.

- agents/orchestrator-worker-discipline.md: portable AI-agnostic worker
  discipline (REPORT schema, park-vs-proceed, stage-gate, §1.7 PR body)
  for aif-dispatched workers. Class B. @dual-pair: aif-orchestrator-discipline.

- packages/core/templates/shared/skill-context/aif-orchestrator-discipline/SKILL.md:
  AIF-native delivery of the same content. YAML from aif-review/SKILL.md (SSOT #50).
  @dual-pair + spec: per dual-implementation-discipline.md §5.

- packages/core/principles/09-doc-authority-hierarchy.ts: added
  aif-orchestrator-discipline/SKILL.md to REQUIRED_HEADER_DOCS (shippedSubset 16→17).
  Upper sentinel 60→65; Wave-4 length sentinel 16→17.

- install.sh: aif-orchestrator-discipline/SKILL.md added to SHIPPED_DOCS.

§1.7: forward — agents/orchestrator-worker-discipline.md:9 authority header per
doc-authority-hierarchy.md §3; install.sh:103 in sync with REQUIRED_HEADER_DOCS
per 09-doc-authority-hierarchy.test.ts:164 Wave 4 invariant. backward —
SKILL.md:10-11 carries @dual-pair + spec: per dual-implementation-discipline.md §7.

Prior-art: prior-art-evaluations.md#50 (skill-context/aif-review, ADOPT channel pattern).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(runtime-bridge): add operator convenience bind-mount section for global skills

Adds '## Operator convenience: mount global skills' section at the end of
docs/runtime-bridge-setup.md documenting how to bind-mount operator-side
global skills (~/.claude/skills/orchestrator/, Superpowers plugins) into
the aif container via docker-compose.override.yml.

Operator-axis only — consumers of the shipped framework are NOT required to
have these paths. The shipped in-repo discipline works without the mount.

Artifact 3 (A-docs) from aif-operator-asset-access I-phase.

Prior-art: skipped — doc addition, no new capability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(meta-orchestrator): add harvest egress step to autonomous dispatch loop (#391)

aif does NOT push or open PRs by design (documented in harvest.ts:11-14).
Nothing in the dispatch loop said "after status=done → call harvest" — this
was the gap that caused the aif-operator-asset-access I-phase to sit stuck
in the container with work done but no PR opened (incident 2026-06-03).

Fix: add egress gate to SKILL.md §5 autonomous dispatch row and to
meta-kickoff.template.md §4c so every generated kickoff carries the
harvest call as a mandatory post-done step.

Anti-pattern #autonomous-done-no-harvest added to SKILL.md §5.

Prior-art: skipped — doc-only change to .claude/skills/ and templates/,
no new capability (refactor only, harvest.ts already shipped).

* chore(orchestrator): mark aif-operator-asset-access DONE (#392)

* docs(m…
artyhoo added a commit that referenced this pull request Jul 13, 2026
…e shared ConventionNode core (#1005)

* feat(live-generation): S1 Inc1 — neutral live-research → ConventionNode bridge + python Tier-0 keys

The one shared BUILD of the live-generation umbrella (research-patch §0 Plane 3): a
thin, stack-neutral projection «a researched practice → a ConventionNode (frozen IR)»
that python (this stage) and rust (LG-S3) both reuse. It authors ONE neutral node from
ONE researched practice and hands it to the existing pure renderAstgrep backend — it is
NOT a pipeline, does not render to disk, and touches no network.

Bridge (packages/core/synthesizer/research-to-node.ts):
  - Defines its OWN input interface (AstgrepResearchedPractice — GenerateCandidate-shaped
    + the frozen-IR `kind:'call'|'attribute'|'import'` discriminator the backend requires;
    no existing type carries {kind,pattern}). Only the OUTPUT ConventionNode is frozen — no
    IR field added.
  - Two honesty lines: (1) MAJOR-1 degrade-not-inert (§Qb) — a practice that does not reduce
    to a single literal call/attribute/import ban is dropped to a research-only finding BEFORE
    node construction, never an inert node; (2) the bridge validates provenance ITSELF
    (validateProvenance, Tier-0 default; tiered when a ResolveCtx is supplied) — the grammar
    gate does not check the provenance host, so an untrusted source is a research-only finding,
    never a trusted node.

Python Tier-0 allowlist keys (packages/core/research/allowlist.ts — DATA, the
`#allowlist-as-code-not-data` discipline, parallel to how react-native/expo were added):
  - python.official → docs.python.org, peps.python.org
  - pyyaml → pyyaml.org (the getff-no-yaml-load flagship's security-guidance source)

Tests (research-to-node.test.ts, all committed fixtures — $0, never a live MCP call):
  AC1 getff-no-yaml-load → valid node (grammar gate passes + provenance resolves);
  AC2 node → renderAstgrep yields rule.pattern "yaml.load($$$ARGS)";
  AC3 mutable-default-arg / bare-except → research-only, no node/rule (non-vacuous: with the
      MAJOR-1 filter disabled the inexpressible practice emits an inert node → the assertion
      goes RED — RED-before-GREEN verified);
  AC4 expressible practice with a non-Tier-0 host → host-tier reject (FF2006), NOT an
      unknown-key/schema failure; no-provenance is fail-closed.

Suites green: test:principles 311, test:backends 171, test:composition 46, research 175,
to-node byte-locks 19, bridge 10; tsc --noEmit clean.

Prior-art: prior-art-evaluations.md#219 (stack-agnostic autogeneration core — BUILD thin, composed over shipped IR + backends + research/trust core; no upstream neutral-multi-backend generator, ast-grep/ruff/antfu confirmed negative 2026-07-11) and prior-art-evaluations.md#218 (LintConfig ADOPT-VOCABULARY coverage-classification + REFERENCE doc->config precedent).
Prior-art: prior-art-evaluations.md#183 (rule-research bridge, the shipped JS reference adapter this umbrella generalizes to the neutral ConventionNode plane) and #212/#215 (astgrep/ruff render targets, ADOPT — reused verbatim for the python adapter).

* fix(live-generation): S1 Inc1 review — drop untested tiered provenance seam + cover gate-failed + kind-drift guard

Review round 1 on commit 91d5332 (the neutral research→ConventionNode bridge):

FIX 1 (drop broken/untested tiered seam): remove ResolveCtx/ResearchToNodeOptions
param+type, the resolveCtx provenance branch, and the tiered
validateProvenanceTiered/resolveAllowedSources imports it used. The Tier-1 path was
structurally unreachable (no package field on AstgrepResearchedPractice) AND had zero
test coverage — YAGNI + a T20 unverified-code-path risk. The bridge keeps ONLY the
Tier-0 validateProvenance path the ACs cover; the Tier-1 python adapter is DEFERRED to
LG-S4/rust per research-patch §Qc, where it lands with a package field + Tier-1-reachable
tests. Doc-comments corrected: the tiered-seam mentions removed; with allowlist-resolver.ts
no longer directly imported the "same purity contract as the renderers" claim is now accurate.

FIX 2 (cover the third degrade path): add gate-failed coverage — a degenerate-pair
practice (expressible + valid Tier-0 provenance, but examples.bad === examples.good) trips
the grammar gate's FF6001 on the built node, asserting status research-only + reason
gate-failed. A non-vacuity companion test shows the same practice with distinct examples
becomes a node, isolating the degenerate pair as the cause.

FIX 3 (kind-drift guard, test-only): export VALID_KINDS from render-astgrep.ts (clean
export, no behavior change) and assert EXPRESSIBLE_KINDS set-equals it, so a future kind
added to the bridge but not the renderer (node builds then FF7002-refuses) fails at
assert time.

research-to-node.test.ts: 10 -> 13 tests. test:principles/backends/composition + tsc green.

Prior-art: skipped — INC-1 review fixes (drop unused/broken tiered seam + add gate-failed + drift coverage), no new capability

* feat(live-generation): S1 Inc2 — python Model A′ render driver + committed getff-researched-no-yaml-load artifact + firing proof

The python adapter's author + session-side render (Model A′) half. A committed
researched-practice record for the flagship getff-researched-no-yaml-load rule is
routed through the INC-1 bridge (researchedPracticeToNode) → renderAstgrep by a new
session-side driver, and BOTH the input record and the rendered ast-grep artifact are
committed (Model A′ pre-render, §Qa — the consumer install path stays Node-free).

- synthesizer/render-researched-astgrep.ts — the driver: pure planResearchedAstgrep /
  planFromCommittedRecords (compose bridge + pure renderer), impure writeResearchedAstgrep
  + a --check drift CLI. Renderers stay pure; all fs writes live in the driver (mirrors
  render-python-templates.ts plan-vs-write and cargo write-clippy.ts vs render-clippy.ts).
- synthesizer/fixtures/live-generation/ — the committed record (.practice.json, real Tier-0
  `pyyaml` provenance from INC-1), the rendered artifact (firing/rules/*.yml), and RED/CLEAN
  firing fixtures sharing one ../rules dir so the SAME committed bytes are drift-gated + fired.
- backends/astgrep/live-generation-research.test.ts — AC1 record→node, AC2 Model-A byte-drift
  gate (committed === renderAstgrep([node]), with a paired-negative teeth check), AC3 live-fire
  via the EXISTING firing-runner harness against pinned ast-grep@0.44.1 (RED on yaml.load / clean
  on yaml.safe_load), AC4 MAJOR-1 degrade-not-inert through the driver. Home = backends/ so CI's
  test:backends fires it with the installed pinned binary.
- rule id namespaced getff-researched-* (§Qd) so a delivered researched rule never collides with a
  starter getff-*. call-kind → ast-grep lane ONLY (no ruff render, python-delivery-v0 decision #5).

STOP-lines honored: frozen IR (no new ConventionNode field); render-astgrep.ts behavior untouched;
NO render-from-research in CI (CI only drift-checks + fires the committed artifact — $0, principle 17
/ no-paid-llm-in-ci). Delivery to a scratch consumer + $0 e2e RED is INC-3, out of scope here.

Prior-art: prior-art-evaluations.md#219 (stack-agnostic autogeneration core — this is the "session-side render + firing artifact" residue: committed researched practice → INC-1 bridge → renderAstgrep → committed ast-grep artifact; #219 landed in INC-1, cited not re-added) and #212 (ast-grep render target, ADOPT — reused verbatim).

* fix(live-generation): S1 Inc2 review — real drift-teeth test + dup-entryId loud guard + docstring

Replace the vacuous String.replace/not.toBe 'teeth' test (never called any drift code — attention-is-not-a-mechanism/#discipline-theatre) with two real ones: mutate the committed artifact on disk, run checkResearchedAstgrepDrift(), assert byte-mismatch / missing, restore verbatim bytes in finally (tree left byte-identical). Add a loud dup-entryId/path guard to planResearchedAstgrep (writeResearchedAstgrep would else silently clobber the first), mirroring planPythonTemplates' lane guard + a covering test. Fix planFromCommittedRecords JSDoc PURE -> PURE (read-only fs).

Prior-art: skipped — INC-2 review fixes (real drift-teeth test + dup-entryId loud guard + docstring), no new capability

* feat(live-generation): S1 Inc3 — deliver researched rule to a scratch consumer + flagship RED proof

The LG-S1 headline "live generation WORKS for python": the INC-2 committed researched
rule (getff-researched-no-yaml-load) is delivered to a FRESH mkdtemp consumer through the
python-delivery-v0 setup.d/45-python.sh augment-first seam — reused VERBATIM via its
PY_TEMPLATE_DIR env override (zero seam edit) — and then FIRES RED via the pinned
@ast-grep/cli@0.44.1: reports the ruleId + exit 1 on a planted yaml.load(raw), CLEAN
(exit 0, zero findings) on yaml.safe_load(raw).

- AC1 (always-on): the researched rule joins the consumer's single ruleDirs
  [.getff/astgrep-rules] additively, byte-identical to the INC-2 artifact; second run
  idempotent; STOP-line — the rule is NEVER baked into the shipped templates/python
  starter set (Model A′ per-consumer, §Qe genuinely-researched-not-starter).
- AC2 (skipIf(!toolPresent)): fireContract (firing-runner.ts, NOT the npx arm) asserts
  the delivered rule fires RED; a non-vacuity test delivers WITHOUT it and fires the same
  fixture → not reported (the RED is delivery-gated, not ambient).
- AC3 $0: committed fixtures only, no paid MCP/LLM; lands in test:backends where
  audit-self.yml installs the free pinned ast-grep binary.
- AC4: 11 install baselines byte-identical (PY_TEMPLATE_DIR delivery touches nothing
  shipped) — no regen.

Home backends/astgrep/ mirrors the INC-2 sibling live-generation-research.test.ts:
test:backends is the only CI suite with the pinned ast-grep on PATH.

Prior-art: skipped — test-only: exercises the existing python-delivery-v0 seam (setup.d/45-python.sh, unedited) + the existing astgrep firing-runner harness + the committed INC-2 artifact; no new capability/dependency/module (flagged only by the >=80-LOC new-file heuristic).

* docs(live-generation): S1 census — expressibility research-patch (OWNER-FORK-1 data)

Records the LG-S1 sub-deliverable 7 census: 15 real python conventions classified by frozen-IR expressibility with real ast-grep@0.44.1 firing (flat 40% / flat+relational 93.3%; require-via-ban 3/3 positives). DATA-only OWNER-FORK-1 record; the unfreeze decision is a separate MT-plane umbrella.

Prior-art: skipped — docs-only OWNER-FORK-1 census data (research-patch), no new capability/dependency/module

* chore(live-generation): S1 — regen synth-and-wire bundle for the python Tier-0 allowlist keys

Byte-consequence of INC-1's allowlist.ts python Tier-0 keys (research/load.ts imports allowlist.ts → bundled transitively via synth-and-wire). The increments touched allowlist.ts but did not rebuild the bundle; pre-push synth-bundle drift gate caught it. Diff is exactly python.official + pyyaml, --check now in-sync.

Prior-art: skipped — build-artifact regen (byte-derived from the reviewed allowlist.ts change), no new capability/dependency/module

---------

Co-authored-by: t <t@t.co>
artyhoo added a commit that referenced this pull request Jul 22, 2026
…ender + seam join — ecosystem-wiring W5 (#1082)

* feat(rule-bootstrap): --from-practice arm — researched-python (Model A′) consumer render lane — ecosystem-wiring W5

The JS live path (--from-research/--from-selection) is eslint-only: engine:'ast-grep'
is parked at L4 as error-severity FF3003/FF3010/FF3012 (diagnostics/registry.ts:182,
'reserved but not wired — deferred per generator-forbid-mvp decision (i)') and
install() writes .ai-factory/ which the python lane forbids (45-python.sh:438).
The SHIPPED researched-python contract is the Model A′ lane (AstgrepResearchedPractice
→ researchedPracticeToNode → renderAstgrep, LG-S1 INC-1/2). This arm is the minimal
glue making that lane invokable for a consumer: practice JSON (file or dir of
*.practice.json) → rendered rule YAML at <consumer>/.getff/rules-research/<entryId>.yml
— a refresh-surviving home (refresh_safe rm-rf-replaces .getff/astgrep-rules from the
template, lib.sh:126, so rendering there would be wiped). MAJOR-1 honesty inherited
from planResearchedAstgrep: research-only findings logged loudly, never written; a
fully-degraded run creates nothing. Render is session-side (Model A′ §Qa) — the
consumer install path stays Node-free.

Absorbs the prior-dispatch leftover test rule-bootstrap-practice.test.ts (7 tests:
render parity vs the committed LG-S1 artifact, dir-mode, MAJOR-1 degrade, provenance
trust gate, empty-dir throw, real-CLI spawn, combined-flags refusal).

Prior-art: prior-art-evaluations.md#183 (rule-research→rule-factory bridge BUILD — this CLI is that entry) + #219 (stack-agnostic autogeneration core — the Model A′ lane this arm exposes); no new capability class, composition-only glue over the shipped INC-1/2 bridge+renderer.

* feat(python-delivery): join consumer researched rules into the astgrep scan dir — ecosystem-wiring W5

_py_join_researched_rules: on every delivery pass (install / --force / --refresh),
copy <consumer>/.getff/rules-research/*.yml (rendered by rule-bootstrap-cli
--from-practice) into .getff/astgrep-rules/ so a researched rule fires via the
consumer's single existing ruleDirs: entry. rules-research is the refresh-durable
home — refresh_safe rm-rf-replaces astgrep-rules from the template (lib.sh:126),
and the join re-assembles it right after. Basename collision with a template-owned
rule → REFUSE-LOUDLY (getff-researched-* §Qd namespace enforced, never clobber a
starter); dry-run-safe; byte-stable on re-run (idempotency checksum unperturbed);
only *.yml joins (practice.json inputs stay put). NO new delivery channel: rides
the .getff/ namespace + astgrep lane the seam already owns (same T-EW-B rationale
as _py_write_rules_lock).

Gates run: shellcheck clean; refresh-covers-full-delivery 6/0 (Check 4 population
untouched — join carries no $tpl copy/refresh token); python-delivery 58/0;
python-rules-lock 19/0; python-entry-lane 26/0; layer-units 29/0.

Prior-art: skipped — delivery-seam glue for the shipped Model A′ lane (composition of existing seam + CLI arm from the previous commit), no new capability class.

* test(astgrep): researched-python LIVE path e2e — research JSON → CLI render → seam join → fires on a scratch consumer — ecosystem-wiring W5

The W5 deliverable proof, WITHOUT the PY_TEMPLATE_DIR pre-seed shortcut
(live-generation-delivery.test.ts / W3): consumer-authored practice JSON at
.getff/rules-research/ → the REAL rule-bootstrap-cli --from-practice entrypoint
(spawned, exit asserted) → rendered YAML byte-identical to the committed Model A′
artifact → deliver_python_toolchain with its DEFAULT template dir joins it into
.getff/astgrep-rules/ → pinned ast-grep FIRES RED (rule id reported, bare exit 1)
on planted yaml.load(raw), CLEAN (exit 0, zero findings) on yaml.safe_load(raw).
Plus refresh-survival (rm-rf-replace + re-join, lib.sh:126) and the CLI-hop
non-vacuity control (no practice record → same violation does NOT report the id).
Home = backends/astgrep (CI installs pinned ast-grep only for test:backends).

Prior-art: skipped — verification-only e2e test for the W5 glue landed in the two previous commits, no new capability.

* fix(python-delivery): content-aware rules-lock guard — plain-pass researched join no longer leaves a stale lock — ecosystem-wiring W5 rework

MAJOR (review-confirmed by reproduction): _py_join_researched_rules runs on
EVERY delivery pass, so a plain no-flag re-run after a consumer authored a
researched rule DELIVERS a new rule into .getff/astgrep-rules — but the
W3-era flag-gated skip in _py_write_rules_lock (lock exists && !REFRESH &&
!FORCE) still skipped, leaving ruleIds/sourceFingerprint STALE and violating
the function's own invariant («the lock is NEVER stale relative to the
delivered .getff/ artefacts»). The guard's assumption «no overwrite flag ⇒
delivered set unchanged» has been false since the join exists.

Fix: replace the flag-gated existence skip with a CONTENT-AWARE compare —
compute the delivered-set sourceFingerprint first, skip ONLY when it equals
the fingerprint stored in the existing lock. Closes plain-pass joins, any
future join-like path, AND stays byte-stable on true no-change re-runs
(fingerprint equal → skip → emittedAt untouched; arm (6) still gates). The
no-hash-tool degrade constant (0000000000000000) cannot prove «unchanged»,
so it never skips — conservative regenerate, already loudly declared
non-authoritative.

Regression arm (11) in tests/install-sh/python-rules-lock.test.sh:
plain install → author researched rule into rules-research → plain re-run.
RED before fix: «ids 4→4 fp e35d7c711e8d1e1e→e35d7c711e8d1e1e
researched-in-lock=0». GREEN after: «researched id captured (4→5 ids) +
fingerprint moved e35d7c711e8d1e1e→34eb23291de12ecd». Full suite 21/21;
shellcheck clean.

Prior-art: skipped — bug-fix rework of a review-confirmed W5 MAJOR, no new capability

* fix(rule-bootstrap): sanitize consumer entryId before fs use + commit collision/idempotency arms + accurate degrade message — ecosystem-wiring W5 rework

Finding 2 (MAJOR, security): runPracticeRender built the output path from a
practice record's entryId verbatim (writeFileSync(join(outDir, `${entryId}.yml`))).
entryId is consumer-authored JSON → an arbitrary-file-write / clobber primitive:
a traversal id (`../../pwned`) wrote OUTSIDE .getff/rules-research (reproduced —
no throw, file landed under the consumer root), and a crafted id could clobber
existing files. Fix: validate EVERY entryId BEFORE any fs use against the shipped
rule-id slug convention (^[a-z][a-z0-9-]*$ — matches starters getff-no-eval… and
the researched getff-researched-* sub-namespace), refuse with a loud
PracticeEntryIdError otherwise; plus a belt-and-braces resolved-path containment
check (resolvedWithinRoot posture, ecosystem-cargo/-python precedent). One bad id
refuses the whole run with nothing written / no outDir created. The CLI exits
HARD non-zero on an unsafe id even without --strict — a traversal is an attack,
not the rc=0 "degrade with guidance" contract. Attack arms (RED→GREEN) in
rule-bootstrap-practice.test.ts: traversal refused (no <consumer>/pwned.yml, no
outDir), separator refused with an entryId-named error (was an incidental ENOENT),
valid slug still renders, real-CLI unsafe id → exit 1.

Finding 3 (test-reality gap): the kickoff-named collision refuse-loudly and
byte-stable idempotency behaviours were reviewer-verified only ad-hoc. Committed
both arms to researched-live-path.test.ts (always-on, real seam): a researched
basename colliding with a template starter is REFUSE-skipped (template bytes win,
loud "REFUSE researched join" log — impostor bytes deliberately differ so a silent
clobber would be visible); two delivery passes leave the scan dir byte-identical
per file incl. the joined researched rule. RED-ability confirmed by inverting the
collision log assertion once (1 failed), then restored.

Finding 4 (MINOR): the --from-practice degrade message pointed at
agents/rule-researcher.md / the rule-research skill as if they document authoring
*.practice.json records — verified those docs are SILENT on practice records
(grep: zero hits). Reworded to point at what actually exists: the
AstgrepResearchedPractice schema (research-to-node.ts) + the committed example
fixture. No skill/agent doc edited (doc-scope follow-up belongs to another umbrella).

Gates: vitest 18/18 (practice 11 incl. 4 attack arms + live-path 7 incl. 2 new);
rule-bootstrap-cli.test.ts 6/6; python-rules-lock 21/21; snapshot compare 11/0;
shellcheck clean; tsc clean on touched files.

Prior-art: skipped — bug-fix rework of review-confirmed W5 MAJOR/MINOR findings, no new capability

* fix(astgrep-test): resolve tsx bin directly so the W5 live path fires under test:backends — ecosystem-wiring W5

researched-live-path.test.ts runs in the `test:backends` suite. That CI job
(.github/workflows/audit-self.yml "Principles as meta-tests") installs packages/core
deps only (`npm ci --prefix packages/core`) — tsx (a packages/core devDependency) lands
in packages/core/node_modules/.bin/tsx. The repo-root hoist (`npm install --silent`)
that would put tsx at $REPO_ROOT/node_modules/.bin runs LATER in the same job, after
test:backends. So `npx --no-install tsx` invoked with cwd=REPO_ROOT walks up from the
root bin only, never finds tsx, and exits non-zero — failing all 5 arms that render via
the CLI (`expect(cli.status).toBe(0)` at 173/200/251/288/304). Green locally only because
a dev root always has tsx hoisted; the divergence is environment, not a flake.

Root-cause fix: spawn the resolved tsx binary directly instead of `npx --no-install tsx`.
tsxBin() prefers packages/core/node_modules/.bin/tsx (guaranteed present under
test:backends), falls back to a root hoist, then PATH. Never skips — CI always ships one
of these, so the always-on render+join block and the pinned-ast-grep firing arms stay
gating. The one always-on arm that already passed (collision refuse-loudly) never called
the CLI, which is why it was the sole survivor.

Prior-art: skipped — bug-fix in an existing W5 test, no new capability (swaps npx-resolved tsx for a direct bin resolution).

* fix(install-test): resolve tsx bin directly in the W5 practice-render sibling — ecosystem-wiring W5

Backward-sweep of the same defect class fixed in researched-live-path.test.ts: the three
`npx --no-install tsx CLI` spawns here (cwd=REPO_ROOT) resolve tsx by walking up from the
repo-root bin only. Under a per-dir CI install layout (`npm ci --prefix packages/core`,
no root hoist) that misses packages/core/node_modules/.bin/tsx and exits non-zero. This
file's install/ dir is not currently gated by the backends job, so it was not CI-red, but
it carries the identical latent trap and would fail the moment install/ is gated under a
core-only layout.

Same root-cause fix: spawn the resolved tsx binary directly via a sibling tsxBin() that
prefers the core-local bin, falls back to a root hoist, then PATH. All 11 arms green.

Prior-art: skipped — bug-fix in an existing W5 test, no new capability (mirrors the tsx-bin resolution shipped for the backends sibling).

* test(astgrep): surface CLI/delivery subprocess stderr on non-zero exit (W5 CI diagnostic)

The tsxBin resolver fix (e147151) did NOT clear CI — the subprocess exits 1 for a
reason the test swallowed. Print tsxBin resolution + subprocess stderr/stdout on any
non-zero exit so the next CI run is diagnostic instead of a guess.

Prior-art: skipped — diagnostic instrumentation on an existing test, no new capability.

* fix(rule-bootstrap-cli): lazy-load runRuleBootstrap so --from-practice runs preset-free (W5 CI)

Root cause of the test:backends RED (PR #1082): rule-bootstrap-cli.ts statically
imported runRuleBootstrap -> synthesizer/rule-bootstrap.ts -> installer/install.ts +
synthesizer/generate-cli.ts -> validator/validate.ts -> gate-conflict.ts /
gate-tautology.ts / gate-autofix-clean.ts, each of which top-level-imports
'@rules-as-tests/preset-next-15-canonical/eslint-rules'. In the test:backends CI job
(npm ci --prefix packages/core only -- a core-only layout with NO sibling workspace
packages) the preset is unresolvable, so merely LOADING the CLI crashed at module-init
(ERR_MODULE_NOT_FOUND) before parseArgs -- killing the lightweight, preset-free
--from-practice render path that never needs the validator.

Fix (option 1 -- smallest, cleanest seam): convert the sole static import that
transitively reaches validate.ts into a dynamic await import('../synthesizer/
rule-bootstrap.ts') placed in main() AFTER the --from-practice early-return, so only
the synthesis/live arm (which genuinely needs L4/L5 validation + the preset) loads it.
The render path (planResearchedAstgrep / research-to-node / renderAstgrep) is already
preset-free, so --from-practice now runs in a core-only environment. One dynamic seam
covers all three preset-importing gates at once.

Verified by local core-only reproduction (worktree-root preset symlink moved aside):
CLI --from-practice exited 1 (ERR_MODULE_NOT_FOUND) BEFORE, 0 AFTER; preset restored,
tree clean. Gates: researched-live-path.test.ts + rule-bootstrap-practice.test.ts
18/18 green; python-rules-lock.test.sh 21/0; tsc -p packages/core clean.

Prior-art: skipped -- bugfix only, no new capability (edit converts an existing static
import to a lazy dynamic import in one existing file; no dependency/module/infra added).

---------

Co-authored-by: t <t@t.co>
artyhoo added a commit that referenced this pull request Jul 22, 2026
…-tests-surface S1) (#1089)

* feat(rule-tests): S1 — write-half protocol + thin skill trigger (rule-tests-surface S1)

Ships the third consumer deliverable's S1 slice: the rule-tests skill surface for
writing/repairing the firing TEST MATERIAL of an EXISTING generated rule.

- NEW agents/rule-test-author.md — the write-half protocol (the ONE place LLM is
  allowed): read delivered rule → write/repair test material (npm manifest
  negative-test; astgrep/ruff enrichment sidecar, S2 pointer only) → verify in
  single-rule isolation → quote the tool verdict verbatim; never edits the rule
  artifact (drift/hash-gated, T-RTS-B). Carries honesty map v0 (8 npm L4 gates; 5
  astgrep defer-refusals FF3003/3010/3012/3015/3018; ruff TID251+TID253 family;
  cargo clippy::disallowed_methods — W4 CI live-fire live state), the D3
  staleness-consent script (reader of tool-decisions.md + rules-lock.<fw>.json,
  honest limits), and per-lane honest-limits.
- NEW .claude/skills/rule-tests/SKILL.md — ~35-line thin trigger, mirror pair to
  rule-research; per-root reading obligations; no stack/toolchain fact in the body
  (T-RTS-A); paired-negative block (principle 15).
- Registration sweep: agents/rule-test-author.md → principle-09 REQUIRED_HEADER_DOCS +
  install.sh SHIPPED_DOCS (Wave-4 length 27→28); rule-tests → setup.d/10-skills.sh CORE
  ship list + install.sh --refresh path; CLAUDE.md Artifact Ownership Contract row.
- Install fingerprints recaptured (4 npm stacks × greenfield/brownfield; python/cargo
  unaffected). Prettier-clean shipped agent.

Self-application (kickoff §2): the write-half verify step exercised by the framework on
its own live-generation fixture — packages/core/backends/astgrep/live-generation-delivery.test.ts
fires getff-researched-no-yaml-load in single-rule isolation (mkdtemp + in-memory
contract, expectedCode=ruleId): AC2 RED (yaml.load → ruleId present, bare scan exit 1)
+ CLEAN (yaml.safe_load → zero findings, exit 0). Sidecar test-material repair-act demo
completes in S2 when the sidecar home lands (T14: coverage stated, no faked repair).

Prior-art: prior-art-evaluations.md#183 + #219 (rule-bootstrapping bridge + live-generation core) — ADAPT of the shipped own-stack rule-research surface: rule-tests is its declared mirror-pair sibling (thin SKILL.md trigger + one named agents/rule-test-author.md write-half protocol), authoring/repairing the per-backend firing test material those researched rules need. Zero new dependency, no IR change.
Prior-art: prior-art-evaluations.md#55 (Superpowers TDD-for-Skills paired-negative) — REFERENCE: the skill ships with executable bad/good test material discipline, not prose claims; substrate-pure.
Prior-art: prior-art-evaluations.md#75 (CC skill description-match activation) + #226 (named agents/*.md protocol + ownership row) — ADOPT-VOCABULARY / REFERENCE for the thin-trigger routing surface and the consumer-facing protocol-doc shape.

* docs(rule-tests): reconcile examples-vs-negative-test editability (S1 review Minor)

* test(install-sh): regen fingerprints after S1 review-fix doc edit (shipped-file cascade)

---------

Co-authored-by: t <t@t.co>
artyhoo pushed a commit that referenced this pull request Jul 22, 2026
…locks principle-11 F1)

The adapter-jig J2 squash (#1094, df8011c) carried its Prior-art evidence in the
PR body only — no git trailers in the squash commit body and no SSOT path match for
its three new capability files, so principle 11 F1 turned RED for every subsequent
PR (first observed on this PR's CI). Bookkeeping pointer appended to the EXISTING
entry #226 (capability already registered by the adapter-jig sessions; BUILD verdict
unchanged) — operator-invited cross-owner SSOT edit, 2026-07-22.

§1.7: forward-check — append-only SSOT edit per prior-art-evaluations.md §3
(docs/meta-factory/prior-art-evaluations.md:299 row 226 extended in place); F1 green
locally (packages/core/principles/11-build-first-reuse-default.test.ts:340).
backward-check — change class = «post-merge SSOT pointer for a squash that dropped
trailers»; sibling surface: #1093 (our own S5 squash) verified — its capability file
run-rule-tests-firing.sh matches the SSOT via the #219/#91/#227 trailer text carried
into the squash body, no pointer needed, SWEPT-CLEAN. GAP-FOUND: none.
artyhoo added a commit that referenced this pull request Jul 22, 2026
…uff/cargo coverage + mutation-root test (rule-tests-surface) (#1097)

* fix(rule-tests): whole-work corrective — sidecar shape validation + ruff/cargo coverage + mutation-root test + tense fix (rule-tests-surface)

Post-merge corrective PR for S5 (#1093). Two-altitude whole-work review found a
BLOCKER (shipped channel did no SHAPE validation) + a MAJOR (ruff/cargo lanes had
zero test coverage) + FIX-3..6. No new capability — extends existing S5 artifacts +
tests + docs; STOP lines unchanged (no spec edit, no .husky edits, single-rule
isolation preserved).

- FIX-1 (BLOCKER): shape validation, not just JSON parse. The runner
  (packages/core/synthesizer/run-rule-tests-firing.sh:74 `_validate_sidecar` +
  :100 `_fail_shape`) and the pre-push arm (packages/core/hooks/pre-push.ts:845
  `SIDECAR_SHAPE_PROBE` + :963) now mirror the S2 loader
  `validateRuleTestsSidecar` (rule-tests-sidecar.ts:96-123) up front, per lane:
  a `badd` typo, an empty `bad[]`/`good[]`, an unknown key, or a mis-shaped entry
  is BROKEN MATERIAL → per-file loud FAIL + exit 1 — closing the silent-green hole
  where `(s && s.bad) || []` coerced a typo'd/empty field to zero samples. Both
  RED unguarded (no lane tool needed).
- FIX-2 (MAJOR): ruff + cargo test coverage. New paired-negatives in
  pre-push.consumer-layout.test.ts exercise `_fire_ruff` end-to-end (TID251-keyed
  sidecar against a delivered-shape ruff-bans.toml — bad[] fires + good[] clean →
  exit 0; broken bad[] → exit 1) and the cargo toggle logic (unset → opt-in skip;
  GETFF_PREPUSH_CARGO_FIRE=1 + cargo absent → DEGRADED skip) — no compile.
- FIX-3: committed consumer-depth regression for D-S5-mutation-root
  (run-generated-rule-mutation.sh:36 git-toplevel root resolution) — asserts the
  manifest path resolves INSIDE the sandbox, catching a regression to `../../..`.
- FIX-4: unified the cargo opt-in skip wording between the runner and the arm
  (pre-push.ts:975); the cargo test asserts the shared substring so drift breaks it.
- FIX-5: closed the S2-deferred stale tense in agents/rule-test-author.md — astgrep
  = landed + committed fixture; ruff/cargo = format landed, committed fixtures
  pending; added the committed-fixture honesty note into the honesty map
  (rule-test-author.md:58, durable in-repo home).
- FIX-6: appended a Residuals / follow-ups gap-log to
  .claude/orchestrator-prompts/rule-tests-surface/done.md (each item + PR-of-record;
  marks what this PR closes).
- Install-sh baselines regenerated (shipped runner + pre-push.ts + agent-doc cascade).

§1.7: forward-check applied — the shape probe RED's a mis-shaped sidecar at the
earliest reachable channel (run-rule-tests-firing.sh:74 `_validate_sidecar` +
pre-push.ts:963 `shapeProbe`), an attention-is-not-a-mechanism §1 upgrade of a
silent-green hole to a deterministic gate; complies with no-paid-llm-in-ci
(deterministic node/bash, zero API calls) and ci-tool-pinning.md §1 (no new tool
install; the mutation-root fix uses git plumbing). The runner re-implements the S2
loader inline rather than importing it because packages/core/synthesizer/ is not
shipped to consumers — verified: install.sh ships only packages/core/{hooks,
eslint-rules}. doc-authority-hierarchy.md §2-§3: agents/rule-test-author.md keeps its
header; edits are body-tense only (rule-test-author.md:58,66). backward-check sweep —
change class = "the shipped sidecar-firing channel + its shape contract + the S2
loader it mirrors"; sibling surfaces swept: the S2 loader `validateRuleTestsSidecar`
(rule-tests-sidecar.ts:96-123) is the SSOT the runner + arm now mirror (kept in sync,
comment-anchored on both), SWEPT-CLEAN; the two firing channels (runner
run-rule-tests-firing.sh + pre-push arm pre-push.ts) both carry the up-front shape
guard now, SWEPT-CLEAN; the cargo opt-in wording exists in exactly two places (runner
+ arm) and is now unified + test-pinned, SWEPT-CLEAN; the mutation-runner root
resolution is the delivery twin of the firing runner and carries the same
git-toplevel fix (run-generated-rule-mutation.sh:36), SWEPT-CLEAN; the stale-tense
sweep enumerated all three future-tense sidecar sites in rule-test-author.md (:20,
:66, :96) — all corrected. GAP-FOUND: none.

Prior-art: skipped — corrective PR extending the existing S5 firing runner + pre-push arm + tests + protocol doc; no new capability, no new dependency, no new file under packages/.

* docs(ssot): register #1094 J2 implementation files in entry #226 (unblocks principle-11 F1)

The adapter-jig J2 squash (#1094, df8011c) carried its Prior-art evidence in the
PR body only — no git trailers in the squash commit body and no SSOT path match for
its three new capability files, so principle 11 F1 turned RED for every subsequent
PR (first observed on this PR's CI). Bookkeeping pointer appended to the EXISTING
entry #226 (capability already registered by the adapter-jig sessions; BUILD verdict
unchanged) — operator-invited cross-owner SSOT edit, 2026-07-22.

§1.7: forward-check — append-only SSOT edit per prior-art-evaluations.md §3
(docs/meta-factory/prior-art-evaluations.md:299 row 226 extended in place); F1 green
locally (packages/core/principles/11-build-first-reuse-default.test.ts:340).
backward-check — change class = «post-merge SSOT pointer for a squash that dropped
trailers»; sibling surface: #1093 (our own S5 squash) verified — its capability file
run-rule-tests-firing.sh matches the SSOT via the #219/#91/#227 trailer text carried
into the squash body, no pointer needed, SWEPT-CLEAN. GAP-FOUND: none.

---------

Co-authored-by: t <t@t.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant