Skip to content

feat(meta-factory): Phase 5+6 — L2 Research Agent + L3 Synthesizer Path A (deterministic v1) - #5

Merged
artyhoo merged 18 commits into
mainfrom
chore/phase-5-research-agent
May 8, 2026
Merged

feat(meta-factory): Phase 5+6 — L2 Research Agent + L3 Synthesizer Path A (deterministic v1)#5
artyhoo merged 18 commits into
mainfrom
chore/phase-5-research-agent

Conversation

@artyhoo

@artyhoo artyhoo commented May 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Phase 5 — Layer 2 Research Agent v1 (8 commits): curated research store + semver-aware loader + source allowlist + diff-mode + symbolic drift detection on 3 canonical own-doc sources. Closes open-questions.md §13.7 symbolic-v1 first half. Drift detection surfaced and closed real divergence in skills/rules-as-tests/references/{overview,ai-traps}.md on first run.
  • Phase 6 — Layer 3 Synthesizer Path A v1 (9 commits): types + 3 curated recipes + pure synthesize + emitter + CLI + self-app snapshot (own repo + next-16 fixture). Path A constraint held — only configuration of existing plugins / preset rules, no AST gen.
  • 2 new CI gates: framework-self-research + framework-self-synth (mirror existing framework-self-detect). All 3 self-application layers now have CI invariant.

Architectural pivot — deterministic v1 (no LLM): mirrors Phase 4 «deterministic bridge over AIF» playbook. Project tenet «documents lie; tests don't» demands snapshot-testable v1; LLM-driven extension layer = strict superset for v2 trigger (first real consumer or Phase 8 acceptance test). Native Anthropic SDK web_search_20250305 allowed_domains documented as v2 contract (Phase 5 research §3.3).

Phase 5/6/7 reordering note (please read before reviewing §6)

EXECUTION-PLAN §6 was reordered:

  • Original (v0.1.x): Phase 5 = L4 Validator, Phase 6 = L2 Research, Phase 7 = L3 + L5
  • Current (v0.1.2+): Phase 5 = L2, Phase 6 = L3 Path A only, Phase 7+ = L4 + L5

Rationale: linear data flow 0→1→2→3→4→5 per architecture.md §2.1. L4 Validator gates synthesized output — without L2/L3 there is nothing beyond Phase 2 manifest meta-tests for L4 to validate. L5 Installer needs L3 output to install.

The Phase descriptions in EXECUTION-PLAN.md §6 retain their original headings for diff hygiene; a header-level note added to §6 documents the mapping. Read via the mapping, not the headings.

Test plan

  • core 168/168 (124 detector + 29 research + 15 synthesizer)
  • preset-next-15-canonical 38/38
  • principles 24/24 (make self-audit)
  • typecheck clean — 3 workspaces
  • actionlint + zizmor clean
  • L2 self-application invariants (a) drift detection, (b) snapshot stability — both closed
  • L3 self-application invariant (a) round-trip snapshot — closed point-in-time on own repo + next-16 fixture
  • CI gate framework-self-research: zero drift across 3 canonical sources
  • CI gate framework-self-synth: own-repo + fixture snapshots stable

Acknowledged trade-offs (NOT regressions)

  • L3 invariant (b) canonical regen ≤5% diff vs packages/preset-next-15-canonical/ — explicitly deferred to Phase 6 v2 / Phase 7. Split-point: requires recipe coverage of all preset-next-15 rules (R12, R14, R20). Trigger to close: Phase 8 acceptance test (Next 15→16) or explicit Phase 7 scope expansion. Self-app score 8/10 reflects this ‑1.
  • EXECUTION-PLAN §6 headings not renamed inline — only top-level note added. Trade-off: diff hygiene > inline rename. Documented in retro.

Retros

Phase 7 entry triggers

7 open questions surfaced in retros/phase-6.md, key one: 6-gate L4 Validator triage (Schema / rule-tester / mutation / tautology / two-AI / cross-rule conflict) — which ship Path A v1 vs deferred.

🤖 Generated with Claude Code

artyhoo added 18 commits May 8, 2026 13:00
…rompt

Layer 2 Research Agent v1 entry research per EXECUTION-PLAN §5.5:
6-capability matrix × AIF/LangGraph/Anthropic SDK context7 queries →
5 build + 1 strong reuse (skill-context sink) + 2 transitive reuses.

Architectural pivot: v1 ships deterministic-curated (no LLM), mirroring
Phase 4 detector's "deterministic bridge" playbook. LLM-driven research
deferred to v2 trigger (first real consumer or Phase 8 acceptance test).

Phase 5/6 reordering vs EXECUTION-PLAN §6: L2 → Phase 5, L3 → Phase 6,
L4 Validator → Phase 7+. Rationale: validator gates synthesized output;
without L2/L3 there is nothing beyond Phase 2 manifest meta-tests to gate.

