Phase 4: Stack Detector v1 (read+write AIF bridge, CI gate, reviewer cycle complete) - #4
Conversation
Applied EXECUTION-PLAN §5.5 Step 0 gate to Phase 4 (Stack Detector v1) per memory rule (context7-only research + phase-entry checklist): - phase-4-research.md (138/200 lines): 6-capability matrix × 3 reuse + 3 build, reviewer GO after closing 2 MAJOR + 3 MINOR - 4.1 hybrid REUSE (read AIF artifacts as primary source) - 4.3 REUSE convergent (adopt AIF severity/weight schema) - 4.6 REUSE skill-context format (write side, touchpoint 4 close) - 4.2/4.4/4.5 build (semver, CLI bin, vitest snapshots) - PHASE-4-PROMPT.md (v0.1.2): 7-task implementation prompt + Task 1.5 (--dry-run flag prereq for L1 idempotence verify), reviewer GO after closing 4 MAJOR + 4 MINOR + 2 NEW MINOR Step 0 paper trail before implementation begins. Phase 4 capabilities split into priority 1-3 (read AIF artifacts) + priority 4-5 (manifest heuristic fallback) — detector positioned as deterministic bridge over AIF, not parallel implementation.
…4-5) Detector v1 in packages/core/detector/ as deterministic bridge over AIF artifacts (read-aif.ts wired in but priority 1-3 fixtures + tests land in Task 2). Priorities 4-5 implemented end-to-end: package.json deps + lockfile signature (priority 4, severity=warn, weight=1), and next.config.* / tsconfig.json presence (priority 5, severity=info, weight=0). Single emit, dual contract per phase-4-research §3.3 + §4.3: AIF severity/weight schema + human confidence label. Version-aware logic via npm semver (transitive via @typescript-eslint/rule-tester — no explicit dep added per phase-4-research §3.2, §4.2 hard constraint). semver.coerce handles canary tags, RCs, ranges; self-rolled regex rejected. Tests: 9 paired cases covering both stack outputs, version-major extraction, fallback chain (priority 4 → 5 → unknown), and self-application sanity check on this repo (ts-server, source: package.json, confidence: medium). detector-v0/ preserved untouched per research §5 #5.
…mpotence verify)
Adds --dry-run flag (DRY_RUN env, dryguard wrapper) so setup.sh can
be run twice on the same repo without side effects, closing the L1
acceptance gap from self-application.md:33 ("setup.sh --stack=$(detect)
idempotent на собственном репо"). All side-effect paths now report
"[dry-run] would: <op>" instead of executing: ai-factory init,
install.sh, depcruise --init, storybook init, husky init, hook copies,
npm install (deps + zod), and jq mutation of package.json.
Verified: bash -n syntax OK; bash setup.sh --stack=ts-server
--dry-run --skip-aif-init --skip-deps emits ≥1 dry-run line; mtime
of .husky/pre-commit unchanged before/after. Hard-constraint check
from PHASE-4-PROMPT (complete, no leaky abstraction): every cp,
mkdir, chmod, npm install, husky init, jq mutation is under guard.
Adds dedicated read-aif tests + 7 fixture trees for AIF priority 1-3
(DESCRIPTION.md, ARCHITECTURE.md, skill-context/<skill>/SKILL.md).
read-aif.ts ships with Task 1; this commit closes its acceptance with
9 paired tests covering:
- priority 1-3 fixture detection (high confidence, severity=pass)
- priority order DESCRIPTION → ARCHITECTURE
- mandatory schema validation (AifSchemaError when canonical heading
missing — bidirectional break risk per phase-4-research §5+§6, with
file path in error message for actionable diagnostics)
- graceful degradation (null when .ai-factory absent or empty;
caller falls back to manifest priority 4)
Verify probes:
npx tsx detector/index.ts ./detector/fixtures/with-aif
→ confidence: high, source: ".ai-factory/DESCRIPTION.md"
npx tsx detector/index.ts ./detector/fixtures/no-aif
→ confidence: medium, source: "package.json"
Adds rules-as-tests-detect npm bin (cli.ts, ~30 LOC argv parsing per phase-4-research §3.4 + Hard constraint — no yargs/commander). detect script now points at the new CLI; the legacy detector-v0 entrypoint moved to detect:install-rules so existing manifest-applicable-rules tests keep their entrypoint. Exposes `./detector` from packages/core/package.json `exports`, lets @rules-as-tests/meta-factory re-export detectStack via src/detector/index.ts (was a throw-not-implemented stub). write-skill-context.ts ships as a stub returning "not yet implemented (Task 6)" — keeps cli.ts compiling with the lazy import for --emit-skill-context flag wiring; full implementation lands in Task 6. semver.d.ts: minimal local declaration shim for the `semver` package (only coerce/valid/satisfies surface). semver itself is transitive (no explicit dep added — Hard constraint upheld), but @types/semver is not in the dep tree. Shim also referenced from meta-factory tsconfig include so re-export typechecks cleanly. Verify: npm --prefix packages/core run detect → JSON, exit 0 /tmp/fake-next-16 (next@16, react@19) → stack=react-next, major=16 grep -c '"semver"' packages/core/package.json → 0 (no explicit dep) npm ls semver --workspaces → semver@7.7.4 deduped (still transitive)
Adds detector/snapshot.test.ts with vitest toMatchSnapshot per fixture. 7 frozen snapshots: with-aif, no-aif, next-15, next-16, react-only, ts-server, aif-skill-context. Snapshots cover the full DetectionResult shape (stack/framework/runtime/confidence/severity/ weight/source/rules) — surfaces drift on any field change. Verified stable on two consecutive runs (probe 6 in PHASE-4-PROMPT verification block) — no spontaneous snapshot updates needed. Stop-rule trigger watchpoint per phase-4-research §5.5 + EXECUTION-PLAN §5.5: if these snapshots churn >3 update/week, signals detector is over-fit (read-config too detailed) or fixtures too coupled to implementation details. Track in retro phase-4.md fragility column.
… a+b)
Adds new audit-self job framework-self-detect that:
(a) verifies setup.sh --stack=$(detect) --dry-run is idempotent on
this repo (two consecutive runs produce identical output) — L1
acceptance criterion (a) from self-application.md:33
(b) diffs the detector output on the root repo against
packages/core/detector/expected-self-detect.json (committed,
frozen) — L1 acceptance criterion (b)
(a) checked first per the source-of-truth ordering in
self-application.md:33; (b) closes the snapshot-stability angle.
Frozen snapshot reflects this repo's current state: ts-server,
source=package.json, confidence=medium (no react/next in deps,
no tsconfig.json marker triggered before package.json fallback).
Verified locally:
actionlint .github/workflows/audit-self.yml → exit 0
zizmor .github/workflows/ → no findings (6 suppressed)
diff <(detect | jq -S .) <(jq -S . expected-self-detect.json) → ✓
setup.sh --dry-run x2 → no diff
…oint 4 close)
Implements write-skill-context.ts (was a Task 3 stub) to emit
.ai-factory/skill-context/{aif-fix,aif-implement,aif-architecture}/
SKILL.md from a DetectionResult. Closes touchpoint 4 from
aif-comparison.md §5 inside Phase 4 instead of deferring to Phase 11
(per phase-4-research §5 scope-delta #2 + §4.6 reuse decision).
Each emitted SKILL.md embeds the full detection state (stack,
framework name + major + version, runtime, source priority, confidence
with severity/weight) plus a skill-specific guidance line so the AIF
runtime can branch on stack without re-deriving from package.json.
Tests cover: file emission for all 3 top skills; mandatory schema
header check (canonical "# <skill> — project-level overrides" first
line, per Task 6 verify probe in PHASE-4-PROMPT — schema validation
mandatory per phase-4-research §6 watch-list); embedded fields;
graceful handling of ts-server (framework.name=null) without
"null"/"undefined" string leaks in markdown; idempotence on
repeated emit.
Verified end-to-end:
cd /tmp/fake-next-16 (next@16, react@19)
rules-as-tests-detect $(pwd) --emit-skill-context=./.ai-factory/skill-context
→ exit 0, 3 SKILL.md files emitted with canonical headings.
Phase 4 closes with GO verdict for Phase 5 entry. All 7 tasks done; all 10 verification probes from PHASE-4-PROMPT green; both L1 acceptance criteria (a+b) closed at the level required by self-application.md:33; both CI gates (snapshot diff + dry-run idempotence) live in framework-self-detect job. Reuse posture validated: 3 reuse decisions closed (4.1 read AIF, 4.3 severity/weight schema, 4.6 emit skill-context). Detector v1 positioned as deterministic bridge over AIF artifacts (read+write), not parallel implementation — confirmed by single-emit dual-contract output (severity/weight + confidence label). Self-application score 9/10 (‑1 because the "≥3 недели stability" window can only be point-in-time at retro). Time-vs-plan well under 1.5x. Open questions for Phase 5 orchestrator surfaced: multi-stack monorepo, Tailwind version-aware, pre-existing setup.sh templates path mismatch, AIF v3+ schema check, packages/core typecheck pre-existing errors.
…(a)) Independent review (Opus 4.7) flagged L1 (a) idempotence test as vacuous on this repo: husky cp/chmod blocks at setup.sh:339,345 gate on a templates path that doesn't exist (templates moved in Phase 3.1), so dry-run idempotence proves only non-husky steps. Lowering self-app score 9→7, promoting Open question #3 from out-of-scope cleanup to Phase 5 entry blocker. M2 (arch.md §2.3 drift) and m1 (aif-comparison.md §5 sync) deferred to Phase 5 entry hygiene. Probes 1–10 still green; verdict remains GO.
setup.sh:339,341,345,347 referenced $PKG_DIR/templates/shared/ but the shared templates moved to $PKG_DIR/packages/core/templates/shared/ in Phase 3.1. With the broken path the husky cp/chmod blocks were dead code on this repo, which made the L1 (a) idempotence test (framework-self-detect CI gate) vacuous — two dry-run invocations diff-clean only because the husky branches never fired, not because they're idempotent. Verification (this repo, ts-server stack): - dry-run x2 still diff-clean - husky branches now appear in dry-run output (4 [dry-run] lines: cp+chmod for pre-commit, cp+chmod for pre-push) - L1 (a) non-vacuous; self-app reversal-condition from docs/meta-factory/retros/phase-4.md:99 met Closes reviewer M1 from post-Phase-4 review session. Note: setup.sh:230,231,238,242 still reference templates/react-next/ which also doesn't exist at root post-Phase-3.1 — separate finding, out of scope for this commit; logged as Phase 5 entry follow-up.
… 7→8 Reflects state after 1555fb1 (setup.sh templates path fix). L1 (a) idempotence criterion no longer vacuous: dry-run output includes 4 husky [dry-run] would: cp/chmod lines, proving the install path is exercised. Score evolution now visible in retro: 9/10 (initial Phase 4 close) 7/10 (post-review calibration, reviewer M1) 8/10 (post-fix, reversal-condition met) Open question #3 closed; new Open question #6 logged for wider Phase 3.1 fallout (react-next templates path rot in setup.sh:230-244, stack-gated so doesn't affect this repo's L1 self-app, but will break react-next consumers). Probes 1-10 still green; verdict remains GO.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Reviewer m1 finding: aif-comparison.md §5 matrix didn't reflect that touchpoint 4 (/aif-fix /aif-architecture skill-context overrides) was closed in Phase 4 instead of deferred to Phase 11. Single-line append to the matrix row marking closure with commit ref b5e16b7.
…import Two pre-existing typecheck errors closed via diagnosis (retro Open #5 packages/core half): probes/audit-r4.ts is a CONSUMER-PROJECT script template (ships via setup.sh to consumer's scripts/audit-r4.ts), not a local probe. Evidence: audit-self/audit-ai-docs.sh:11 explicitly says "scripts/audit-r4.ts in consumer project"; ts-morph is a CONSUMER dep added by setup.sh COMMON_DEPS, not a packages/core dep; manifest path is "scripts/audit-r4.ts" (consumer view), not "probes/audit-r4.ts". Excluding probes/ from local typecheck is the architecturally correct fix — the file is meant for consumer's tsconfig with consumer-installed ts-morph, not ours. render-rules.ts: ajv@8 d.ts exports Ajv BOTH as named class AND default (see node_modules/ajv/dist/ajv.d.ts:3,8). Under Node16 module resolution + CJS interop, default import resolves to namespace ("This expression is not constructable"). Named import gives the class directly.
Same architectural pattern as packages/core/probes/ (closed in prior commit): templates/ contains files that ship to CONSUMER projects (playwright.config.ts, vitest.config.ts) and reference CONSUMER deps (@playwright/test, @vitejs/plugin-react), not preset deps. Local tsc shouldn't typecheck them — consumer's tsconfig with consumer-installed deps does. Closes retro Open #5 (preset half).
Three items closed in orchestrator session post-PR #4 push: - m1 (9f61a02) aif-comparison.md §5 touchpoint 4 status sync - Open #5 core (4f0f226) typecheck via consumer-template diagnosis - Open #5 preset (ef8262a) typecheck via templates/ exclusion All architectural realizations: probes/ and templates/ contain consumer-deployed files referencing consumer-installed deps. Local exclusion is the correct fix, not adding deps locally. Full regression green: self-audit 24/24, core 95/95, preset 38/38, all 3 workspaces typecheck clean. Q2 + M2 + Open #6 architectural choice still open — research bundle mapped (5 option bundles); awaits Art's decision.
…v storybook json from preset
Creates packages/core/templates/react-next/{.storybook/main.ts,.storybook/preview.ts,storybook-package-additions.json}
(last moved from packages/preset-next-15-canonical/templates/ via git mv — history preserved).
Updates setup.sh:230,231,238,242 from $PKG_DIR/templates/react-next/ to $PKG_DIR/packages/core/templates/react-next/.
Closes Open #6 (option b): establishes scalable per-stack templates pattern symmetric with packages/core/templates/shared/.
…rtial-extended) Extends DetectionResult with optional missing[] (absent known packages, fed to Layer 2 Research Agent) and patterns[] (observable stack patterns e.g. nextjs-app-router). Ships 5 known packages and 5 patterns. Defers language/structure/router to v2. Annotates arch.md §2.3 with v1.1 subset note. Declares detector-v0 maintenance-only. Snapshot diff is additive only.
Open #2 (Tailwind v3/v4 version-aware) closed by ea3e006 via patterns[] field including tailwind-v3-config + tailwind-v4-css-tokens. Mantine/Chakra additions remain additive scope. New versioning entry covers Bundle 4-partial-extended completion: - missing[] + patterns[] schema additions (v1-active partial) - arch.md §2.3 v1.1 subset annotation (Q2/M2 partial closure) - detector-v0 deprecation declared - Test count semantics clarified per prior reviewer note Phase 5 entry closures total: 5 of 6 (Open #2, #3, #5, #6 + m1). Q2/M2: partial via annotation; full v2 deferred to Phase 7+.
Phase 5 entry sealed — final stateThis PR started as Phase 4: Stack Detector v1 deliverable, then absorbed the Phase 5 entry hygiene batch (closing open questions from the Phase 4 retro) plus Bundle 4-partial-extended schema work. Two independent reviewer cycles verified each scope expansion. Documenting change-of-state per reviewer recommendation; PR body left intact for history. Two reviewer cycles complete (Opus 4.7)Cycle 1 — Phase 4 close + M1 fix Cycle 2 — Phase 5 entry hygiene + Bundle 4-partial-extended Phase 5 entry closures (5 of 6 actionable)
Final regression
Deferred (not blocking)Open #1 (multi-stack monorepo, Phase 9+), Open #4 (AIF v3+ schema validation), m2 (regex order until first false-positive), m3 (spec-validation gh-API flake → network-tagged suite), Q2/M2 full v2 schema (`language`, `structure`, `router` → Phase 7+ if Layer 2 Research Agent demand emerges). Source of truth`docs/meta-factory/retros/phase-4.md` — versioning entries trace 9 → 7 → 8 self-app score evolution + per-commit closure log + Bundle 4-partial-extended completion entry. `detector-v0` is now `@deprecated` (removal trigger = v1 R8 parity in Phase 6+). Re: PR body "Pre-existing typecheck errors (out of scope here)"Those were closed in this same PR (`4f0f226` + `ef8262a`) — body left intact for history; this comment documents change-of-state. |
…s-reuse passes its own framework gates Extend Phase 8.8 description with explicit recursive self-validation section. P-build-vs-reuse principle is subject to the same gates as every other rule in the framework (recursive thesis per aif-comparison.md §10 differentiator #4). Three forms of recursion documented: 1. L4 6-gate validation table — gate-by-gate status for the new principle. Gate 1 (schema) ✓ — manifest entry; Gate 2 (rule-tester) n/a — manual rule (G2 Pages-Router precedent); Gate 3 (mutation) n/a v1; Gate 4 (tautology) ✓ MUST — pre-push hook companion must fire when violated; Gate 5 (two-AI review) DEFER per §13.10 entry #4; Gate 6 (cross-rule conflict) ✓. 2. Self-application at creation — chicken-and-egg resolved: the 2026-05-08 analog research session IS the consult evidence for creating P-build-vs-reuse. Strongest form recursion: rule about prior-art consult is itself prior-art-consulted. Documented in Phase 8.8 retro as «self-application at-creation evidence». 3. 6 Уровней as defense-in-depth — each level catches a distinct class of violation failure mode (drift / staleness / phase-entry / session-start / implementation-time / time-decay). Violation must pass all 6 layers undetected to slip through. Mutation testing note: when gate 3 activates (Phase 9+ Path B), invert pre-push trailer regex → meta-test must fail. Otherwise the test of the test is itself tautological. Acceptance criteria extended: P-build-vs-reuse passes applicable L4 gates + self-application-at-creation evidence documented in retro.
Closes 2 MAJOR + 1 MINOR review findings on PR #13: - M1 — A3 SSOT entry #5 was added with 1 phrasing; PROMPT Hard Constraint #10 strictly requires ≥3 phrasings BEFORE adding SSOT entry. Added 2 more context7 query-docs phrasings against /anthropics/anthropic-sdk-typescript covering cache_control TTL options, max_uses budget, server_tool_use.web_search_requests counter, Usage interface token-counter independence. Updated §4.A3 with 3-phrasing log + production-tracking surface details (strengthens SSOT #5 ADOPT-WHEN-TRIGGERED claim). - M2 — A1 ROI rigor: PROMPT §6 T3 explicitly named Cody and Aider as candidates; T3.2 covered only Cursor + Continue.dev + Factory. Added Cody (Sourcegraph) + Aider lookups. Both confirm: Cody's cody.contextFilters / *.rule.md and Aider's .aider.conf.yml are AGENT-side configuration (model selection, repo filters, prompt guidance), NOT ESLint rule pick-from-menu. 5-candidate coverage reinforces «no LLM-pick-of-ESLint-rules analog in production» negative-existence claim — A1 DEFER verdict strengthened, not weakened. - m1 — retro Verification block #4 said «10 trailer lines» but actual count is 11 (T5 skipped + T6 retro trailers were missing from enumeration). Corrected to 11 = 3+1×8 across 9 commits. No verdict change in §5 matrix; ROI thesis still closes negative. SSOT entries #4, #5 unchanged. Phase 9 implementation scope (A6/A7/A8/A9 BUILD; A1-A5 DEFER) unchanged. File sizes after T7: phase-9-entry-research.md 182 LOC (≤200); retro 115 LOC (≤200); SSOT 96 LOC (≤500). Principle 08 7/7, 246/246 core tests, 9/9 verdict rows match acceptance regex. Prior-art: prior-art-evaluations.md#5 (web_search_20250305 — 2 additional context7 phrasings close M1 violation of Hard Constraint #10; ≥3 phrasings discipline now satisfied for SSOT entry add). Prior-art: prior-art-evaluations.md#4 (Factory ESLint Plugin — Cody + Aider lookups close M2 coverage gap; 5-candidate base reinforces negative-existence claim driving A1 DEFER).
The triggering incident for this Phase 8.8.1 session: phase-9-entry-research.md §4.A1 closed §13.10 entry #2 ROI re-evaluation negative on a 5-candidate context7 base (Cursor, Continue, Factory, Cody, Aider) and missed two production-grade analogs in adjacent paradigms — AIF /aif-evolve (skill-context rule synthesis from accumulated patches; already an integrated dep) and Oh My ClaudeCode family (multi-agent orchestration in our exact runtime). Patch maps the gap onto checklist items §1.1 (own-stack sweep — #own-stack-blind-spot), §1.3 (semantic-distance — #semantic-anchor), §1.5 (prompt-list anchoring — #prompt-list-anchoring), with #negative-existence-claim applying as the umbrella mode. Prevention rules are operationalisable («before closing X, also check Y»), not generic ergonomics. Tags seed the aggregation table — first datapoint per tag. Verdict (A1 DEFER) is unchanged; only the recorded reason is corrected. No re-litigation of the matrix per Hard Constraint #4. 43 LOC ≤100 cap. Prior-art: skipped — docs-only patch documenting an incident, no new capability surface or explicit dependency added (per CLAUDE.md capability-commit definition).
Phase 8.8.1 addendum push-based health check on 12 non-cascade §13.x triggers (excluding §13.2 narrative, §13.3/§13.6/§13.7/§13.9 closed, §13.11 cascade, §13.10 #5 cascade, §13.15 unused). For each entry: decompose trigger into observable signals → run verification probe → classify FIRED / STILL ARMED / CASCADE-DEPENDENT. Result: 12/12 STILL ARMED or PENDING T7. Zero FIRED. No per-fire trigger-fire-§<N>.md patches needed. Probes consulted: - §13.1: research-store file count (12 at 1-pattern-per-file granularity); - §13.4 / §13.5 / §13.10 #4 / §13.12 / §13.13: consumer evidence (none); - §13.8: self-application.md §3 row count (9, no 10th proposal); - §13.10 #1: Phase 8 closure status (no curated-store gap); - §13.10 #2: Phase 9 entry coverage gap recorded f92f60b — DEFER carries forward; - §13.10 #3: Phase 9 implementation scope (housekeeping only — no new pattern); - §13.14: rules-lock.json schemaVersion = 1 (no bump); - §13.16: PENDING T7 self-review. Sweep adds value most when external signals (consumer issues, version bumps) accumulate between sessions OR a phase fires a trigger the originating session didn't notice. Both vectors quiet at 2026-05-08; unsurprising outcome. Re-sweep cadence: next phase entry research session. T6 distillation folds the sweep methodology into rule §1 as 6th checklist item. Prior-art: skipped — docs-only sweep report companion to T5.5 audit, no new capability surface or explicit dependency added (per CLAUDE.md capability-commit definition).
…pointer + m3 forward-finding) Closes 1 MAJOR + 2 MINOR review findings on Phase 9 prompt drafting: - M1 — A8 «calibration» framing was overclaim. PHASE-9-PROMPT.md §4 T3 said weights become «data-backed» — but 5 mutants × 3 dimensions ≈ 1 sample per dimension = regression guard for the formula, NOT statistical calibration of 0.40/0.40/0.20 values. Updated §4 T3 «Why» paragraph + sub-task #4 doc-note text + verification grep probe (regression guard, not data-backed) to distinguish «formula behaves as designed» (what corpus proves) from «weight values are right» (still requires real divergent-plan data per Phase 8 retro Self-reflection #9). - m2 — retro Versioning section did not point to next-step push + gh pr create. Added explicit «Next:» line at end of Versioning + 2026-05-09 entry recording T6 review delta-fix landing. Also corrected the branch-name reference (drafting work is on docs/phase-9-prompt-draft after Phase 8.8.1 T0 split, not on docs/phase-9-coverage-gap). - m3 — drafting session did not apply (yet-to-be-formalized) Phase 8.8.1 search-coverage methodology to its own decisions (own-stack sweep on A8 corpus shape; adversarial check on N=15; surfaced Open Q from drafting itself = 0). Circular: Phase 8.8.1 not yet merged → methodology not formally required. Documented as Open Q #4 forward-finding for post-Phase-8.8.1-merge audit. Reviewer-flagged BLOCKER B1 (drafting work «lost») was misdiagnosed — drafting commits live on this branch (origin synced at 1c422cd); T0 of Phase 8.8.1 meta-prompt split correctly. Reviewer's coverage was bounded to one branch, missed the parallel docs/phase-9-prompt-draft. PHASE-9-PROMPT.md = 307 LOC ≤500 cap unchanged; retro 119 → 124 LOC (+5 from m2 closing + m3 Open Q #4) ≤200 cap.
…+ §13.23 self-review Updates self-reflection skill (.claude/skills/self-reflection/SKILL.md) by adding the §1.7 enforcement layers table: 3 active + 1 deferred → 4 active. The 4th layer (pre-push trailer check) shipped in Commit 1 of this batch. Ships paired self-review patch at docs/meta-factory/research-patches/2026-05-11-§13.23-4th-layer-self-review.md per §13.23 promotion path #4. The patch walks the new layer through §1.7 forward+backward checks applied to its own motivating gap (local-push- bypasses-CI + discipline-theatre risk). §13.23 status update in open-questions.md folds into sub-wave 7.5.c per kickoff §«Sub-wave 7.6.d» (atomic with §13.27 + §13.28 closures); NOT modified in this commit. §1.7 self-application: this commit touches .claude/skills/self-reflection/ SKILL.md AND adds new ## §1.7 enforcement layers section heading. The C4 predicate fires on this commit. The §1.7 check shipped in Commit 1 of this batch (one commit earlier in the push range) and is in warn-only calibration mode — B1 bootstrap trailer below satisfies §1.7 for this commit. Prior-art: skipped — doc-only update to self-reflection skill description + self-review patch in research-patches/; no new packages/ subdirectory, no new file ≥80 LOC. §1.7 Bootstrap: updating skill ladder from 3 active + 1 deferred → 4 active layers and shipping self-review patch immediately after introducing the §1.7 enforcement layer — §1.7 check itself is in 30-day calibration (warn-only) at this commit; B1 exemption per 7.6.a research §4 + 7.6.b §2 Problem 2 verdict (mirrors Commit 1 bootstrap exemption; both commits are in the same push, same bootstrap context).
…rrides where unavoidable (#980) Fixes all 16 open Dependabot alerts (triage-first, per-alert rationale below). Two atomic commits: lock/manifest bumps; drizzle fixture pins. No new dependencies — overrides entries and in-range bumps only; not a capability commit. ## Per-alert disposition | Alerts | Package | Fix | Mechanism | |---|---|---|---| | #17-#24 (7×, incl. **critical** CVE-2026-33937) | handlebars 4.7.8 → 4.7.9 | root lock | `eslint-plugin-boundaries ^5.0.0 → ^6.0.2` in `packages/preset-react-spa/package.json:26` — 6.0.2 pins `@boundaries/elements 2.0.1` → handlebars 4.7.9. Chosen over also-available ^7.0.2 as the smaller major jump with the same security outcome. The shipped template `packages/preset-react-spa/templates/eslint.config.react.mjs` is unchanged: v6 keeps the explicitly-configured legacy `element-types` rule + `boundaries/elements` settings working. | | #4, #5 (CVE-2026-8723) | qs 6.15.1 → 6.15.2 | both locks | `overrides` in root `package.json` + `packages/core/package.json` — **unavoidable**: latest typed-rest-client (3.0.0) still pins vulnerable qs 6.15.1 exactly (verified against the npm registry). | | #27 (CVE-2026-53550) | js-yaml 4.1.1 → 4.2.0 | root lock | `overrides "js-yaml": "4.2.0"` — parent markdownlint-cli2 pins exact 4.1.1; its fixed release 0.23.0 requires node>=22 while CI runs node 20, so the parent bump is blocked. Exact spec matches packages/core's direct-dep spec (npm direct-dep override rule). | | #12 (CVE-2026-48988) | markdown-it 14.1.1 → 14.3.0 | root lock | `overrides "markdown-it": "^14.2.0"` — same markdownlint-cli2 exact-pin reason. | | #10, #13 (CVE-2026-49356) | @babel/core 7.29.0 → 7.29.7 | both locks | in-range `npm update` (parents allow ^7.x / ~7.29.0). | | #7 | esbuild 0.28.0 → 0.28.1 | packages/core lock | in-range `npm update` (tsx ~0.28.0, vite ^0.28.0). Root esbuild was already 0.28.1. | | #16, #28 (CVE-2026-39356, high) | drizzle-orm ^0.40.0 → ^0.45.2 | 2 fixture manifests + stub | `packages/core/detector/fixtures/hono-drizzle-monorepo/apps/api/package.json:5`, `packages/core/research/fixtures/tier1-single-root/package.json:3`, stub `.../node_modules/drizzle-orm/package.json` bumped in lockstep. Consuming tests verified version-insensitive (name-based detection; `research-plan.json` has `version: null`) — fixed, not dismissed. | ## Notes - **Synth bundle deliberately NOT regenerated:** the bundle is built by ROOT esbuild (`scripts/build-synth-bundle.sh:17` uses `$ROOT/node_modules/.bin/esbuild`), which was already 0.28.1 and is unchanged; the packages/core esbuild bump is a non-feeding transitive. `NODE_ENV=development build-synth-bundle.sh --check` green after fresh `npm ci` of both locks. - **Consumer note (preset-react-spa):** boundaries v6 changes the `dependency-nodes` default from `["import"]` to `["import","export","require","dynamic-import"]`. Consumers may see new (legitimate) `boundaries/element-types` findings on `export … from` / `require()` / dynamic-import edges — stricter, not broken. - **Expected `npm ls` marker:** `invalid: qs@6.15.2` under typed-rest-client is the documented override-forced exact-pin violation (the point of the fix); no CI gate runs `npm ls`. ## Verification - Full local CI-equivalent gate set green: typecheck, full packages/core suite (2288 tests, 0 failed), `format:check`, `NODE_ENV=development build:synth-bundle:check`, `render-rules --check`, `render-rule-index --check`, install-sh `gh-534-arch-boundaries` + `f17-lint-rules-planted-violation` + `byte-identical` (fingerprints unchanged — no SNAPSHOT_MODE regen needed). - Lock integrity independently re-verified from clean state: `npm ci` (root) and `npm ci --prefix packages/core` both exit 0; every touched lock entry has version+resolved+integrity; zero unexpected version changes in either lock diff (only intended packages + their transitive helper chains). ## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files) ### §1.7 Forward-check applied This PR introduces no new rule; the forward obligation is that each fix lands at the earliest reachable channel and is executable, not prose: vulnerable versions are excluded structurally by manifest constraints — `package.json:6-10` (overrides block), `packages/core/package.json` overrides, `packages/preset-react-spa/package.json:26` (^6.0.2 floor) — so any future `npm install` cannot silently re-resolve a vulnerable version; regressions fail at install/lock level, before CI. ### §1.7 Backward-check applied Class of this change = dependency-version-constraint updates. Surfaces where the class occurs, each swept: root `package-lock.json` (all 6 target packages patched, no residual vulnerable entries — verified over the full lock, not the diff); `packages/core/package-lock.json` (qs/@babel/core/esbuild patched; contains NO handlebars/js-yaml/markdown-it entries → no overrides needed there); fixture manifests `packages/core/detector/fixtures/hono-drizzle-monorepo/apps/api/package.json:5` + `packages/core/research/fixtures/tier1-single-root/package.json:3` + its stub (bumped in lockstep, internally coherent); other fixture manifests under `packages/core/**/fixtures/` (swept — no other pinned occurrences of affected packages); shipped templates + `install.sh` (grep: zero version pins of affected packages); synth bundle `packages/core/synthesizer/synth-and-wire.bundle.mjs` (not fed by the core esbuild entry — `scripts/build-synth-bundle.sh:17`; `--check` green); install fingerprints/baselines (`tests/install-sh/byte-identical.test.sh` green — unshifted).
…yte-drift gate, pure-bash delivery layer (python-delivery-v0) (#991) * docs(orchestrator): python-delivery-v0 meta-launch kickoff (launch-table + stage gates) Generated by /pipeline python-delivery-v0 (plan-currency: kickoff on staging #967, no in-flight S1, umbrella open). Orchestration record for the S1-S3 dispatch. Prior-art: skipped — orchestration bookkeeping doc, no new capability * feat(python-starter): curated Python starter ConventionNode[] set (S1 Task 3) Adds packages/core/python-starter/starter-nodes.ts — a curated Python starter node set authored in the frozen neutral IR (ir/types.ts, no field added) and rendered through the existing ADOPTED backends: renderAstgrep (#212, syntax-class default) and renderRuff (#215, TID251/TID253 fast-path). Colocated vitest unit tests (RED-first) assert: grammar-gate pass, getff-* id namespacing (probe-proven: duplicate ids = ast-grep exit 8), render-through-both-backends without error, the probe-proven firing shape, and the datetime caveat — the rendered YAML covers BOTH `datetime.now()` and `datetime.datetime.now()` via two literal-pattern nodes (a single pattern cannot match both; a `$MOD.now()` metavariable over-fires on `foo.now()` — live-verified against @ast-grep/cli@0.44.1). Set content (all severity error): ban datetime.now()/ datetime.datetime.now() calls (flagship P5), ban eval()/os.system() calls (bandit ADOPT-VOCABULARY; ruff refuses FF7001 — ast-grep catch-all), ban datetime.datetime.utcnow (TID251), ban module-level import tensorflow (TID253). SSOT row 217 appended to docs/meta-factory/prior-art-evaluations.md in this same commit (register tail verified: last row was 215; 216 is reserved for the delivery-layer task). vitest.config.ts registers the new python-starter/ include glob. Verified: python-starter 12/12, test:backends 154/154, test:principles 303/303, tsc --noEmit clean. Prior-art: prior-art-evaluations.md#217 (curated Python starter node set — BUILD own content in the neutral IR; ruff has no canonical "recommended" starter preset, bandit's catalog is engine-coupled with no neutral-IR export, Factory plugin #4 is JS/ESLint-coupled — ADOPT-VOCABULARY the ban semantics, author the set ourselves). * feat(python-delivery): commit-time render script + pre-rendered templates + byte-drift gate (S1 T4) Render the curated Python starter node set (packages/core/python-starter/starter-nodes.ts, #217) through the pure ADOPTED backends into static shipped templates under packages/core/templates/python/ (Model A). The renderers stay pure; all fs writes live in packages/core/python-starter/render-python-templates.ts (planPythonTemplates() is pure and is what the drift gate imports). Single-owner lane per rule (coordinator Decision #5), partitioned by node kind — a rule ships in exactly ONE lane so a consumer never gets a duplicate report: - ast-grep lane (call-kind): getff-no-datetime-now, getff-no-datetime-datetime-now, getff-no-eval, getff-no-os-system — one single-doc YAML file per rule under .getff/astgrep-rules/ (idiomatic ast-grep layout; a multi-doc stream fails the repo's own single-document pre-commit yaml.safe_load gate — empirically verified). - ruff lane (attribute/import-kind): getff-no-utcnow (TID251), getff-no-tensorflow-module-import (TID253) — one ruff.toml for the whole lane. Our own sgconfig.yml template (Decision #4, no `ast-grep new` shellout) references the rules dir from the consumer's perspective (ruleDirs: [.getff/astgrep-rules]). Every generated file carries a framework-generated header comment. Byte-drift gate: packages/core/backends/python-templates-drift.test.ts asserts committed templates == fresh render, byte-for-byte, at the pre-push channel (test:backends / hooks/pre-push.ts §5c). Idempotent: re-running the render script produces zero diff. Live-fired end-to-end against the pinned @ast-grep/cli@0.44.1 + ruff==0.15.21 on the exact consumer tree layout (both lanes fire; no rule double-reports). Templates land under a NEW packages/core/templates/python/ dir NOT in install.sh's enumerated copy-list — no install-fingerprint / shipped-doc / principle-test surface is touched. Prior-art: prior-art-evaluations.md#212 (ast-grep render backend, ADOPT) and prior-art-evaluations.md#215 (ruff fast-path render backend, ADOPT) — this commit renders the #217 curated node set through the already-ADOPTED backends; no new render target, the emission verdicts stand. * feat(python-delivery): pure-bash delivery layer + augment-first collision policy + tmp-dir tests (S1 T5) setup.d/45-python.sh ships the pre-rendered Python lint bundle (ast-grep rules + sgconfig.yml + ruff config, S1 Task 4) into a consumer Python repo with an augment-first collision policy — merge into what the consumer authored, never silently clobber. INERT on the npm flow (gated on GETFF_TOOLCHAIN=python; S2 wires the ./setup python entry) — install.sh:564 auto-sources it as a guarded no-op, so byte-identical.test.sh stays 8/8. Collision matrix (probe-decided, .superpowers/sdd/task-2-report.md): - fresh dir -> whole-file copy - sgconfig.yml exists -> structural merge into the block-list ruleDirs (idempotent); unproven shapes (flow-list / multi-key) REFUSE-LOUDLY (a naive second ruleDirs: key = duplicate-field exit 8; a clever-but- unsafe merge is a STOP-line violation) - ruff.toml exists -> REFUSE (a sibling ruff.toml wins entirely + silently disables theirs); ship getff-ruff.toml + `extend` instructions (scalar caveat surfaced if they already use extend) - pyproject [tool.ruff] -> REFUSE (a sibling ruff.toml SILENTLY overrides [tool.ruff]); ship getff-ruff.toml + [tool.ruff.lint] merge steps - re-run -> delivered config artefacts byte-identical (idempotent) Every degrade path is printed AND appended to <consumer>/.getff-python-install.log. A pre-existing .prettierignore gets `.getff/` appended idempotently; none is created. tests/install-sh/python-delivery.test.sh covers every cell + npm-inertness/activation as first-class fixtures (RED-before-GREEN on the merge/refuse logic), plus GATED live-fire: all 4 shipped ast-grep rules + both ruff TID bans fire RED on planted violations (pinned @ast-grep/cli@0.44.1 + ruff==0.15.21; SKIP when unobtainable, like cargo firing.test.ts). Wired into audit-self.yml (meta-all-wired). refresh-covers-full-delivery.test.sh made lane-aware — the Python lane is inert on npm and has its own S2 refresh semantics, out of the npm-refresh-parity gate's population (same rationale as its skill/merge/yq exclusions). SSOT row 216 appended (delivery-mechanism BUILD verdict). Prior-art: prior-art-evaluations.md#216 (thin pure-bash delivery writer — BUILD; no headless tool copies a pre-rendered multi-tool lint bundle into an existing repo with augment-first collision. ADAPT the skip/version-stamp concepts from #124/#125, REFERENCE the `ast-grep new project` #212/#185 scaffold precedent; reuses install.sh copy_safe/merge machinery, zero new deps). * fix(python-delivery): sgconfig idempotency survives trailing-comment edits + .ruff.toml fixture + empty-array guard Review-round-1 findings addressed (commit 9e9b5bac9, setup.d/45-python.sh + tests): - Important: _py_sgconfig_merge's idempotency grep required an exact-match line, so a consumer-added trailing comment on our entry (`- .getff/astgrep-rules # our rules`) was not recognised as already-present — a re-run inserted a DUPLICATE entry, tripping ast-grep exit 8 on the consumer's next scan. Fixed by stripping trailing #-comments before the compare (same shape as the existing rest="${rest%%#*}" ruleDirs: value check two lines away), bash 3.2 compatible. TDD: added a failing cell-(ii) fixture first (RED: 2 entries instead of 1), watched it fail against the unfixed code, then fixed and watched it pass. - Important: setup.d/45-python.sh:153's `.ruff.toml` (dotfile) collision branch had no fixture. Added cell-(iii-dotfile): consumer has .ruff.toml only → layer REFUSEs loudly, ships getff-ruff.toml, prints extend instructions, logs the refusal. Passed immediately (branch already existed and worked). - Minor: tests/install-sh/refresh-covers-full-delivery.test.sh's NPM_LANE_LAYERS array expansion under `set -u` would crash on bash 3.2 if the array were ever empty. Added the repo-precedent length guard (setup.d/lib.sh:281-283 shape) before expansion. Prior-art: skipped — review-fix round, no new capability --------- Co-authored-by: t <t@t.co>
…verables (α-σ shapes + claims + inflight) (#1176) * feat(scripts): add prune-worktrees.sh — safe-by-default sweep of landed CC worktrees Idempotent preserve-then-prune sweep that reclaims worktrees whose work already landed in origin/staging (ff-merged, squash-merged PR, or closed PR). Safety properties: - DRY-RUN by default; --apply to actually remove. - NEVER removes: unmerged-with-open/no-PR worktrees, locked worktrees, the primary checkout, or the worktree the script runs from. - PR-floor guard (>=100 records): aborts if 'gh pr list' flakes, so a partial result can't misclassify squash-merged branches as active and under-prune. - T17 preserve-before-destroy: gitignored orchestrator-prompts content is reconciled into $CANON via scripts/link-coordination.sh before any removal (best-effort; conflict -> skip, leave intact). - dirty worktrees: classified but skipped without --force-dirty. shellcheck-clean (no dead-variable warnings). * docs(role-context): research pre-codification — progressive disclosure per role (review + 4 options) Design doc preparing material for a fabla on whether/how to codify role-based context shaping in this repo. No rule proposed yet. Research findings: - External: Anthropic + LangChain + arXiv converge on 'progressive disclosure' (canonical term) / 'context isolation'. Anti-patterns: context pollution, context rot (Anthropic official); lost-in-the-middle (Liu et al. TACL 2024, 5300+ citations, the mechanism-level citation). - Internal: the repo's inject-matching-rule.sh implements progressive disclosure for PATHS and SESSIONS but never for ROLES (no hook branches on subagent_type). 6-block input contract (claude-glm-executor-handoff:52-71) is flagged for promotion but never promoted. - SDD: strong CATEGORICAL discipline (brief-as-file, reviewer allowlist task-reviewer-prompt.md:38-50) but zero QUANTITATIVE discipline (no token/ char budget, unbounded ## Context slot, no enforcement script — the 42k-char warning is prose only). Lays out 4 design options (full / spike / surgical / defer) + 5 open questions for the brainstorm. * docs(role-context): rev 2 — wave-2 audit of superpowers plugin + wrapper skills Wave 2 delegated research (4 parallel agents: DeepWiki obra/superpowers + web + wrapper-skill audit + exhaustive skills audit) revised the picture: Three corrections to wave-1 framing: 1. superpowers DOES ship a real injection hook — hooks/session-start (hooks/hooks.json:3-15) — but it's ONE flat SessionStart dump of using-superpowers/SKILL.md, identical for every role, no other hook events. Wave 1 missed the plugin's hooks/ dir entirely. 2. Progressive disclosure + injection are NOT a deliberate pair in either superpowers or this repo. Disclosure is prose about the host harness (writing-skills/anthropic-best-practices.md:235,1049,1099); injection is one flat bootstrap. Never cross-referenced. 3. The 3 wrapper skills (arch/pipeline/dispatcher) add verdict + routing on top of SDD — they do NOT add context-shaping. All three consume SDD's templates as-is. New design candidate: Option E — extend the SessionStart/SubagentStart hook to be per-role. Plumbing partially exists (inject-subagent-context.sh:62 already preserves subagent_type). Hook IS the enforcement — earliest reachable channel per invariant #4. Doc adds: §3.7 (plugin hooks), §4.7 (wrapper skills), §5.5 (deliberate-pair verdict), Option E + matrix row, open question #6 (is the pair load-bearing), 17 internal citations including superpowers source repo. * docs(role-context): rev 3 — wave-3 in-flight compat + scratchpad Seat B + 4 GLM hypotheses Wave 3 reconciled two live threads wave-1/2 missed: 1. session-start-token-audit umbrella (operator-commissioned today, branch claude/session-start-token-audit-77d224) — owns the context budget vocabulary; ~140KB/~36-40k tokens injected at session start; S2 trims by re-scoping channels (target <=20-25k). Natural host for role-context-budget work. 2. AIF scratchpad Seat B (feature-scratchpad-d49985) — already answered the operator's exact question. VERDICT: REVISE. 4 of 5 per-role axes already match SDD; only gap is axis 5 (ambient injection uniform). LOAD-BEARING (F2): uniform digest is DELIBERATE anti-drift machinery (2026-05-09 incident — reviewers pattern-matched on EXECUTION-PLAN 'north star' and reinforced wrong goal across cycles). New: Option F (hybrid one-line anchor = Seat B's Option C) — keep uniform digest (preserve anti-drift) + add per-role prime line. Resolves F2 tradeoff that Option E ignores. 6 options now A/B/C/D/E/F. Split: main design doc now 595 lines (<=600 husky pre-commit limit); wave-3 dossier holds §10 full content (compat matrix, 4 GLM hypotheses H1-H4 with evidence+falsifier+confidence, honest §10.6 disclosure, §10.8-10.9 full tables moved from main doc). Sequencing constraint: do not touch inject-matching-rule.sh surface until PR #1175 (getff-honest-signals S6) merges; do not start role-context-budget work until session-start-token-audit S2 lands. * docs(research-patch): raw research for Opus cold-verify — per-role context shaping Replaces the earlier design doc + dossier (wrong format — those were spec/ design output, my job is raw material for Opus to filter, then fabla writes the spec). Format: docs/meta-factory/research-patches/ — matches the existing R-phase research-patch convention (e.g. 2026-07-18-zcode-parity-s7-subagentstart.md). Content: - R1 external: progressive disclosure / context isolation are the canonical terms; anti-patterns context pollution/rot/lost-in-the-middle (Anthropic + LangChain + Liu et al. TACL 2024). - R2 internal: 3-layer path/session-gated inject mechanism; NO per-role branch in any hook (inject-subagent-context.sh:62 preserves subagent_type but does not branch). - R3 superpowers plugin: ships ONE hook (SessionStart flat inject); SDD has categorical discipline only (no quantitative budget; 42k-char warning is prose). - R4 wrapper skills (arch/pipeline/dispatcher): add verdict+routing, NOT context-shaping; consume SDD templates as-is. - R5 in-flight: session-start-token-audit umbrella (operator-commissioned today) owns the budget vocabulary; PR #1175 OPEN on inject-matching-rule.sh surface; AIF scratchpad Seat B already answered the operator's exact question (REVISE — only gap is axis 5 ambient injection uniform; F2: the uniform digest is DELIBERATE anti-drift machinery per 2026-05-09 incident). 10 falsifiable claims (C1-C10) with explicit 'wrong if' falsifiers. 8-item verify-list for Opus (digest size, 2026-05-09 incident root cause, skill-context generalization, BUILD re-trigger, re-grep, version check, orthogonality). 5 forks PARKED (no verdict picked, per reviewer-discipline §2 + the operator's explicit instruction that GLM builds material, Opus filters, fabla decides). * docs(role-context): reframe as raw material — 18 candidate shapes (α-σ), no recommendation Reverses the earlier spec framing. The fabla should generate its own shapes first, then skim this catalogue for anything it didn't think of — not the other way around. GLM's job is resource-intensive raw material; Opus filters; fabla decides and writes the spec. Changes: - Design doc renamed 'Design — Role-Based Context Budget' → 'Candidate shapes — per-role context (raw material for fabla, NOT a spec)'. - §6 replaced 'Options A-F with pros/cons/effort/risk + comparison matrix' with 18 candidate shapes (α-σ), each described neutrally: shape, channel, interacts-with, unverified-assumptions. No scoring, no ordering. - New shapes beyond the original 6: η (templates), θ (token gate), ι (tool surface), κ (upstream plugin), λ (artifact-handoff), μ (two-layer digest), ν (role-from-content), ξ (observability), ο (absorb into token-audit), π (codify MUST stay uniform), ρ (consumer generator), σ (measure-first). - TL;DR rewritten as 'raw observations' — no 'natural host', no 'decision in front of us', no direction. - §7 'Open questions' → 'Forks surfaced (no answer implied)'. - §9 'Next step' → 'What this file is (and is not)'. - Dossier §10 reframed: 'in-flight context, not analysis'. §10.3 'changes 3 options' → 'Overlap with Seat B fork (awareness)'. §10.7 'Recommended next action' → 'What the fabla may want to do (suggested, not prescriptive)'. Pressure-language scan: 0 hits on best/strongest/recommend/we-should/ pros/cons/effort. Lint clean. 18 shapes (α-σ). 3 files total: design doc (580) + dossier (138) + raw research patch (263, companion with 10 falsifiable claims C1-C10 + 8-item verify-list for Opus). * docs(per-role-context): rename files to honest names (was misleading) Old names implied a spec/design that doesn't exist; new names say what the files actually are: - design.md → candidate-shapes.md (18 shapes α-σ, no spec) - wave3-dossier.md → inflight-context.md (in-flight surface, not analysis) Cross-references updated. Content unchanged. * feat(scripts): runtime-probe kickoff — verify static research claims in live CC Prior research (3 waves, 8 subagents) read .claude/hooks/*.sh source code and inferred behavior. This kickoff dispatches a task to a LIVE Claude Code container to verify or refute those static claims. 6 probe targets (P1-P6): - P1: inject-subagent-digest.sh emits identical payload regardless of subagent_type - P2: inject-subagent-context.sh preserves subagent_type but doesn't branch - P3: inject-matching-rule.sh path-glob + once-per-session dedup - P4: CC-native loader injects rules without paths: frontmatter - P5: session-bootstrap digest size (~500 tokens estimate, unverified) - P6: 3 wrapper skills don't add per-role context shaping Dispatched to aif-handoff: taskId=f164e807-191a-4336-9fe1-52145255c00e Board: http://localhost:5180/projects/441c1c0c-b633-4612-a34c-2cc0c4d0eaf2 Output: per-role-context-runtime-probe-report.md in the task project root. Read-only investigation — no code/rule changes. The probe reports measurements; the fabla decides later. * feat(scripts): triangulation kickoffs — deep-project-research + cold-review Parallel to the runtime-probe (taskId f164e807), dispatch 2 more tasks for triangulation: 1. deep-project-research (taskId f4dc0bff) — exhaustive sweep of 12 surfaces the prior 3-wave GLM research only sampled: packages/runtime-bridge full tree, plugin/, packages/core/, tests/, scripts/ rest, .github/, setup.d/, all agents/, .claude/skills/**, .zcode/skills/**, ~/.claude/skills/**, docs/meta-factory/ SSOT deep, .claude/rules/ deeper grep, CLAUDE.md + ~/.claude/CLAUDE.md + MEMORY.md. Answers 7 load-bearing questions (Q1-Q7). Output: per-role-context-deep-research-report.md. 2. cold-review (taskId 4e73e54e) — skeptical external review of the 3 prior GLM deliverables (raw research patch + candidate-shapes + inflight-context). 8-checklist audit (A-H): format honesty, claim quality, hidden pressure language, completeness, verifiability of the 8-item verify-list, internal consistency, framing bias, token-economy. Output: per-role-context-cold-review-report.md. Both: read-only, no recommendations, no rule proposals. The fabla decides after Opus filters all 3 angles (runtime + deep + cold). * docs(research-patch): add §1.7 self-review marker (principle 13 compliance) Pre-push hook caught the missing marker. Forward-check applied against the active disciplines (recommendation-laziness, reviewer-discipline, ai-laziness-traps, attention-is-not-a-mechanism, doc-authority-hierarchy); backward-check sweeps 12 sibling 2026-07-* patches (SWEPT-CLEAN) and verifies no scope duplication. * feat(scripts): bundle-for-cc kickoff — assemble 5 sources into 1 for Opus review Operator defined the session boundary: GLM (this worktree) produces raw material; aif containers verify it (3 parallel tasks); Opus in CC accepts the bundle for review + fabla-prep. GLM does NOT filter; Opus is the filter. This task waits for the 3 parallel verify tasks to finish (runtime-probe f164e807, deep-project-research f4dc0bff, cold-review 4e73e54e), then assembles ONE bundle document preserving all findings + contradictions. Output: per-role-context-bundle-for-opus-in-cc.md (read in CC, not in aif). Bundle structure: - §1 substrate: operator question + 3 GLM deliverables - §2 runtime-probe results (live verification) - §3 deep-project-research results (exhaustive sweep) - §4 cold-review findings (audit of deliverables) - §5 contradictions to resolve (LOAD-BEARING — preserves both sides) - §6 parked forks + 18 candidate shapes - §7 what the bundle does NOT do (no filter, no pick, no recommend) Dispatched: taskId=73519b9c-f27e-4bd2-b0b6-002bedcebdbb * chore(per-role-context): remove dispatched kickoffs + prune-worktrees.sh - 4 per-role-context kickoffs already dispatched to aif (taskIds f164e807 / f4dc0bff / 4e73e54e / 73519b9c recorded in aif-handoff); they are dispatch-inputs, not repo utilities — keep scripts/ clean. - prune-worktrees.sh isolated to chore/prune-worktrees-script branch for separate PR (unrelated to research track). --------- Co-authored-by: Test <test@example.com>
…ope (#1235) Two dispatch inputs for the same umbrella, both gated on state that only just became true. **S3 (new)** — one beat + per-stack research paths, spec §6. Its gate (S2b merged, #1233 `a66c0cb9aa`) cleared minutes ago, so this is the first moment S3 could be planned against a tree that exists. Anchors re-verified live at `a66c0cb9aa`, not carried from the spec: `agents/rule-researcher.md` has 0 hits for python/pyproject/ruff (wall #4 confirmed); `INSTALL-FOR-AI.md` has 0 hits for the continuation clause; the delivered starter AGENTS.md comes from `packages/core/templates/shared/AGENTS.md.template` via `setup.d/45-python.sh:808`; `_py_join_researched_rules` is at `setup.d/45-python.sh:161`; the F-A bundling precedent `synth-and-wire.bundle.mjs` is 395 149 bytes as committed. S3 owns fork F-A (Node for the generation CLI: bundle vs declare honestly) — spec §12 assigns it to «the trace umbrella's planner» and gives size-and-maintenance-cost criteria, so the kickoff instructs RESOLVE-with-measurement, not park, and hands it a real maintenance datapoint: the 2026-08-07 S2b egress hit a synth-bundle drift caused by root-vs-packages/core lock disagreement on semver (7.7.4 / 7.8.1 / 7.8.5). Scope note carried explicitly: `setup.d/47-go.sh` + `do_go_lane()` landed 2026-08-06 (#1171). S3 stays python + a rust pointer per spec §6.2 and is told not to widen to go, but also not to let the researcher-agent edit read as lane-complete while omitting it. **R1 §8 (amendment)** — round-2 rework scope after a `FIDELITY: STOP` at the egress seam. Round 1's patch is on `research/getff-any-stack-trace-r1` (`1479f54741`) with no PR opened. The BLOCKER is a false negative-existence claim about the go lane — and the amendment records that this was NOT worker error. Verified mechanically rather than inferred: git show 0e2d366:install.sh | grep -cE 'do_go_lane|TOOLCHAIN=go' -> 0 (R1's base) git show origin/staging:install.sh | grep -cE 'do_go_lane|TOOLCHAIN=go' -> 6 (today) PR #1171 merged 2026-08-06T22:04Z R1's search was honest and correct when it ran on 2026-08-01; the go lane landed five days later, fifty minutes before the egress. The audit aged out. Round 2 must verdict all 7 go cells against the live tree AND record the staleness itself as a finding — a parity audit's verdicts are only as live as the tree they were taken against. Three MAJORs in the same amendment are NOT staleness and stand on their own against §2/§3 as originally written: npm rung 6 is the only EXISTS with no firing evidence (plus four line anchors that no longer resolve); five further EXISTS cells rest on analogy («same machinery as…») rather than a cited firing artefact; and a hybrid `EXISTS*` token breaks «exactly three verdicts», leaving the §7 GAP counts unreconcilable with the matrix. New round-2 trap T-R1-D — patching the auditor's findings instead of re-deriving them: the line numbers in §8 are a map, not evidence, and a round-2 patch citing them second-hand would launder exactly the failure class it exists to fix. Both kickoffs validated locally: principle 12 (traps enumeration) 7/7; `host-verify.sh --list` resolves S3's three-command contract and accepts R1's 161-char prose-only opt-out. Prior-art: skipped — dispatch-input authoring only; no capability, no dependency, no code module. Co-authored-by: Test <test@example.com>
MAJOR — §8.2 claimed `ToolSearch` deferral "roughly doubles the usable budget". The snapshot cannot support that: window 1m, free space 665.4k, so making the 58.1k deferred schemas resident moves free space to ~607.3k (-8.7%). What doubles is the resident HEAD (58.2k -> 116.3k). Restated to the measure the snapshot actually bounds; the supported neighbouring claims (58.1k is about the size of the whole head; still the most expensive available regression) are kept. MINORs, all from the same cold seat: - §0a heading was present-tense "five blocks stay unpriced", false after the update -> marked "(as at stage close) … stayed", with the current count (three: 5d, 9, row 8's injected form) stated in the ANSWERED block and again in §7. - "five rule files" contradicted the patch's own table -> four, with the four named and the other three memory files identified. - DECISION-NEEDED #4 Option A pointed at the sibling's "§5/§9"; the sibling has no §9 (it runs §0-§8) -> corrected to its actual 4 B/t sites, §5, §7 and §8. - The 5d basis asserted server instructions "sit inside the system-prompt region"; the capture establishes only that /context does not itemise them apart from tool schemas -> the locational claim is dropped, since asserting a region is the estimate T-SH-A forbids. - rows 1-4 restatement read 30,163, which reproduces from neither derivation route -> 19,719 × (4/2.6187) = 30,120, remainder 62,340 - 30,120 = 32,220, share 51.7%. All count-claims re-swept by class after the edit: table holds 14 rows with exactly 2 carrying the literal UNMEASURED marker; every surviving "five" is either historical-and-marked or refers to the item-4 probe's five files, a different subject. Prior-art: skipped — review-absorption edit on an existing research patch, no new capability
…check, surface the channel disagreement Round 2 confirmed all six round-1 findings closed and re-derived every §8 figure independently, then found two MAJORs the addendum had not noticed about its own effect on the rest of the file. MAJOR 1 — the §1.7 backward-check asserted SWEPT-CLEAN using figures this same commit restates. Both verdicts re-adjudicated in place rather than left standing: - ADR-3: the "inside ADR-3's stated band" clause was wrong when written — the band is 29-39% and both measurements (27.8% / ~21%) fall BELOW it; under §8.1's conversion the same share moves to ~47%, outside on the high side. Now GAP-FOUND, direction unresolved pending #4. - the spec's P14 row: "the row's arithmetic holds" is true only under the 4 B/t constant it was computed with, since §8.1 restates the same seat at 51.7%. Now HOLDS-CONDITIONALLY on #4B. MAJOR 2 — one seat, two irreconcilable harness figures, previously unflagged. By difference the main seat's remainder is 69,300 of 89,019; /context's categories matching row 5's own definition sum to 28.8k for that SAME session, and neither 28.8k nor 86.9k (adding deferred schemas back) reaches 69,300. The totals disagree the same way: 58.2k resident vs 89,019 first-turn billed, gap ~30.8k. New §8.5 states the disagreement, offers the dispatch-prompt hypothesis explicitly as unmeasured (§0 defines the channel as "resident head PLUS its dispatch prompt", and rows 1-4 never subtract it; this session opened with /orchestrator, which injects a whole SKILL.md body), and draws the consequence that matters: by-difference systematically OVERSTATES the remainder, because anything it cannot attribute to rows 1-4 lands in row 5 by construction. Raised as DECISION-NEEDED #5 with three options including "measure the gap directly". Not resolved here. MINORs: - §4 was the only section the revision sweep had skipped. R1 now carries a PERFORMED block (the paste happened; two of four rows closed, not four; S-D′ no longer has to park). R5's conclusion is REVERSED with its reasoning shown — its "next lever is harness-side" is contradicted by memory files being 50.5% of the resident head and repo-owned. - rows 5c/5e now carry the seat annotation: orchestrator MAIN seat, n=1, not the 62,340-tok subagent seat the table is sized against, with an explicit do-not-sum-against-row-5. - The headline now warns that both its percentages are contested, naming #4 and #5. Count-claims re-swept: 14 table rows, exactly 2 carrying the literal UNMEASURED marker. Prior-art: skipped — review-absorption edit on an existing research patch, no new capability
… the 600-line gate Round 3 confirmed round-2's MAJOR #2 (channel disagreement) and MINOR #4 (seat annotations) fully discharged, and re-derived every §8 figure independently. It then caught the replacement figures themselves. MAJOR — the ADR-3 re-verdict swapped one unsupported number for another: `29,464 / 62,340 ≈ 47%` divides a MAIN-seat /context numerator by the SUBAGENT-seat by-difference denominator — exactly the cross-seat, cross-channel mix this same commit forbids at rows 5c/5e and that §8.5 declares irreconcilable. Restated within one channel: 17,363 × (4/2.6187) = 26,522 = 42.5% of the 62,340-tok seat, or ~26.5% against ADR-3's own ~100k denominator. Both readings put the repo-owned share BELOW the 29-39% band, not above it, so the verdict is now "GAP-FOUND — measured low, consistently across the conversion change" instead of "direction unresolved". MINORs: - §8.1 gave "two reasons, both binding" for not reconverting §2; one was FALSE. Concatenating the five files the ratio was measured on gives 69,453 B and row 1's published 17,363 est-tok is exactly 69,452 B / 4 — the same population, byte for byte. The claim is withdrawn in place and the surviving reason (re-derivation is beyond an addendum) is named as the only one. An unverified escape clause is a stronger shield than the correction it blocks, and this one was steering DECISION-NEEDED #4. - DECISION-NEEDED #5 Option A's "wrong by roughly 2.4x" over-extended: 2.41x is the main-seat ABSOLUTE; the share moves 77.8% -> 49.5%, i.e. 1.57x, and the subagent-seat 68.4% is untouched because /context cannot run inside a subagent. - §8.2 called the whole 29.4k memory block repo-owned; 2,764 of it is host-side (~/.claude CLAUDE.md 964 + MEMORY.md 1,800 = §2 rows 2 and 3). Repo-owned is 26,700 = 45.9% of the head. - The §4 sweep had reached R1 and R5 but not R4, whose premise the paste contradicts: R4 rests on the harness truncating the skills listing "to a ~2k budget", while /context measures the injected block at 8.9k — essentially the un-truncated source-side ~9.1k. Surfaced for S-I, not re-derived here. R4's "129 SKILL.md files" also carries no reproducing command and a recount gives 112, so the population is marked UNVERIFIED. Structural: absorbing the above pushed the patch to 602 lines, over the repo's 600-line markdown gate. Trimming to 599 would be gaming the gate, so §8 is split into a companion patch, 2026-08-07-s-h-p14-context-addendum.md, with §8.x numbering preserved so every cross-reference already written stays valid. Parent 435 lines, addendum 196. Prior-art: skipped — review-absorption edit plus a size-driven split of an existing research patch, no new capability
…e 13) The pre-push principle-13 gate correctly rejected the new patch: a research patch must carry an actual §1.7 self-review, not merely name the section. Added Forward + Backward + T15. The backward-check is a real outward sweep, not a restatement of this diff — the change class is "a post-merge artefact that revises figures already published in a merged research patch", and six surfaces are verdicted, of which four are GAP-FOUND and left unedited by ownership: - the sibling p3d-p11 patch shares the falsified 4 B/t constant at its §5/§7/§8, so §8.1 applies to it identically — named in DECISION-NEEDED #4's Option A as required scope; - the token-economy spec's tag convention (the constant under one of its tags is wrong); - ADR-3 (repo-owned share measures below its 29-39% band under BOTH conversions); - the S-A profile patch (closed historical artefact, its authoring session owns it). T15 records the reflexive fact that this file exists only because the parent hit the 600-line markdown gate — a document about document cost split by a size discipline. Prior-art: skipped — self-review section required by principle 13 on an existing patch, no new capability
…ad of restating it an eighth time Seven cold rounds, seven REVISEs, and rounds 4-7 each found the MAJOR in the PREVIOUS round's own replacement wording. The class never changed: deriving a quantity across mismatched populations, denominators or conversion constants. This round stops deriving rather than deriving better. MAJOR (round 7) — the injected-vs-source share published last round has exactly the defect the adjacent paragraph withdraws another channel for: its numerator is the harness total for 74 LISTED entries, its denominator a byte sum over 129 SKILL.md FILES, and the same note proves those populations differ (dataviz ~380 and claude-api ~360 are in the numerator and have no SKILL.md at all). All four attempts at that share — a 66% population ratio, a ~57%, a "minority", a 56.4-86.7% range — are now withdrawn together with one stated reason, and NO figure, range or magnitude word replaces them. What survives is the comparison that needs no denominator: 8.9k measured against the spec's ~2k budget. MAJOR (round 7) — this file established last round that 26,700 is the PRE-S-G five-file set, then used that fact in exactly one place while three others kept building on it: §8.2 reading 1 and §8.4 ranked S-D′'s "top-ranked own-able target" from it, and R5 REVERSED overturned a recommendation with it. The dominant file in that measurement, ai-laziness-traps.md (26,387 B), is one S-G already replaced with a digest a quarter its size. So no ranking, no "top lever" and no share of the current head is derived from the snapshot; what it establishes is the narrower and still-useful claim that repo-owned memory files were the largest resident CLASS on the seat measured. R5's reversal is downgraded to SUSPENDED: its premise moved, but its opposite is not established either, and a post-S-G re-measurement is S-E's. MAJOR (round 7) — §8.4 still said "#4 and #5", the site round 6 named live. Now #4/#5/#6, and §6's Forks bullet — which still listed three — records the real inventory: six forks, five open. MAJOR (round 7) — the §1.7 note asserted the §7 S-I site "was found by the author's own class grep, not by any review naming it". False: round 6's watch-list names that exact line. The claim is retracted in place, and the note now states plainly that the author-side sweep never became class-driven in any round. MINORs: "no magnitude word is used here" was false of its own sentence; the §8 pointer omitted §8.6; "five rounds running" and "round 5's findings" were inherited prose that silently re-pointed to a different commit. Prior-art: skipped — review-absorption edit on existing research patches, no new capability
…CISION-NEEDED #3 (#1249) * docs(s-h): P14 addendum — operator /context paste answers DECISION-NEEDED #3, and falsifies the 4 B/token convention The operator ran `/context` post-merge and supplied the output, taking Option A of DECISION-NEEDED #3 (§0a). Recorded as a new §8 addendum rather than an in-place rewrite: the measurement history must read "unknown at stage close -> known 2026-08-07", not as though the split had been available all along. §0a is kept verbatim, annotated ANSWERED. Two findings, in order of consequence: §8.1 — the seed's binding 4 B ~ 1 token conversion is FALSIFIED. Seven files carry both a `wc -c` byte count and a harness-reported token count; aggregate 77,156 B / 29,464 tok = 2.62 B/token. Every 4 B/t figure in this patch and its sibling is low by ~1.53x, and because row 5 (harness remainder) is computed BY DIFFERENCE, the remainder is correspondingly HIGH — a first-order restatement puts it near 52%, not 68.4%. Figures are left as published and the correction is recorded as owed, not made: the row-1 file set is the pre-S-G resident set while the ratio was measured on the current one, so they are not the same population, and re-deriving §2 on a new constant is a re-measurement beyond an addendum. Raised as DECISION-NEEDED #4. §8.2 — the reported percentages sum to 105.6% because the two `(deferred)` rows are counted but NOT resident. The identity confirms it exactly: 334.6k - 276.4k = 58.2k resident, and the non-deferred rows sum to 58.2k. Half the resident head is memory files (29.4k of 58.2k), of which two documents carry a third of everything (repo CLAUDE.md 9.3k + ai-laziness-traps.md 9.8k). ToolSearch deferral withholds 58.1k — almost exactly what the entire resident head costs, which is the number §3's "preserve what already works" lacked. §8.3 — closes TWO of the four `UNMEASURED — channel absent` rows, not four: 5c (MCP tool schemas, 8.4k) and 5e (skills 8.9k + custom-agent listing 1k). 5d stays open (`/context` does not itemise server instructions apart from tool schemas) and row 9 stays open (a different population: "Custom agents" counts registered agent types, not the repo's agents/ directory). Neither was filled from the nearest plausible neighbour — that is T-SH-A working, not a shortfall. Revised partition 14 / 11 / 2 / 1, counted from the table. All count-claims re-swept by class after the edit rather than site-by-site (the W-9 lesson from the round-3 fidelity audit): table recount gives 14 rows and exactly 2 carrying the literal marker; the stage-close claims of "four" are retained as historical and each carries its revision inline. Coverage: n=1, an orchestrator seat in a worktree with five rule files injected; a fresh main-checkout or subagent seat has a different resident set. All figures are the harness's own estimates at its own rounding; no tokenizer was run. Prior-art: skipped — post-merge measurement addendum to an existing research patch, no new capability * fix(s-h): absorb cold-audit REVISE on the P14 addendum MAJOR — §8.2 claimed `ToolSearch` deferral "roughly doubles the usable budget". The snapshot cannot support that: window 1m, free space 665.4k, so making the 58.1k deferred schemas resident moves free space to ~607.3k (-8.7%). What doubles is the resident HEAD (58.2k -> 116.3k). Restated to the measure the snapshot actually bounds; the supported neighbouring claims (58.1k is about the size of the whole head; still the most expensive available regression) are kept. MINORs, all from the same cold seat: - §0a heading was present-tense "five blocks stay unpriced", false after the update -> marked "(as at stage close) … stayed", with the current count (three: 5d, 9, row 8's injected form) stated in the ANSWERED block and again in §7. - "five rule files" contradicted the patch's own table -> four, with the four named and the other three memory files identified. - DECISION-NEEDED #4 Option A pointed at the sibling's "§5/§9"; the sibling has no §9 (it runs §0-§8) -> corrected to its actual 4 B/t sites, §5, §7 and §8. - The 5d basis asserted server instructions "sit inside the system-prompt region"; the capture establishes only that /context does not itemise them apart from tool schemas -> the locational claim is dropped, since asserting a region is the estimate T-SH-A forbids. - rows 1-4 restatement read 30,163, which reproduces from neither derivation route -> 19,719 × (4/2.6187) = 30,120, remainder 62,340 - 30,120 = 32,220, share 51.7%. All count-claims re-swept by class after the edit: table holds 14 rows with exactly 2 carrying the literal UNMEASURED marker; every surviving "five" is either historical-and-marked or refers to the item-4 probe's five files, a different subject. Prior-art: skipped — review-absorption edit on an existing research patch, no new capability * fix(s-h): absorb cold-audit round-2 REVISE — re-verdict the backward-check, surface the channel disagreement Round 2 confirmed all six round-1 findings closed and re-derived every §8 figure independently, then found two MAJORs the addendum had not noticed about its own effect on the rest of the file. MAJOR 1 — the §1.7 backward-check asserted SWEPT-CLEAN using figures this same commit restates. Both verdicts re-adjudicated in place rather than left standing: - ADR-3: the "inside ADR-3's stated band" clause was wrong when written — the band is 29-39% and both measurements (27.8% / ~21%) fall BELOW it; under §8.1's conversion the same share moves to ~47%, outside on the high side. Now GAP-FOUND, direction unresolved pending #4. - the spec's P14 row: "the row's arithmetic holds" is true only under the 4 B/t constant it was computed with, since §8.1 restates the same seat at 51.7%. Now HOLDS-CONDITIONALLY on #4B. MAJOR 2 — one seat, two irreconcilable harness figures, previously unflagged. By difference the main seat's remainder is 69,300 of 89,019; /context's categories matching row 5's own definition sum to 28.8k for that SAME session, and neither 28.8k nor 86.9k (adding deferred schemas back) reaches 69,300. The totals disagree the same way: 58.2k resident vs 89,019 first-turn billed, gap ~30.8k. New §8.5 states the disagreement, offers the dispatch-prompt hypothesis explicitly as unmeasured (§0 defines the channel as "resident head PLUS its dispatch prompt", and rows 1-4 never subtract it; this session opened with /orchestrator, which injects a whole SKILL.md body), and draws the consequence that matters: by-difference systematically OVERSTATES the remainder, because anything it cannot attribute to rows 1-4 lands in row 5 by construction. Raised as DECISION-NEEDED #5 with three options including "measure the gap directly". Not resolved here. MINORs: - §4 was the only section the revision sweep had skipped. R1 now carries a PERFORMED block (the paste happened; two of four rows closed, not four; S-D′ no longer has to park). R5's conclusion is REVERSED with its reasoning shown — its "next lever is harness-side" is contradicted by memory files being 50.5% of the resident head and repo-owned. - rows 5c/5e now carry the seat annotation: orchestrator MAIN seat, n=1, not the 62,340-tok subagent seat the table is sized against, with an explicit do-not-sum-against-row-5. - The headline now warns that both its percentages are contested, naming #4 and #5. Count-claims re-swept: 14 table rows, exactly 2 carrying the literal UNMEASURED marker. Prior-art: skipped — review-absorption edit on an existing research patch, no new capability * fix(s-h): absorb cold-audit round-3 REVISE, and split the addendum at the 600-line gate Round 3 confirmed round-2's MAJOR #2 (channel disagreement) and MINOR #4 (seat annotations) fully discharged, and re-derived every §8 figure independently. It then caught the replacement figures themselves. MAJOR — the ADR-3 re-verdict swapped one unsupported number for another: `29,464 / 62,340 ≈ 47%` divides a MAIN-seat /context numerator by the SUBAGENT-seat by-difference denominator — exactly the cross-seat, cross-channel mix this same commit forbids at rows 5c/5e and that §8.5 declares irreconcilable. Restated within one channel: 17,363 × (4/2.6187) = 26,522 = 42.5% of the 62,340-tok seat, or ~26.5% against ADR-3's own ~100k denominator. Both readings put the repo-owned share BELOW the 29-39% band, not above it, so the verdict is now "GAP-FOUND — measured low, consistently across the conversion change" instead of "direction unresolved". MINORs: - §8.1 gave "two reasons, both binding" for not reconverting §2; one was FALSE. Concatenating the five files the ratio was measured on gives 69,453 B and row 1's published 17,363 est-tok is exactly 69,452 B / 4 — the same population, byte for byte. The claim is withdrawn in place and the surviving reason (re-derivation is beyond an addendum) is named as the only one. An unverified escape clause is a stronger shield than the correction it blocks, and this one was steering DECISION-NEEDED #4. - DECISION-NEEDED #5 Option A's "wrong by roughly 2.4x" over-extended: 2.41x is the main-seat ABSOLUTE; the share moves 77.8% -> 49.5%, i.e. 1.57x, and the subagent-seat 68.4% is untouched because /context cannot run inside a subagent. - §8.2 called the whole 29.4k memory block repo-owned; 2,764 of it is host-side (~/.claude CLAUDE.md 964 + MEMORY.md 1,800 = §2 rows 2 and 3). Repo-owned is 26,700 = 45.9% of the head. - The §4 sweep had reached R1 and R5 but not R4, whose premise the paste contradicts: R4 rests on the harness truncating the skills listing "to a ~2k budget", while /context measures the injected block at 8.9k — essentially the un-truncated source-side ~9.1k. Surfaced for S-I, not re-derived here. R4's "129 SKILL.md files" also carries no reproducing command and a recount gives 112, so the population is marked UNVERIFIED. Structural: absorbing the above pushed the patch to 602 lines, over the repo's 600-line markdown gate. Trimming to 599 would be gaming the gate, so §8 is split into a companion patch, 2026-08-07-s-h-p14-context-addendum.md, with §8.x numbering preserved so every cross-reference already written stays valid. Parent 435 lines, addendum 196. Prior-art: skipped — review-absorption edit plus a size-driven split of an existing research patch, no new capability * docs(s-h): add the §1.7 self-review the split addendum owed (principle 13) The pre-push principle-13 gate correctly rejected the new patch: a research patch must carry an actual §1.7 self-review, not merely name the section. Added Forward + Backward + T15. The backward-check is a real outward sweep, not a restatement of this diff — the change class is "a post-merge artefact that revises figures already published in a merged research patch", and six surfaces are verdicted, of which four are GAP-FOUND and left unedited by ownership: - the sibling p3d-p11 patch shares the falsified 4 B/t constant at its §5/§7/§8, so §8.1 applies to it identically — named in DECISION-NEEDED #4's Option A as required scope; - the token-economy spec's tag convention (the constant under one of its tags is wrong); - ADR-3 (repo-owned share measures below its 29-39% band under BOTH conversions); - the S-A profile patch (closed historical artefact, its authoring session owns it). T15 records the reflexive fact that this file exists only because the parent hit the 600-line markdown gate — a document about document cost split by a size discipline. Prior-art: skipped — self-review section required by principle 13 on an existing patch, no new capability * fix(s-h): absorb cold-audit round-4 REVISE — four MAJORs, all in this round's own replacement figures The split is sound (parent 463, addendum 261, all 43 §8.x cross-references resolve) and the addendum's §1.7 backward-check verified as a real outward sweep. But three of round 3's four replacement figures were themselves defective, plus a new challenge block that reversed a downstream premise on an invalid comparison. MAJOR — "Both readings put the repo-owned share BELOW the 29-39% band" is arithmetically false: 42.5% > 39%. And 42.5% is a share of the 62,340-tok SUBAGENT seat while ADR-3's band is denominated on ~100k, so it is not band-comparable at all. Round 3 replaced a cross-SEAT mix with a cross-DENOMINATOR one. Now stated from the directly measured figure with both traps recorded inline so it is not re-derived wrongly a third time. MAJOR — 26,522 was derived by applying §8.1's SEVEN-file aggregate ratio (2.6187, inflated by the one host-side Russian-text outlier at 3.32 B/t) to row 1's FIVE-file population, while the addendum measures that exact population directly at 26,700 (five-file ratio 2.6012). One commit, two values for one block. The measured figure now supersedes the derivation: 26,700 = 26.7% against ~100k (band-comparable, below the band) and 42.8% of the subagent seat (not comparable). MAJOR — round 3's "29.4k is not repo-owned" fix was applied in §8.2 but not swept: §8.4 (the S-D′-facing ranking section) and §4 R5's REVERSED note both still read "29.4k, 50.5% repo-owned", overstating the own-able block by 2,764 tok at the one site a downstream stage reads. Both fixed to 26,700 = 45.9%. Third site of the same class: "six ASCII-dominant repo files" counted host-side MEMORY.md as a repo file. MAJOR — the R4 CHALLENGED block concluded the skills listing "appears not to be truncated at all", comparing the /context-measured 8.9k against a ~9.1k figure that is a 4 B/t estimate this same commit declares low by 1.53x. In one constant: 41,057 B / 2.6187 = 15,678 tok, so 8.9k is ~57% of source; independently the snapshot lists 74 entries against a 112-file population, ~66%. Both channels say REDUCED. The supported half survives — the ~2k budget premise is wrong by ~4x — and that, not "no truncation", is what is routed to S-I. MINORs: §4 R2's "until then / which R1 would settle" was stale once R1 discharged (now PARTLY SETTLED, with the evidence stated as non-conclusive and the row keeping its UNMEASURED pricing rather than gaining a "0"); §7's S-I-kickoff backward-check verdict was not re-adjudicated although this commit moves that kickoff's premise (now GAP-FOUND, routed not edited); the T3 demand for a reproducing command was applied to the 129 being corrected but not to the 112 correcting it (command now published beside it). The addendum's §1.7 now records the method failure rather than only the rows: four rounds, four sweeps driven by the last review's list, each re-failing on whatever the list omitted — T21 in its own-work form. Prior-art: skipped — review-absorption edit on existing research patches, no new capability * fix(s-h): absorb round-5 REVISE by WITHDRAWING claims, not repairing them a fourth time Round 5 found 2 MAJOR, both again in the previous round's replacement figures. That is four consecutive rounds where a hand-revised quantitative claim was itself defective, so this round changes method: the unsupportable claims are withdrawn rather than corrected again. MAJOR — the ADR-3 verdict was denominator-SELECTED, not measured. 26,700 has four defensible denominators and they disagree in direction: 29.99% of this seat's own 89,019 first-turn total (INSIDE the 29-39% band), 26.6% of the 60-session median (below), 45.9% of the /context resident head (above), 42.8% of the subagent seat (above). Rounds 3-5 each picked one and each pick was defective — cross-seat, then cross-denominator, then ratio-transferred-across-populations. The verdict is now WITHDRAWN with all four denominators tabled and no verdict issued, and the choice raised as DECISION-NEEDED #6 (which cannot be settled independently of #5, since the options differ precisely by the ~30.8k dispatch-prompt gap #5 records). MAJOR — the "74 listed entries / 112 files = 66%" corroborating channel is WITHDRAWN entirely. The numerator is provably not a subset of the denominator: the two largest listed entries in the capture, dataviz (~380) and claude-api (~360), have no SKILL.md anywhere, as do >=14 other built-ins. The denominator is an unfiltered find carrying marketplace/cache duplicates, vendored node_modules files, worktree copies, packages/core fixtures and uninstalled catalogue rows. A ratio across two different sets measures nothing; publishing it would be the estimate-dressed- as-measurement T-SH-A forbids. MINORs: the "~57% of source" precision is withdrawn to direction-only — it swings 56% to 87% across the four conversion constants in play, and the SKILL.md corpus is itself multi-byte-heavy (six skills carry Russian descriptions), so no constant is defensible for it without measuring that corpus. The 112 recount is no longer offered as a correction: publishing the command is necessary but not sufficient, since the command must already exclude what the claim is not about. measure-always-on.sh's "21-28%" gained the re-adjudication marker every sibling surface had. The §1.7 note previously NAMED T21 while committing it. It now states plainly that this round's sweep was list-driven too, that its hunks map one-to-one onto round 5's findings, and that the class-driven counter T21 prescribes is what the five cold audit rounds have been doing while the author-side sweep never became class-driven. It also records the second method finding: three attempts to repair one comparison failed because the comparison had four denominators, and the correct response was withdrawal. Class sweep applied to the withdrawal itself: every site carrying a listing share was found by grep and corrected, not only the one the audit named — the §7 S-I re-adjudication repeated the withdrawn 57%/66% pair and now reads direction-only. Prior-art: skipped — review-absorption edit on existing research patches, no new capability * fix(s-h): absorb round-6 REVISE — the withdrawal inverted a claim; state ranges, never magnitude words Round 6 confirmed the round-5 withdrawal is complete (57%/66% survive nowhere; 74/112 only inside their own WITHDRAWN notices; no third site) and re-derived all four tabled shares as correct. It then found three MAJORs, all again in this round's own replacement wording. MAJOR — withdrawing "~57% of source" to "a minority of source" INVERTED the claim. Under every constant the same note lists, the injected 8.9k is 56.4% / 56.8% / 72.0% / 86.7% of source — a majority — and 51.4% against the pre-S-I byte count. A magnitude word is not a weaker form of a number, it is a different claim. Both sites now carry the explicit range and NO magnitude word; the withdrawal rule is stated so the next editor does not substitute another adjective. MAJOR — the new measure-always-on.sh re-adjudication claimed the measured 26,700 supersedes the "21-28%" pair. Wrong on the NUMERATOR, not the denominator: 26,700 is the pre-S-G five-file set (pinned byte-for-byte in §8.1) while the "~21%" member is the post-S-G set. No denominator choice repairs a numerator mismatch, so no restatement is offered at all — the bound is unverified here and both the surface and the post-S-G measurement stay S-E's. MAJOR — "Options A/B and C differ precisely by the ~30.8k gap" holds only for A (89,019 − 58,200 = 30,819). B differs by 42,329 and is a 60-session median set against a gap measured on one session, so B compounds #5 with a population change rather than restating it. Corrected in place. MINORs: the "six skills carry Russian descriptions" clause is DROPPED rather than corrected — two greps disagreed (6 vs a repo count polluted by node_modules), and the sentence two lines above faults another figure for lacking a reproducing command, so publishing an unverifiable one there was the same defect. "#6 below" pointed above. DECISION-NEEDED #6 is now propagated to every enumeration that had stopped at #5: the §2 headline warning, R5's REVERSED note (which quotes 45.9% — one of #6's four tabled options, now labelled as such), the §8 pointer, the addendum header and its §1.7 obligation count. The §1.7 note also records that this round's two records disagreed about whether the sweep found an unnamed site: the commit message was right, the paragraph was wrong. The S-I re-adjudication was found by the author's own class grep. Honest summary now stated: list-driven for five rounds, class-driven for exactly one item. Prior-art: skipped — review-absorption edit on existing research patches, no new capability * fix(s-h): stop deriving — withdraw every cross-population share instead of restating it an eighth time Seven cold rounds, seven REVISEs, and rounds 4-7 each found the MAJOR in the PREVIOUS round's own replacement wording. The class never changed: deriving a quantity across mismatched populations, denominators or conversion constants. This round stops deriving rather than deriving better. MAJOR (round 7) — the injected-vs-source share published last round has exactly the defect the adjacent paragraph withdraws another channel for: its numerator is the harness total for 74 LISTED entries, its denominator a byte sum over 129 SKILL.md FILES, and the same note proves those populations differ (dataviz ~380 and claude-api ~360 are in the numerator and have no SKILL.md at all). All four attempts at that share — a 66% population ratio, a ~57%, a "minority", a 56.4-86.7% range — are now withdrawn together with one stated reason, and NO figure, range or magnitude word replaces them. What survives is the comparison that needs no denominator: 8.9k measured against the spec's ~2k budget. MAJOR (round 7) — this file established last round that 26,700 is the PRE-S-G five-file set, then used that fact in exactly one place while three others kept building on it: §8.2 reading 1 and §8.4 ranked S-D′'s "top-ranked own-able target" from it, and R5 REVERSED overturned a recommendation with it. The dominant file in that measurement, ai-laziness-traps.md (26,387 B), is one S-G already replaced with a digest a quarter its size. So no ranking, no "top lever" and no share of the current head is derived from the snapshot; what it establishes is the narrower and still-useful claim that repo-owned memory files were the largest resident CLASS on the seat measured. R5's reversal is downgraded to SUSPENDED: its premise moved, but its opposite is not established either, and a post-S-G re-measurement is S-E's. MAJOR (round 7) — §8.4 still said "#4 and #5", the site round 6 named live. Now #4/#5/#6, and §6's Forks bullet — which still listed three — records the real inventory: six forks, five open. MAJOR (round 7) — the §1.7 note asserted the §7 S-I site "was found by the author's own class grep, not by any review naming it". False: round 6's watch-list names that exact line. The claim is retracted in place, and the note now states plainly that the author-side sweep never became class-driven in any round. MINORs: "no magnitude word is used here" was false of its own sentence; the §8 pointer omitted §8.6; "five rounds running" and "round 5's findings" were inherited prose that silently re-pointed to a different commit. Prior-art: skipped — review-absorption edit on existing research patches, no new capability * fix(s-h): absorb round-8 REVISE — narrow the withdrawal to what its basis says, repair the broken substitution Round 8 recorded the first CLEAN on W-5, the cross-population-share criterion that produced the MAJOR in rounds 4, 5, 6 and 7. The remaining findings are a different and narrower class. MAJOR — §8.4 stated its withdrawal broader than the §8.2 reading it cites as basis: §8.2 withdraws shares "of the current head", §8.4 restated that as "every share of it [the pre-S-G set] is withdrawn" — while four surfaces still publish exactly such shares (§8.2's class table, §8.6's four-denominator table and their two parent-side twins) and while holding #6 open, whose entire subject is which share to publish. Narrowed to match: the pre-S-G shares stay valid AS pre-S-G shares and are not withdrawn; what is withdrawn is any share of, or top lever for, the CURRENT set — which is what a subtraction map acts on. #6 is now named as the question of which denominator a pre-S-G share is quoted against. MAJOR — propagating the share-withdrawal into the §7 S-I surface broke the sentence: "the listing is nonetheless reduced to measured at 8.9k injected" left a dangling verb phrase, asserted 8.9k twice, and kept the direction word "reduced" that R4 forbids six lines into its own text. Rewritten to carry R4's own closing position: the budget premise is wrong by ~4x, and NO claim is made about truncation either way. MINORs: §1.7's marker inventory still read "R5 REVERSED" after this round renamed it SUSPENDED; the -20,782 B set cut was attributed entirely to the traps->digest swap, which accounts for -19,684 B (the rest is two other files in the same trim); §8.4 called §8.1's measured B/token aggregate an "identity" alongside §8.2's exact arithmetic one, upgrading a 2.37-3.32 empirical average to an exact relation in the round whose purpose was the opposite; "the file that dominates this measurement" is 9.8k against CLAUDE.md's 9.3k, so it is the largest single file, not a dominant one. Prior-art: skipped — review-absorption edit on existing research patches, no new capability * fix(s-h): absorb round-9 REVISE — propagate the narrowed withdrawal to §1.7, drop the wrong-side share label Round 9 resolved five of round 8's six findings and returned one MAJOR of the same W-17 class at a site the previous commit did not reach, plus one MINOR in the wording it introduced. MAJOR — §8.4 narrowed its withdrawal to "any share of the CURRENT set", but the §1.7 T15 paragraph still carried the pre-round-8 breadth: "(§8.2 reading 1, whose share figures are withdrawn as pre-S-G)". The file therefore issued two incompatible instructions about the same table to the same consumer, and the §1.7 form also dropped the "of the current head" qualifier its cited basis carries (§8.2 reading 1). Restated to match that basis exactly: a pre-S-G measurement from which no share of the current head is derived. Enumerated every withdrawal statement across all three S-H patches (grep -n withdraw → 6 hits: addendum :69, :159, :290, :327; parent :262, :297); this was the sole over-broad survivor — :262 withdraws the injected-vs-source share, :297 withdraws the R5 reversal, both correctly scoped. MINOR — the exemption introduced last round read "remain valid as shares of that pre-S-G set". That is exact for §8.2, whose denominator IS the pre-S-G resident head (58.2k), but inverted for §8.6, where the pre-S-G block (26,700) is the NUMERATOR and the four denominators are seat totals — the relation the same paragraph states correctly two lines later. Replaced with a form true of both: computed from that pre-S-G measurement, each against the denominator its own table names. Both edits are subtractive/narrowing and introduce no figure, ratio or magnitude word — the strategy that first produced a CLEAN on W-5 at round 7. Prior-art: skipped — review-absorption edit on existing research patches, no new capability --------- Co-authored-by: Test <test@example.com>
…red) + the kickoff it unblocks (#1255) * arch-v2: S-L recalculation stage — the #5-C measurement + the kickoff it unblocks The S-H work shipped three open forks (#4 conversion constant, #5 remainder channel, #6 denominator) whose resolutions all move the same numbers. #5's Option C was a measurement rather than a vote; it is run here, and it changes what the recalculation stage has to do. Measurement (docs/meta-factory/research-patches/2026-08-07-s-l-5c-first-turn-vs-context.md): one seat, `/context` as the first message, then the first billed turn — both halves, same seat, in the required order. Four session-root transcripts in one worktree, all sharing an identical cache_read prefix of 22,367, so the deltas are differences with everything else held byte-constant. The addendum's named falsifier FIRED. The ~30.8k gap was hypothesised to be dispatch-prompt content; measured, the `/orchestrator` injection is 13,523 tok (44% of it), and two bare-prompt seats reproducing 75,496 exactly still show a 16,196-token gap against a `/context`-reported 59.3k with no dispatch prompt present at all. The residual is seat-constant and dominated by harness-injected session-start payload, which also reopens the DIRECTION of the addendum's §8.5 (if `/context` omits genuine recurring seat cost, it under-reports rather than by-difference overstating). Routed to S-L, not decided here. Second, independent falsification of flat-constant conversion: a dense markdown table measured 1.83 B/token, below the 2.37-3.32 spread S-H recorded. Fork #4 = Option A in its per-seat form is re-confirmed, and 2.62 is explicitly not the replacement — it would have mis-priced that block by 43%. S-L kickoff: applies #4 per-seat at every consuming site (BYTES_PER_TOKEN=4 is live, five consumers), designs the #5 re-labelling from scratch, runs the residual decomposition, and re-adjudicates ADR-3's band to close #6. Tier 2, no marker, host-bound. Recorded as a third gate on S-D′, which ranks levers by the P14 price list — a falsified conversion falsifies the ranking. The merged S-H patches are untouched: corrections land as a new patch plus annotations, per the Artifact Ownership Contract. Prior-art: skipped — measurement plus a stage kickoff over existing S-H outputs, no new capability, no dependency and no packages/ file. * arch-v2: record S-L's ordering as an open question, not a third gate Self-correction to the previous commit, caught by running the backward sweep for the class "a new stage is added to a live umbrella" instead of recalling it. That commit rewrote S-D′'s Depends-on cell from the two-gate `S-E + S-H` form to a three-gate `S-E + S-H + S-L`. The sweep found the phrase is load-bearing in eight places outside that cell: umbrella `:237`, `:371` and §3 (which DEFINES "the two-gate form" as a named pattern), four statements in the just-merged s-d-prime/kickoff.md rev 6 (`:1`, `:106`, `:305`, `:355` — the last three saying the two-gate dependency is the ONLY remaining gate), and the spec's "S-D′ after S-E + S-H" at `:410`. Editing one cell to three-gate while those stand contradicts the umbrella's own Ordering statement ("table and prose agree"), and sweeping all eight is a structural change to a named pattern with an owner — out of scope for a PR whose job is to author one kickoff (CLAUDE.md PR strategy). So: the cell returns to two-gate and carries the constraint as an explicit open question; the S-L section states why it is recorded rather than enforced; the promotion is surfaced as DECISION-NEEDED for the operator. The constraint itself is unchanged and still stated in S-L's own kickoff — what changes is that this PR no longer pretends to have decided it. Prior-art: skipped — correction to a coordination-doc row in the same PR, no new capability. * fix(patch): spell the sibling patch's full filename — a leading hyphen crashed the dead-link check The backward-check row abbreviated the second grep-collision file as `-s-a-profile.md`. The CI dead-link step pipes every backticked *.md span into `basename "$ref"`, and GNU coreutils parses a leading `-s` as the --suffix option: `gbasename "-s-a-profile.md"` → "missing operand", exit 1, and the step runs under bash -e, so the whole job went red on a cosmetic abbreviation. Reproduced locally before fixing (gbasename, exit 1) rather than inferred from the log, and a repo-wide `grep -rhoE '`-[^`]*\.md`'` confirms this was the only leading-hyphen backticked .md reference in the tree — introduced by the previous commit, not pre-existing. Fix is the honest one anyway: write 2026-08-01-token-economy-s-a-profile.md in full. The CI script's own fragility (an unguarded operand reaching basename) is a separate concern, surfaced in the PR body rather than fixed here. Prior-art: skipped — one-line filename spelling correction in an unmerged patch, no new capability. * arch-v2: S-L's ordering deadline went LIVE mid-session — S-E merged Merge-forward from staging brought #1237 (S-E) in, and that falsifies a claim this branch made twice: that the "before S-D′" deadline was real but not binding "since S-D′ is blocked on the two-gate S-E + S-H and S-E is unmerged". Both gates are now closed — S-H #1239 at 2026-08-07T00:06Z, S-E #1237 at 2026-08-07T09:39Z — verified with `gh pr view --json state,mergedAt` rather than read off the umbrella prose, which still described the pre-merge state. S-D′ is dispatchable today, so S-L is on the critical path, and a dispatcher reading s-d-prime/kickoff.md:305 would find "the remaining gate ... is the two-gate S-E + S-H dependency alone" satisfied and dispatch against a falsified conversion. The Option A call from the previous commit stands — re-declaring the umbrella's named two-gate form across eight sites is still a structural change with an owner, and still out of scope here. What changes is urgency, not scope: the S-D′ row now carries the both-gates-satisfied warning inline, and the PR's DECISION-NEEDED 1 is a live footgun rather than a hypothetical one. Prior-art: skipped — factual correction of an ordering claim plus its coordination-row surfacing, no new capability. * fix(patch): publish the conversion ratio as 1.835, not the truncated 1.83 13,827 chars / 7,535 tokens = 1.8350. The patch published "1.83", which is a truncation rather than a rounding, and the derived "mis-priced by 43%" figure is computed from the exact value: |2.62 - 1.835| / 1.835 = 43%. A reader recomputing from the published 1.83 gets 43% too, but from a correctly-rounded 1.84 gets 42% — so the published figure and its own derivation could disagree depending on which way the reader rounded. Publishing three decimals removes the ambiguity: 1.835 reproduces both the "below the 2.37-3.32 band" claim and the 43% mispricing exactly. Caught by re-deriving every published figure machine-side rather than re-reading them. Prior-art: skipped — numeric precision correction in an unmerged patch, no new capability. --------- Co-authored-by: Test <test@example.com>
… it, #5/#6 routed (#1263) * arch-v2 S-L: recalculation — fork #4 as a band, the unit defect under it, #5/#6 routed Applies fork #4 = Option A in its per-seat form and runs the §1.3 decomposition. Four results: 1. The conversion had an undefined UNIT. S-H measured bytes, the 5-C census measured codepoints, both labelled "B/token". Re-measured at f31fd8c (the commit the seat actually loaded — the seat pre-dates S-G reaching that worktree), S-H's byte table reproduces exactly, and in codepoints its 3.32 outlier collapses to 2.587, inside the cluster. The unit now binds to the channel and every figure carries its suffix. 2. §1.3's named load-bearing unknown is CLOSED: /context's `Skills 8.9k` IS the skill_listing attachment (69 entries both channels, per-entry sum 8,870). `Custom agents 1k` is the orchestrator-planner entry alone; the six built-in agent descriptions are billed and counted nowhere. `Messages 1.3k` is bounded to exactly one hook inject, never both (UNMEASURED which). 3. /context UNDER-reports: ≥15,258 codepoints of every-seat harness-injected payload are billed and appear in no category. Addendum §8.5's "the gap indicts the by-difference method" inverts. 4. ADR-3's 29-39% band is stale by construction — S-G cut the numerator 69,453 → 48,671 B (reproduced exactly). Post-S-G the set is 18,081-20,469 tok, a range, not a point. scripts/measure-turn-attribution.sh: BYTES_PER_TOKEN=4 → a LO/HI band at all five consumers, each emitting a range with its direction of error. No constant is substituted — a `4` → `2.62` diff is exactly the T-SL-A defect this stage exists to prevent. Also fixes a defect the cold backward sweep found in this stage's OWN first edit: the stream builder used jq `length` (codepoints) for a field reported as "stdout-bytes" and converted through the byte band. With `utf8bytelength` the corpus figure now matches the live `wc -c` probe — 1,759 vs 1,760 and 1,866 vs 1,866 — which falsifies the merged `…-p3d-p11.md:482` account of that "~2% spread" as a trailing newline plus language variant. It was a unit conflation, and it closes. DECISION-NEEDED #5 (which channel keeps "harness remainder"), #6 (ADR-3's denominator) and #7 (T-SH-B corpus drift, +11.1%) are surfaced with recommendations and NOT picked — §3a. Prior-art: skipped — recalculation of existing measurements plus a unit correction in an existing script; no new dependency, no new packages/ file, no new capability. * fix(s-l): cold-audit round 1 — false numerator provenance, a void share table, and a 1.785 printed as 1.835 A cold fidelity audit on the round-1 head returned REVISE with three MAJOR findings. All three were real; two were invisible to the author. Fixed: 1. §4.1 numerator provenance was FALSE. It read "the five repo-owned files with no `paths:` frontmatter" — that predicate selects 11 rules + CLAUDE.md = 124,529 B, not the 48,671 B actually measured. Seven Class-B/C rules carry no `paths:` key yet reach the model by hook, skill-embed or agent channel. "No paths:" is not "always-on". The numerator is now defined by the channel that produced it (/context Memory Files, 5 project-scoped files). 2. §4.2's whole share table was T-SL-B — the trap this stage exists to prevent, published while the patch self-certified compliance with it. The numerator is the post-S-G set (contains ai-laziness-digest.md, absent at f31fd8c); every denominator was measured on a pre-S-G seat. The table is replaced: the only internally-consistent pairing (pre-S-G/pre-S-G) is published, and post-S-G shares now read UNMEASURED — channel absent. This makes the #6 answer stronger, not weaker: ADR-3 cannot be restated as a percentage until a post-S-G seat is measured, which is a result rather than a preference, and it ships with a named falsifier. 3. §1.2 row 1 paired 7,535 tokens (stdout + command block) with 13,450 (stdout alone) and printed 1.835, which computes to 1.785. Same trap, one table earlier. Numerator corrected to 13,829. Also delivers what the audit flagged as routed-instead-of-done: the #5 naming RULE and the #6 operative restatement are now stated as delivered (with falsifiers), leaving only the term assignment and the denominator choice routed to the operator, per the dispatch instruction. Plus the MINORs: six-file spread with a k-of-n coverage predicate (MEMORY.md is host-side and its state at seat time is unrecoverable); 59,300 labelled as /context's reported total, not the resident head (58,200); the grep block marked a digest rather than passed off as literal stdout; the Messages row noted as excluding the 31-cp prompt that arrives after the reading. §6 no longer self-certifies T-SL-B. It records all three trips as an incident table, naming which were caught cold — because a trap the author declares clean while violating it is worth more as evidence than as a checkbox. Prior-art: skipped — corrections to this stage's own unmerged patch after a cold audit; no new dependency, no new packages/ file, no new capability. * fix(s-l): cold-audit round 2 MINORs — a spec enumeration stronger than the measurement, and four traceability slips Round 2 returned GO (W-1..W-6 clean). These are its five MINORs, all cheap and all correctness/traceability rather than substance: - spec §1.5 annotation said "both hook injects" are counted nowhere, while the patch's own §2.3 concludes /context's `Messages` counts exactly ONE of them. The spec is S-D′'s consumed instrument, so an enumeration there that overstates what was measured is the one MINOR worth fixing on its own merits. The >=15,258 figure it accompanies was already correct. - §2.2's console block was a hand-composed digest wearing `$` prompts. §1.4 got that label in round 1; §2.2 did not. Both figures re-verified (n=69, sum 8,870). - Three `(§1.5)` cross-references pointed at the corpus-drift section after the 641->596 line compression renumbered the band treatment to §1.6. - 13,829 vs the 5-C patch's printed 13,827 for the same delta, previously unremarked: 5-C also nets the seats' 4-char prompt difference and 2-char hook differences. Both round to 1.835. - Supersede anchor cited addendum :60-63; the quoted sentence is at :55-57. Prior-art: skipped — traceability corrections to this stage's own unmerged patch plus one spec wording fix; no new dependency, no new packages/ file, no new capability. * fix(s-l): cold-audit round 3 MINORs — name both seats at the pairing, and stop normalising a merged sign slip Round 3 returned GO (W-1..W-9 clean). Its two MINORs, neither changing a figure: - §3.1's counted/uncounted accounting paired census rows from seat 384ada17 with /context categories from seat 45489086 without naming either — while §3.2's own naming rule demands channel, unit and seat. Both are now named at the pairing, and the reason it is legitimate is stated as a CHECK rather than an assumption: skill_listing measures 26,696 cp identically on both seats, so the payload is seat-invariant across the pair. §2.3 gets the same treatment. - §1.4 rendered the `…-p3d-p11.md:536` site in backticks as `wc -c ÷ 4 B/tok`, but the merged source prints `×`. Division is the correct intent (multiplying bytes by B/token is dimensionally nonsense) and the stated direction only follows under it — but backticks signal verbatim, so quoting the source's own sign and naming the correction is the honest form. Silently normalising another patch's slip is the small version of what this stage exists to catch. Prior-art: skipped — citation-accuracy corrections to this stage's own unmerged patch; no new dependency, no new packages/ file, no new capability. --------- Co-authored-by: Test <test@example.com>
…GUOUS + host-verify contract (#1310) * docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev) Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A + description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1 iteration, external cold fidelity mandatory; Park-4 CC detection = CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park did not fire (live runtime-profiles probe, no substring collision); Park-6 ship the full functional set (create-worktree.sh + worktree-node-modules.sh + link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added. Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3). * docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300). The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores only the env-var NAME in the profile, #4(ii) writes the value to ~/.config/getff/glm.env, and #3 requires the validation ping to run through the created profile. Verified against source: aif resolves the key from its own runtime process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219, :247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) — so glm.env is a dead end as written and #3 is unreachable without dereferencing the value, which #1 forbids. §7b closes it by binding the OUTCOME (value reachable in the aif runtime process env under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure) while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked as a dispatcher-authored completion, not an operator decision — the operator can override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id (:90), both defaults halves, ping through the profile, 10-skills.sh untouched. §4.1 adds the missing host-verify contract — the stage ships a shell helper, an install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host surface destination-environment-verification.md §1 exists for; the umbrella had no contract on any of its six kickoffs (exit 2). Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an ambiguity in an existing stage kickoff against in-repo source citations. --------- Co-authored-by: Test <test@example.com>
… path, bind run-3 blockers (#1319) * docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev) Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A + description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1 iteration, external cold fidelity mandatory; Park-4 CC detection = CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park did not fire (live runtime-profiles probe, no substring collision); Park-6 ship the full functional set (create-worktree.sh + worktree-node-modules.sh + link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added. Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3). * docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300). The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores only the env-var NAME in the profile, #4(ii) writes the value to ~/.config/getff/glm.env, and #3 requires the validation ping to run through the created profile. Verified against source: aif resolves the key from its own runtime process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219, :247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) — so glm.env is a dead end as written and #3 is unreachable without dereferencing the value, which #1 forbids. §7b closes it by binding the OUTCOME (value reachable in the aif runtime process env under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure) while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked as a dispatcher-authored completion, not an operator decision — the operator can override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id (:90), both defaults halves, ping through the profile, 10-skills.sh untouched. §4.1 adds the missing host-verify contract — the stage ships a shell helper, an install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host surface destination-environment-verification.md §1 exists for; the umbrella had no contract on any of its six kickoffs (exit 2). Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an ambiguity in an existing stage kickoff against in-repo source citations. * docs(beta-delivery-ux): S4 kickoff §7d — retract the unreachable host path, bind run-3 blockers §7d.0 retracts the §1 + §7a #1 instruction to re-read `~/code/aif-handoff/packages/api/src/routes/runtimeProfiles.ts` at stage entry. That path is host-only: the agent container mounts `$PROJECTS_DIR` (= `$PROJECTS_HOST_ROOT/projects`), so the repo root holding `packages/api/` sits one level above the mount. The instruction was unexecutable and run 3 guessed instead of parking — same class as state.md §2 decision 14. Replacement is an oracle the container can actually reach: the live aif API. §7d.1 pins two probes (required-field discovery + route-existence discovery) with the dispatcher's own 2026-08-09 outputs recorded for reproduction, not for trust. Run-3 blockers bound as corrections: §7d.2 `runtimeId`+`providerId` required in the create body; §7d.3 the ping route is `POST /runtime-profiles/validate` (the guessed `/<id>/v1/messages` → 404); §7d.4 §7b #1 still undelivered (the helper verifies reachability but never establishes it); §7d.5 companion install is prose in a field `engine.sh:18` early-returns past; §7d.6 (MAJOR) the suite mocks the non-existent endpoint and a body-blind 201, so §4.1 passed over §7d.2/§7d.3 — the stub becomes fail-closed with a paired-negative that fails on the run-3 helper, and `it.fails()`-as-delivery is banned. Adds T-BDU-D: unreachable instructed source → probe, else PARK; never substitute a plausible shape. Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the capability-commit LOC triggers per CLAUDE.md. * docs(beta-delivery-ux): S4 kickoff §7d.1 — name the real base-URL var, and the container trap under it Own cold-QA of #1319 caught two defects in §7d.1. (1) It invented `$AIF`; the helper actually resolves `AIF_URL="${RUNTIME_BRIDGE_AIF_URL:-http://localhost:3009}"` (scripts/getff-glm-onebutton.sh:94), so the probe block now uses that name. (2) The more load-bearing half: that shipped default is unreachable from the agent container. Measured 2026-08-09 from aif-handoff-agent-1 — `http://localhost:3009/runtime-profiles` → curl exit 7 (000), `http://api:3009/runtime-profiles` → 200. `RUNTIME_BRIDGE_AIF_URL` is unset in the container, so an in-container run of the run-3 helper could not have reached aif at all, which is consistent with the suite mocking every call (§7d.6). §7d.1 now instructs the export for probing, explicitly forbids changing the shipped consumer default, and §4 item 1 must quote the base URL alongside any live end-to-end transcript. Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the capability-commit LOC triggers per CLAUDE.md. * docs(beta-delivery-ux): S4 kickoff §7d.3/§7d.4 — fire my own falsifier; the ping is two halves §7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through `POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a reachability check. The dispatcher fired that falsifier itself rather than shipping the trap: - for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey` and `baseUrl` are non-empty — no network call (aif-handoff packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the Qwen3.8-Max-Preview profile returned ok:true sub-second. - `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider. So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves — `/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is stated as dispatcher-authored and operator-overridable, matching §7b. §7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and `resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env (packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure. Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the capability-commit LOC triggers per CLAUDE.md. --------- Co-authored-by: Test <test@example.com>
…, name frozen against the live registry (#1336) * docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev) Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A + description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1 iteration, external cold fidelity mandatory; Park-4 CC detection = CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park did not fire (live runtime-profiles probe, no substring collision); Park-6 ship the full functional set (create-worktree.sh + worktree-node-modules.sh + link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added. Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3). * docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300). The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores only the env-var NAME in the profile, #4(ii) writes the value to ~/.config/getff/glm.env, and #3 requires the validation ping to run through the created profile. Verified against source: aif resolves the key from its own runtime process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219, :247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) — so glm.env is a dead end as written and #3 is unreachable without dereferencing the value, which #1 forbids. §7b closes it by binding the OUTCOME (value reachable in the aif runtime process env under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure) while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked as a dispatcher-authored completion, not an operator decision — the operator can override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id (:90), both defaults halves, ping through the profile, 10-skills.sh untouched. §4.1 adds the missing host-verify contract — the stage ships a shell helper, an install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host surface destination-environment-verification.md §1 exists for; the umbrella had no contract on any of its six kickoffs (exit 2). Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an ambiguity in an existing stage kickoff against in-repo source citations. * docs(beta-delivery-ux): S4 kickoff §7d — retract the unreachable host path, bind run-3 blockers §7d.0 retracts the §1 + §7a #1 instruction to re-read `~/code/aif-handoff/packages/api/src/routes/runtimeProfiles.ts` at stage entry. That path is host-only: the agent container mounts `$PROJECTS_DIR` (= `$PROJECTS_HOST_ROOT/projects`), so the repo root holding `packages/api/` sits one level above the mount. The instruction was unexecutable and run 3 guessed instead of parking — same class as state.md §2 decision 14. Replacement is an oracle the container can actually reach: the live aif API. §7d.1 pins two probes (required-field discovery + route-existence discovery) with the dispatcher's own 2026-08-09 outputs recorded for reproduction, not for trust. Run-3 blockers bound as corrections: §7d.2 `runtimeId`+`providerId` required in the create body; §7d.3 the ping route is `POST /runtime-profiles/validate` (the guessed `/<id>/v1/messages` → 404); §7d.4 §7b #1 still undelivered (the helper verifies reachability but never establishes it); §7d.5 companion install is prose in a field `engine.sh:18` early-returns past; §7d.6 (MAJOR) the suite mocks the non-existent endpoint and a body-blind 201, so §4.1 passed over §7d.2/§7d.3 — the stub becomes fail-closed with a paired-negative that fails on the run-3 helper, and `it.fails()`-as-delivery is banned. Adds T-BDU-D: unreachable instructed source → probe, else PARK; never substitute a plausible shape. Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the capability-commit LOC triggers per CLAUDE.md. * docs(beta-delivery-ux): S4 kickoff §7d.1 — name the real base-URL var, and the container trap under it Own cold-QA of #1319 caught two defects in §7d.1. (1) It invented `$AIF`; the helper actually resolves `AIF_URL="${RUNTIME_BRIDGE_AIF_URL:-http://localhost:3009}"` (scripts/getff-glm-onebutton.sh:94), so the probe block now uses that name. (2) The more load-bearing half: that shipped default is unreachable from the agent container. Measured 2026-08-09 from aif-handoff-agent-1 — `http://localhost:3009/runtime-profiles` → curl exit 7 (000), `http://api:3009/runtime-profiles` → 200. `RUNTIME_BRIDGE_AIF_URL` is unset in the container, so an in-container run of the run-3 helper could not have reached aif at all, which is consistent with the suite mocking every call (§7d.6). §7d.1 now instructs the export for probing, explicitly forbids changing the shipped consumer default, and §4 item 1 must quote the base URL alongside any live end-to-end transcript. Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the capability-commit LOC triggers per CLAUDE.md. * docs(beta-delivery-ux): S4 kickoff §7d.3/§7d.4 — fire my own falsifier; the ping is two halves §7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through `POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a reachability check. The dispatcher fired that falsifier itself rather than shipping the trap: - for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey` and `baseUrl` are non-empty — no network call (aif-handoff packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the Qwen3.8-Max-Preview profile returned ok:true sub-second. - `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider. So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves — `/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is stated as dispatcher-authored and operator-overridable, matching §7b. §7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and `resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env (packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure. Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the capability-commit LOC triggers per CLAUDE.md. * docs(beta-delivery-ux): R1 stage kickoff — npm release mechanics (A6) R1 is the last unstarted stage of the umbrella (S1-S5 all merged). This is its dispatch input, scoped from the spec (§4 A6 `:278-284`, §11 F-C' `:467-468`) and the binding input `launch-preannounce-track/s6-u10-handoff.md`. Three things the kickoff decides, because a worker cannot: 1. Dispatchability (§0.1) — R1 IS aif-dispatchable. `private: true` STAYS on `packages/core/package.json` for the whole stage; npm refuses to publish a private package, so the publish guard is mechanical rather than a reminder (attention-is-not-a-mechanism §1). Verified `npm pack --dry-run` works on a private package, so the tarball loop runs with the guard armed. 2. Name architecture (§0.2) — probed the live registry, not the spec's prose: unscoped `getff` ALREADY EXISTS at 0.0.1, published 2026-06-23 by the maintainer as a reservation stub. So the registry-side freeze is done, 0.0.1 is spent as a version, and `@getff` SCOPE ownership stays unproven (a 404 on `@getff/core` proves the package is free, not the scope) — that is an entry probe with a STOP attached. 3. Scope boundary (§0.3) — R1 does NOT execute the `@rules-as-tests/*` rename. Measured blast radius 33 tracked files / 55 occurrences; umbrella §3 puts the rename in U9 (post-announce), and the acceptance gate is name-independent because `npm i <tarball>` installs by path. Also recorded: two gates of the binding input's §0 are SUPERSEDED by the 2026-07-23 amendment (U9 no longer the rename window; the private-drop ordering), and the honest gap R1 does not close — U10's `npx getff init` gate needs a package with `bin: getff`, which does not exist in this repo and which A6 does not ask R1 to build. Host-verification contract declared (no opt-out): the new tarball cell plus the existing pnpm start cell, which is the file-copy fallback the §4 rollback doctrine requires to keep working. `scripts/host-verify.sh --list` parses both, exit 0. Prior-art: skipped — dispatch-input authoring only; a markdown kickoff adds no capability, no dependency, and no code module (CLAUDE.md doc-file carve-out). --------- Co-authored-by: Test <test@example.com>
…T14 (#1565) The entry-probe table pinned the landing container base at `733197e`. That is the pre-PR-#4 commit, i.e. exactly the staleness this session verified and had already seen fixed before dispatching: the base clone, the container, and `gh api repos/artyhoo/getff-landing/commits/main` all read `b65ff4b534`. So the kickoff text and the tree the worker actually gets disagreed at row 1 — the one row whose job is to tell the worker what a correct starting state looks like, and which instructs a STOP-and-report on a base anomaly. Best case the worker distrusts row 1 from its first probe; worst case it reports a false anomaly. Pin corrected, with the three surfaces the SHA was verified against named inline so the next reader can re-check rather than re-trust. Two additions in the same pass, both aimed at gate row 4 (the physical `/docs/<slug>.md` twin), which is where an assumed route is most likely to survive as prose: - T10 — enumerate `ls -R out` and paste the route inventory BEFORE asserting which routes exist. - T14 — coverage bounds the verdict: six rows passing with two skipped is «coverage insufficient to conclude», not leg A green. The report must say which ran. T12 now also names the two symbols that shorten the docs search (the static search client under `fumadocs-core/search/client`, and the llms-text helper commonly shown as `getLLMText`) while explicitly requiring both to be confirmed against current docs — a rename is itself a doc-drift finding, and this line must not become the memory it warns against. The base-pin defect and the T10/T14 gap were both caught by session zen-panini-3ac9cf-c3 reviewing the merged kickoff after standing down from this stage. Verification: principle 12 green (9/9); markdownlint 0 errors; prettier clean; extractProfileHint(<this file>) -> undefined. Prior-art: skipped -- corrections inside one dispatch-input document; no new dependency, no code change, no new capability under packages/. Co-authored-by: Test <test@example.com>
…healthy worktree (#1567) Entry-probe row 1 said «if your worktree is dirty at start … STOP and report». Measured in this stage's own live worktree, a fresh aif task worktree is never clean: it carries exactly two untracked entries that aif itself puts there. $ docker exec aif-handoff-agent-1 git -C <task worktree> status --short ?? .ai-factory/ ?? AGENTS.md $ docker exec aif-handoff-agent-1 git -C <task worktree> log --oneline -1 b65ff4b Merge pull request #4 from artyhoo/feature/bs-pre-smoke-cf8942 So the literal instruction fires a STOP on the worker's very first probe, on a perfectly healthy tree. On a stage that forbids fix-forward and whose whole output is a STOP-or-GO verdict, a spurious STOP is not a harmless false alarm — it is indistinguishable in the report from the real thing this stage exists to detect. The clause the row was reaching for is real but narrower: it is the BASE CLONE that is unsafe, and the isolation actually works — the base clone shows `M package.json` plus ~16 untracked installer entries, and none of that reaches the per-task worktree, which is cut clean from HEAD. Row 1 now says so: the two-entry untracked set is expected and is not a STOP; STOP on a modified or deleted TRACKED file, or on finding yourself in `/home/www/getff-landing` itself; and in every case stage only paths under `smoke/`, never `git add -A`. Found by inspecting the live worktree at `planning` rather than from the kickoff text — the same class as the stale base pin in #1565, and the second time this stage shipped a row 1 that disagreed with the tree the worker actually gets. Row 1 is the one row a worker acts on before it has any other context, which is exactly why it keeps being worth measuring against reality instead of reading. Verification: principle 12 green (9/9); markdownlint 0 errors; prettier clean. Prior-art: skipped -- correction inside one dispatch-input document; no new dependency, no code change, no new capability under packages/. Co-authored-by: Test <test@example.com>
Summary
framework-self-detectCI gate, and AIF skill-context override emission (touchpoint 4 closed early instead of deferring to Phase 11).1555fb1→ re-review final GO. Full audit trail in docs/meta-factory/retros/phase-4.md versioning entries (score evolution 9 → 7 → 8 traceable).PHASE-4-PROMPT.mdgreen; self-audit 24/24, core 95/95, preset 38/38.Reviewer trail (last 4 commits)
6a2b5ffretro + initial GO verdictf0c4e8cpost-review calibration (M1: vacuous L1 (a) flagged)1555fb1fix(setup): husky templates path →packages/core/templates/shared/(closes M1)a0af459post-fix re-calibration (L1 (a) non-vacuous, score 7 → 8)Test plan
[dry-run] would: cp/chmodlines)framework-self-detectpasses locally (snapshot frozen)--no-verify, no force push, no destructive opsPre-existing typecheck errors (NOT Phase 4 regressions)
Documented in retro Open #5; all exist on
main:packages/core:probes/audit-r4.ts(ts-morph missing),render/render-rules.ts(ajv default constructor)packages/preset-next-15-canonical/templates:playwright.config.ts(@playwright/test),vitest.config.ts(@vitejs/plugin-react + poolOptions overload)Flagged for Phase 5 entry hygiene; out of scope here.
Phase 5 entry open questions (in retro)