Closes [open-questions.md §13.7] symbolic drift detection v1 in Task 5.
ResearchPlan = top-level Layer 2 output: framework + version + patterns[] +
missing[] + drift. ResearchEntry per pattern with provenance + extras escape
hatch (research §5 risk mitigation). DriftReport populated only in --self mode.

JSON Schema mirrors TS types 1:1 for runtime Ajv validation in load.ts.
…ware loader

Store layout: store/<framework>/<major>.x/<patternId>.json + store/shared/<patternId>.json.
Loader resolves: exact-major → previous-major → shared/, sorted by id, skips missing
patterns rather than throwing.

6 entries hand-authored: next/16.x/{nextjs-app-router,nextjs-pages-router,
react-server-components}, next/15.x/nextjs-app-router (diff-mode test pair),
shared/{tailwind-v3-config,tailwind-v4-css-tokens}.

Each entry validated against research-plan.schema.json#/definitions/ResearchEntry
on load via Ajv — malformed throws ResearchEntryError loudly per project tenet.

10 tests: 7 loader scenarios (exact, semver coerce, version variant, shared
fallback, missing skip, mixed sort, framework=null) + 3 schema invariants.
ALLOWED_SOURCES = static map of allowlist key → trusted hostnames.
validateProvenance enforces:
- key exists in registry
- URL parseable
- https-only (no http downgrade)
- hostname matches a registered host (exact or subdomain)

Wired into load.ts: every loaded entry's provenance items validate on load;
failure throws ResearchEntryError loudly (no silent skip).

7 tests paired positive/negative: exact-host, subdomain, key-mismatch host,
unknown key, malformed URL, http rejected, registry exposed.
diffPlans(a, b) → ResearchDelta { added, removed, modified, frameworkChanged,
versionChanged }. Granularity = entry.id as join key; modified detection via
stable JSON hash so key-order changes alone do not produce false churn.

7 tests: identical empty, added, removed, modified by content, key-order
stable (negative — no false modify), framework/version transitions, mixed
delta with stable sort.
….7 v1)

detectDrift(repoRoot) scans 3 canonical own-doc sources (skills/SKILL.md,
references/overview.md, references/ai-traps.md) for two drift kinds:
- term-presence: principle missing in ≥2 sources
- modal-verb: principle has divergent MUST/SHOULD/MAY strength across sources
  (same-line modal attribution to avoid neighbour-principle false positives)

5 canonical principles tracked: 5-layer framework, AST over grep, paired
negative tests, mutation testing, two-AI review.

Real-repo canary surfaced one legitimate drift on first run: 'paired negative
tests' was only documented in skills/SKILL.md. Closed by adding bullet to
overview.md Layer 2 + new anti-pattern #10 (tautological tests) to ai-traps.md
— substantive additions, not stuffing. Zero mismatches at HEAD.

Operationalizes open-questions §13.7 first half (symbolic). Behavioral and
embedding-based v2/v3 deferred per phase-5-research §6 watch-list.

4 tests: no-drift fixture (zero mismatches), with-drift fixture (term-presence
+ modal-verb both fire), missing-source error path, real-repo canary.
index.ts exports only research(detection: DetectionResult) → ResearchPlan
plus types, enforcing Planner-Executor module-surface contract: no file-write
helpers leak through this public boundary; L3 in Phase 6 imports the schema,
not internals.

cli.ts (51 LOC) supports four modes:
- <projectRoot> default → ResearchPlan (drift: null)
- --self → ResearchPlan with drift populated for own repo
- --diff <a> <b> → ResearchDelta JSON
- --pattern <id> → filter ResearchPlan.patterns to one entry

package.json: bin rules-as-tests-research, exports ./research, script research.
Smoke-tested: --self emits empty drift on root repo (post-Task-5 doc fix);
--diff identical plans → empty delta.
… snapshot

framework-self-research CI job mirrors framework-self-detect pattern:
- step 1: diff research --self output against expected-self-research.json
  (L2 invariant b: snapshot stability)
- step 2: assert .drift.mismatches.length == 0 across the 3 canonical
  self-application sources (L2 invariant a: drift-free at HEAD)

Frozen snapshot captures own repo's L2 output: framework=null (ts-server),
patterns=[], missing=5 known packages, drift={zero mismatches}.

snapshot.test.ts mirrors detector/snapshot.test.ts pattern for local CI parity.
actionlint + zizmor clean. Full regression: principles 24/24, core 153/153,
preset 38/38, all 3 workspaces typecheck clean.
Verification: 9/9 probes green; principles 24/24, core 153/153, preset 38/38,
typecheck clean, actionlint+zizmor clean, both L2 invariants closed.

Self-application score 9/10 — drift detection non-vacuous (caught + closed
real drift in skills/ on first run), snapshot point-in-time stable.

Time vs plan: <<1h wall-clock vs 6h orchestrator-path estimate (Opus 4.7
burn-mode direct execution; mirrors Phase 4 compression pattern).

7 open questions surfaced for Phase 6 entry; key one — L3 scope decision
(manifest entries vs ESLint config vs probes vs RULES.md fragments — all of
above for v1 or subset). EXECUTION-PLAN §6 numbering swap (L2/L3/L4
reordering) tracked as closing item for Phase 6 retro.
…rompt

Layer 3 Synthesizer Path A v1 entry research per EXECUTION-PLAN §5.5:
8-capability matrix × AIF aif-implement/aif-plan/aif-loop + ESLint flat
config + typescript-eslint queries → 5 build + 2 strong reuse (manifest
schema for SynthesizedRule shape, transitive deps) + 1 deferral (canonical
regen ≤5% diff to Phase 6 v2 / Phase 7).

Architectural pivot: v1 ships deterministic-curated (no LLM), mirroring
Phase 5 playbook. 3 recipes hand-authored (one per Next 16 pattern with a
Phase 5 ResearchEntry). Tailwind recipes deferred — no canonical preset to
anchor against.

Phase 6 = L3 only (without L5 Installer); EXECUTION-PLAN §6 numbering swap
covering Phase 5/6/7 reordering tracked as Phase 6 retro closing edit.
SynthesizedRule shape intentionally mirrors rules-manifest.schema.json
RuleEntry semantics — generated rules indistinguishable from manual ones
at validation time (Phase 6 reuse decision 6.7).

ID pattern '^G[0-9]+$' marks generated rules visually distinct from manual
R-rule corpus (R1-R20, IR1-IR6).

ManifestCheck union mirrors core's existing Check shape; provenance back-
references Phase 5 ResearchPlan source so synthesized rules track research
lineage.
…terns

Each recipe maps Phase 5 ResearchEntry pattern → SynthesizedRule template:
- nextjs-app-router → no-restricted-imports forbidding 'next/router' under app/
- nextjs-pages-router → manual migration check (Pages Router removed in Next 16)
- react-server-components → enable existing 'rules-as-tests/no-server-imports-in-client'

Path A constraint: configuration of existing plugins / preset rules only.
Recipe 3 references the preset's already-shipped custom rule (no AST gen).
Recipe 2 is manual — codemod-shaped migrations are not lintable per file.

Hand-authored, committed; mirrors Phase 5 store posture.
synthesize(plan: ResearchPlan) → SynthesisPlan. Recipe lookup by entry.id;
framework filter via recipe.appliesTo; sequential G1, G2, ... IDs in input
order. Skips patterns without matching recipe (Tailwind under Next plan)
and skips all when plan.framework=null (own-repo case).

Output validated against synthesis-plan.schema.json on every call;
malformed throws SynthesisPlanError loudly.

8 tests: empty plan, single pattern, sequential IDs, missing recipe skip,
framework-null skip, framework-mismatch skip, rulesMd id substitution,
eslint config merge.
emit(plan, outputDir) writes 3 files: rules-manifest-additions.json (keyed
by rule id), RULES-additions.md (header + plan.rulesMd), eslint-rules-
snippet.json (plan.eslintConfigSnippet verbatim).

Throws EmitError when outputDir is missing or not a directory — no
auto-mkdir; caller's responsibility (avoids accidentally creating paths
on typo'd CLI arg).

Idempotent: same plan + same outputDir → byte-identical output across
repeated calls.

NOT exported from index.ts (Planner-Executor segregation per Phase 6
research §3.4): only CLI imports emit.ts directly.

5 tests: empty plan → 3 files with empty bodies, populated plan with G1
rule, idempotence (byte equality across repeated emit), missing dir,
non-directory path.
index.ts exports only synthesize() + types per Planner-Executor contract;
emit.ts is direct-path-imported by cli.ts, never via public boundary.

cli.ts (52 LOC) supports four modes:
- <projectRoot> default → detect → research → synth → JSON
- --from-research <path> → load ResearchPlan from disk → synth → JSON
- --output <dir> → also call emit() to write 3 artifact files
- --pattern <id> → filter SynthesisPlan.rules to research.entryId === id

package.json: bin rules-as-tests-synth, exports ./synthesizer, script synth.
Smoke-tested: own-repo CLI emits framework=null + 0 rules (no patterns;
synth correctly skips when no recipes apply).
next-16-fixture/ stack: package.json (next@^16.0.1 + react@^19), app/page.tsx
(triggers nextjs-app-router pattern), pages/index.tsx (triggers nextjs-pages-
router), src/widget.tsx with 'use client' (triggers react-server-components).
All 3 Phase 6 recipes apply → 3-rule SynthesisPlan with G1/G2/G3.

Frozen snapshots:
- expected-self-synth.json: own repo, ts-server, framework=null, rules=[]
- expected-fixture-synth.json: next-16 fixture, 3 rules, full check+examples+research

Two snapshot tests verify byte-equality on round-trip detect→research→synth.

L3 invariant a (round-trip snapshot stability) closed point-in-time. L3
invariant b (canonical regen ≤5% diff vs preset-next-15-canonical) deferred
with documented split-point in retros/phase-6.md.
Mirror of framework-self-detect + framework-self-research patterns:
- step 1: synth on own repo, diff against expected-self-synth.json
  (L3 invariant a: empty plan baseline for ts-server)
- step 2: synth on packages/core/synthesizer/fixtures/next-16-fixture,
  diff against expected-fixture-synth.json (3-rule plan with all 3
  recipes firing)

Both diffs use jq -S sort + diff for deterministic comparison.

actionlint + zizmor clean.
…ing edit

Verification: 9/9 probes green; principles 24/24, core 168/168 (124 detector
+ 29 research + 15 synthesizer), preset 38/38, typecheck 3 workspaces clean,
actionlint+zizmor clean. L3 invariant (a) round-trip snapshot stable; (b)
canonical regen ≤5% diff explicitly deferred to Phase 6 v2 / Phase 7 with
documented split-point.

Self-application score 8/10 — round-trip stable; canonical regen deferral
acknowledged as debt, not architectural failure.

Time vs plan: <<1h wall-clock vs 6h orchestrator-path estimate. Same Opus
4.7 burn-mode compression as Phase 4 + Phase 5.

EXECUTION-PLAN.md §6 closing edit: discharge of Phase 5/6/7 reordering
tracked across retros 5 and 6 — Phase 5 = L2 Research, Phase 6 = L3 Path A,
Phase 7 = L4 Validator + L5 Installer. Phase headings retained for diff
hygiene; mapping documented at top of §6.

7 open questions surfaced for Phase 7 entry — key one: 6-gate L4 Validator
triage (which gates ship Path A v1 vs deferred).
@artyhoo
artyhoo merged commit b94ef56 into main May 8, 2026
27 checks passed
artyhoo added a commit that referenced this pull request May 8, 2026
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).
artyhoo added a commit that referenced this pull request May 8, 2026
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).
artyhoo added a commit that referenced this pull request May 8, 2026
Verification artifact for Phase 8.8.1 hard constraint #5 «apply principle
to itself». Walks the original Phase 9 entry §4.A1 lookup through
.claude/rules/phase-research-coverage.md §1 checklist; tests whether the
rule, had it been active before T3.2 close, would have caught the AIF
/aif-evolve + Oh My ClaudeCode coverage gap recorded in f92f60b.

Result: 5/6 §1 items independently catch the gap (§1.1 own-stack /
§1.2 category / §1.3 semantic-distance / §1.4 adversarial / §1.5
prompt-list ≠ complete). §1.6 (trigger sweep) is N/A — it covers
armed-not-fired triggers, not missed candidates within a fired trigger.

§2 self-reflection prompts also pass: §2.2 specifically would have
surfaced that Phase 5 / Phase 6 actively engaged AIF /aif-evolve and
documented its post-hoc nature — Phase 9 entry had prior-knowledge
available but did not consult it (a prior-research-not-reused regression
the prompt would have caught at retro time).

§13.16 self-trigger transitions from PENDING T7 → STILL ARMED
(observed-zero-FP-on-self-application; foundational datapoint, not yet
3 sessions for retire path).

Methodology validated. Rule ships GO, not REVISE.

Prior-art: skipped — docs-only audit-trail document for self-application verification, no new capability surface or explicit dependency added (per CLAUDE.md capability-commit definition).
artyhoo added a commit that referenced this pull request May 13, 2026
…ale evidence

#38 CodeRabbit: rationale point (d) «Component A+B covers 3/4 incidents» was
stale — PR #51 is incident #5; Component A did NOT catch quantifier
confabulation. Struck in-place with 2026-05-13 note. Trigger column updated.

#41 Danger JS: ADOPT. Strongest production-grade analog for machine-verifiable
PR-body validation. Direct problem-class match (same domain, paradigm). SSOT
#38 deterministic vacuum filled. Used by React Native, Yarn, ESLint, Apollo.
dangerfile.ts pattern maps to §1.7 schema check (Mechanism C). Verdict reversed
from initial DEFER after build-vs-reuse principle challenge — DEFER was
path-of-least-resistance rationalisation.

Prior-art: prior-art-evaluations.md#41 (Danger JS, verdict ADOPT — production analog for machine-verifiable PR body validation; build-vs-reuse properly applied after maintainer challenge 2026-05-13)
artyhoo added a commit that referenced this pull request May 13, 2026
…ale evidence

#38 CodeRabbit: rationale point (d) «Component A+B covers 3/4 incidents» was
stale — PR #51 is incident #5; Component A did NOT catch quantifier
confabulation. Struck in-place with 2026-05-13 note. Trigger column updated.

#41 Danger JS: ADOPT. Strongest production-grade analog for machine-verifiable
PR-body validation. Direct problem-class match (same domain, paradigm). SSOT
#38 deterministic vacuum filled. Used by React Native, Yarn, ESLint, Apollo.
dangerfile.ts pattern maps to §1.7 schema check (Mechanism C). Verdict reversed
from initial DEFER after build-vs-reuse principle challenge — DEFER was
path-of-least-resistance rationalisation.

Prior-art: prior-art-evaluations.md#41 (Danger JS, verdict ADOPT — production analog for machine-verifiable PR body validation; build-vs-reuse properly applied after maintainer challenge 2026-05-13)
artyhoo added a commit that referenced this pull request May 21, 2026
…self-audit §11.1 interim) (#96)

Implements Option (ii) — the E-checklist Stop-gate interim from the merged
autonomous-self-audit R-phase (#95 §10.5/§11.1). Extends the live Stop hook
with a deterministic scan that enumerates factual-claim shapes (numeric
counts, file:line citations, negative-existence) in the last assistant turn
and appends an item-specific re-verify instruction — targeting the
at-write-time factual class (incidents #1 "4+ files", #5 ":30", #2
negative-existence) that the generic recap nudge does not force. Fires on
claim-PRESENCE regardless of turn length (covers short turns the long_text
gate skips). No LLM, no external call (no-paid-llm-in-ci). Honest limit:
raises salience, does not structurally force compliance.

Also: portable last-assistant-line extraction (grep|tail, was BSD-only
tail -r) so the companion test can run on Linux/CI. New behaviour test
tests/hooks/eot-claim-scan.test.sh (6 sub-tests incl. mutation) registered
in audit-self.yml — the Q-D recursive backstop: the drift-catcher hook is
itself caught by a test if it silently breaks.

Prior-art: prior-art-evaluations.md#20 (Claude Code hooks API, ADOPT) — extends existing Stop-hook substrate, no new surface. Agent Verifier (REFERENCE/ADAPT per #95 research-patch §5.1) — session-executed verifier pattern; this auto-fires it at the AI's own claims. No new dependency.
artyhoo added a commit that referenced this pull request May 21, 2026
…(N2 #5, Commit A) (#105)

ADAPT of Superpowers' «NO SKILL WITHOUT A FAILING TEST FIRST» — idea only, no
dependency (substrate-pure per DECISION=C). Design-then-test split mirroring
11-build-first-reuse-default.design.md (Commit A = design; Commit B = test ≤2wk).

Honest scope correction: this is a NEW principle (slot 15), not an extension of
principle 02 — principle 02 checks rules-manifest.json code-rule entries
(examples.bad/good), which SKILL.md does not have. The paired-negative idea is
re-expressed against the skill artifact as a documented failure-without-skill
block. Scope = in-repo skills only (.claude/skills/* + skills/*); grandfather
existing 5 with a 2026-05-21 cutoff (principle-09 precedent) so CI stays green
and the requirement is forward-going. One convention choice (marker form)
surfaced for maintainer at Commit B per reviewer-discipline.

No behaviour change; no test added; CI untouched.

Prior-art: skipped — design-doc markdown, no new capability (no dep, no
executable code; companion test deferred to Commit B).
artyhoo added a commit that referenced this pull request May 21, 2026
…ommit B) (#112)

Ships the executable test for principle 15 per its design sketch (Commit A, #105).
Each in-scope SKILL.md not in the EXEMPT_SKILLS allowlist must carry a body-section
paired-negative block (`## Without this skill` + `## With this skill`, both non-trivial
and differing — anti-tautology, mirroring principle 02). Grandfather = explicit
EXEMPT_SKILLS allowlist (mirrors principle 09's EXEMPT_PATTERNS mechanism, NOT a date —
the design's "date cutoff" wording is corrected to "allowlist" in §3/§5/§6/§7/§10).
Self-test: positive + mutation 1a (no Without) + 1b (no With) + 2 (tautology) +
3 (exemption load-bearing). All 5 current skills grandfathered → CI stays green.

ADAPT of Superpowers' «NO SKILL WITHOUT A FAILING TEST» (idea, no dependency;
substrate-pure). SSOT #55 registered in this commit (#54 taken by Aider run_cmd via #110).

§1.7 forward-check: principle 15 complies with build-first-reuse-default (ADAPT verdict,
SSOT #55, zero Superpowers dep — substrate-pure per DECISION=C); no-paid-llm-in-ci (pure
structural parse, no LLM); doc-authority-hierarchy (companion design.md carries header at
packages/core/principles/15-skill-paired-negative.design.md:4); reviewer-discipline
(marker form was maintainer-decided, not executor-picked). Mirrors principle 02's
anti-tautology at packages/core/principles/02-paired-negative-test.test.ts:80.
§1.7 backward-check: new rule scope = in-repo SKILL.md (.claude/skills/* + skills/*).
Complete sweep satisfied by grandfather — all 5 current skills in EXEMPT_SKILLS at
packages/core/principles/15-skill-paired-negative.test.ts:34; the exemption itself
carries the load-bearing mutation-3 self-test at
packages/core/principles/15-skill-paired-negative.test.ts:197 (proves the allowlist is
load-bearing — same headerless skill passes IFF its path is in EXEMPT_SKILLS). No skill
forced to change.

Prior-art: prior-art-evaluations.md#55 (Superpowers TDD-for-Skills, verdict ADAPT — idea re-expressed for the SKILL.md artifact as principle 15; no dependency, substrate-pure).
@artyhoo
artyhoo deleted the chore/phase-5-research-agent branch May 22, 2026 18:09
artyhoo added a commit that referenced this pull request Jul 2, 2026
…solved) + implementation plan (#849)

* docs(kickoff): rule-research-trust-tiers — tiered registry-derived docs-trust to replace hardcoded allowlist

R-phase inlined: 5-thread deep-research 2026-07-02 (registry-metadata trust anchoring incl. abuse evidence, TUF/cargo-vet/TOFU vocabulary, 2025-26 docs-channel injection incidents EchoLeak/Copilot-CVE/lethal-trifecta, ContextCrush context7 incident, allowed_domains semantics survey). Design: Tier 0 builtin / Tier 1 auto-derived from LOCAL installed-package metadata scope-locked to the same package / Tier 2 cargo-vet-style committed ack records; offline factory preserved; Path B re-tightening trigger; 4 DECISION-NEEDED for maintainer.

Prior-art: prior-art-evaluations.md#5 (allowed_domains, trigger now fired) + kickoff §3 external table (10 candidates with verdicts); SSOT entries to be appended per stage S-obligations

* docs(kickoff): §9 maintainer direction — generate-first delivery, presets demoted to snapshot-oracles

Records the 2026-07-02 maintainer directive: generation is the primary rule-delivery path for ALL stacks; presets become (1) regression oracles (EXECUTION-PLAN §1 diff-budget criterion becomes shipped CI), (2) explicitly-dated cold-start fallbacks, (3) authoring examples. Sequencing: trust-tiers S1-S2 first (generation for arbitrary stacks requires Tier 1/2 provenance), then a generate-first-delivery umbrella with its own R-phase. DECISION-NEEDED #5 added.

Prior-art: skipped — kickoff §9 direction note, no new capability

* docs(kickoff): rule-research-trust-tiers — hardened kickoff, all 7 DNs resolved (deep-research round 2) + implementation plan
artyhoo added a commit that referenced this pull request Jul 5, 2026
/#921) (#923)

## Summary

A real `git push` from a consumer install is hard-blocked by 8 checks in `packages/core/hooks/pre-push.ts` that reference **maintainer-only paths** a consumer never receives (`install.sh` ships only `packages/core/{hooks,eslint-rules}`). The absent paths make vitest exit 1, tsx `ERR_MODULE_NOT_FOUND`, npm `ENOENT`, or a raw `ENOENT` bubble → "pre-push hook crashed". `check:shields-up` never caught this — it verifies the hook is *wired*, not that the check chain *runs*; only a real push surfaces it. This wraps each maintainer-only section in the same `existsSync` consumer-skip guard its sibling sections (3b–3f/4b) already use. Closes #920, #921.

## Changes

- **`packages/core/hooks/pre-push.ts`** — 8 consumer-skip guards:
  - #1 §3 self-test → guard on `packages/core/audit-self/audit-ai-docs.test.ts`
  - #2 §4 render drift → guard on `packages/core/render/render-rules.ts`
  - #3 §5–5d meta-tests → one `coreMetaTestsAvailable` boolean (`packages/core/package.json`) over all four sites
  - #4 §6 spec-validate → guard on `packages/core/spec-validation/validate-batch-spec.ts`
  - #6 §7 prior-art / #7 §1.7 → one `isFrameworkRepo` boolean (SSOT register presence — a framework-authoring gate unsatisfiable on a consumer)
  - #5 cmd-script-liveness / #8 guard-liveness → one `hasRulesManifest` boolean (`packages/core/manifest/rules-manifest.json`)
- **`packages/core/hooks/pre-push.consumer-layout.test.ts`** (new) — behavioral regression test (runs in CI via `test:hooks`) that spawns the real orchestrator against a fixture consumer layout (exact `install.sh` copy-list, every maintainer-only path absent) and asserts `exit 0`; paired-negative re-plants `packages/core/package.json` to prove the §5 guard is load-bearing.

### Design note (deviation from the issue's literal fix for #5/#8)

Blockers #5 and #8 share one `rules-manifest.json`-existence guard rather than the issue's "skip-loudly catch" fallback. A consumer has no manifest rules to check, so skipping is semantically correct; keying on the manifest also keeps guard-liveness's ESLint-stack import off the consumer path entirely (the pnpm-strict `@typescript-eslint/parser` non-hoist, #8) **while preserving the maintainer's loud-die-on-real-breakage** — the section still runs fully wherever the manifest exists.

### Backward sweep

Enumerated every `run()`/`requireTool()`/`import()` call site in `main()`. The only unconditional external calls left unguarded are **zizmor** (§2 — scans the rendered `.github/workflows/` that `install.sh` delivers to consumers) and **lychee** (§8 — already skip-loudly on absent binary). Both are genuinely consumer-appropriate and correctly excluded from the 8.

## Prior-art consult

- [x] The new ≥80-LOC test file is flagged as a capability commit by the LOC-based detector; it carries `Prior-art: skipped — …` per the CONTRIBUTING escape hatch for hook-flagged non-capability additions (test + reuse of the existing sibling guard pattern, no new capability).
- [x] No new capability area surfaced — the fix reuses the `existsSync` guard pattern already established at sections 3b–3f/4b; no SSOT entry added or matched.

## Test plan

- [x] `npm --prefix packages/core run test:hooks` green (48 files / 687 tests, incl. the new consumer-layout regression + paired-negative)
- [x] New consumer-layout test: POSITIVE (maintainer paths absent → exit 0) + NEGATIVE (planted `packages/core/package.json` re-engages §5) both pass
- [x] `tsc --noEmit` + `prettier --check` clean on both changed files
- [x] Pre-push hook ran on push (maintainer repo — all guards evaluate true, every section runs as before)
- [x] Branched from `origin/staging` so the 2 unrelated in-flight ahead-commits (settings/SSOT/principle-31 drift) and their pre-existing `harness-config-drift`/`rule-channel-degradation` failures are excluded — verified those 5 tests pass on this base

## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files)

### §1.7 Skipped: mechanical bug fix to hook enforcement code (pre-push.ts) reusing the existing sibling existsSync consumer-skip guard pattern — introduces no new discipline rule, so no forward/backward rule-scope sweep applies
artyhoo added a commit that referenced this pull request Jul 11, 2026
…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).
artyhoo added a commit that referenced this pull request Jul 11, 2026
…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>
artyhoo added a commit that referenced this pull request Jul 13, 2026
…e shared ConventionNode core (#1005)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: t <t@t.co>
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
…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
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
… 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
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
…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
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
…ate 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
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
…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
artyhoo added a commit that referenced this pull request Aug 7, 2026
…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>
artyhoo added a commit that referenced this pull request Aug 7, 2026
…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>
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
…re 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.
artyhoo added a commit that referenced this pull request Aug 7, 2026
… 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>
artyhoo added a commit that referenced this pull request Aug 7, 2026
…are term, name the denominator, accept the drift (#1266)

## What

Records the operator's verdicts on the three DECISION-NEEDEDs that S-L (PR #1263, squash
`a9fc0af959`) routed, and applies the two that bind a site.

| fork | verdict | effect |
|---|---|---|
| **#5** — which channel keeps «harness remainder» | **A — retire the bare term** | two explicit names; re-labelling only |
| **#6** — which denominator ADR-3 names | **A — the seat first-turn total** | names the denominator; produces **no** number |
| **#7** — T-SH-B corpus drift +11.1% | **accept as accretion** | nothing — S-H's aggregates stand |

**No published number changes value.** Three files: a new patch recording the verdicts, the
ADR-3 edit they bind, and two in-cell kickoff updates.

## Why these picks

- **#5 = A** on asymmetry of error: the two channels differ by a **non-constant** residual
  (16,196 tok vs ~17.3k), so the bare term does not denote one quantity. A stays correct
  whichever way the rule's own falsifier resolves; B would be substantively wrong if the
  residual stays variable. C was dominated — it re-bases onto the `/context` channel S-L
  measured **incomplete** (≥15,258 cp billed and uncategorised).
- **#6 = A** on the umbrella's own hard-won discipline: *a share's numerator must be a provable
  subset of its denominator* — only A satisfies it (same seat class, n=2, 35.4% on the pre-S-G
  pairing). B spans seats with different repo rule-sets; C denominates on the incomplete channel.
- **#7 = accept**: drift is **upward** (+11.1%), ordinary accretion — the opposite class from the
  −27% retention event T-SH-B was written against. All S-L measurements are within-seat, so
  corpus size does not enter them.

## The distinction that made the #5 sweep non-mechanical

The term is retired **as a quantity label**, not **as an artifact title**. The umbrella's three
uses (`:102`, `:273`, `:309`) name the P14 *deliverable* — which is also the merged patch's
filename — so renaming them would break provenance, not improve labelling. Exactly one live site
used the term for a quantity, and it is fixed.

## Also carried: the stale share the spec still held

ADR-3 still read «29-39%» — S-G moved the numerator (69,453 → 48,671 B) while every denominator
in hand was pre-S-G, making it a cross-population division. PR #1265 swept the three sibling
copies but deliberately left this one, reserved for verdict #6. Now aligned with them.

## Surfaced, not built

The naming rule's only enforcement is authoring discipline — `#hope-as-gate` by
`.claude/rules/attention-is-not-a-mechanism.md:29`. Promoting it to a channel is a capability
commit and out of scope for a verdict-recording PR; flagged for the operator, not undertaken.

## Provenance

n/a

## Review findings

n/a

## Fidelity verdict

FIDELITY: skipped — non-stage PR: it records operator verdicts on the already-merged S-L DECISION-NEEDEDs and re-labels the sites those verdicts bind; no stage kickoff governs it.

## Parked questions

- The #5 naming rule's falsifier is **live and unspent** (n=2, readings 6.8% apart): a third
  reading showing a seat-constant residual would justify collapsing back to one term.
- ADR-3's share stays `UNMEASURED — channel absent` until one `/context` reading on a post-S-G
  seat — cheap, and the natural first task of whichever stage next needs the figure.

### §1.7 Forward-check applied

- **Principle-level:** `packages/core/principles/10-research-patch-annotation.test.ts:23`
  (scope annotation) green on the new patch; `13-phase-research-coverage-s17.test.ts:138`
  **caught a real gap in this PR's first draft** — a backward-check shipped with no
  forward-check. Recorded as an incident in the patch (§4), not silently fixed. Full suite:
  37 files / 353 tests green.
- **Commit-level:** not a capability commit per `CLAUDE.md` (no dependency, no file ≥50 LOC
  under a new `packages/core/` subdir, none ≥80 LOC under `packages/`) → `Prior-art:` escape
  hatch with rationale on the commit.
- **Doc-authority:** the design spec is absent from the Artifact Ownership Contract and is
  stage-edited by design — S-L §6 recorded
  `docs/superpowers/specs/2026-07-31-arch-v2-context-pipeline-design.md:155` as
  `GAP-FOUND, routed`, i.e. an edit deferred until this verdict existed.
- **600-line markdown gate:** `.claude/orchestrator-prompts/arch-v2-context-pipeline/kickoff.md`
  edited **in-cell only** — 592 lines before and after (`wc -l`).
- **Kickoff-staging-placement** (`.claude/rules/kickoff-staging-placement.md`): the kickoff edit
  targets `staging`, where `/pipeline` and aif read it.

### §1.7 Backward-check applied

**Class** = a live surface using «harness remainder» bare as a *quantity* label, or stating a
repo-owned always-on *share*. Population: `grep -rn` over `*.md`/`*.sh`/`*.ts` (38 hits) + the
share sites.

- `docs/superpowers/specs/2026-07-31-arch-v2-context-pipeline-design.md:155` (bare term) and
  `:151` (stale «29-39%») — **GAP-FOUND → fixed** (now `:161`, `:170`).
- `.claude/orchestrator-prompts/arch-v2-context-pipeline/kickoff.md:102,273,309` —
  **SWEPT-CLEAN, out of class**: artifact titles (the P14 deliverable / merged patch filename).
- `scripts/check-alwayson-budget.sh:13`, `packages/core/hooks/pre-push.ts:1252`,
  `.github/workflows/audit-self.yml:827` — **SWEPT-CLEAN**: already `UNMEASURED — channel absent`
  (PR #1265); verified the new ADR-3 wording agrees with all three.
- Closed stage kickoffs (`.claude/orchestrator-prompts/arch-v2-context-pipeline-s-e/kickoff.md:135`,
  `arch-v2-context-pipeline-s-h/kickoff.md:6,69`, `arch-v2-context-pipeline-s-l/kickoff.md:88,152`)
  and the closed `session-start-token-audit` umbrella (`done.md` present) — **out of class**.
- Seven research patches carrying the term — **out of class**, append-only; corrections land as a
  new patch, which is this one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant