Skip to content

Phase 4: Stack Detector v1 (read+write AIF bridge, CI gate, reviewer cycle complete) - #4

Merged
artyhoo merged 20 commits into
mainfrom
chore/phase-4-stack-detector
May 8, 2026
Merged

Phase 4: Stack Detector v1 (read+write AIF bridge, CI gate, reviewer cycle complete)#4
artyhoo merged 20 commits into
mainfrom
chore/phase-4-stack-detector

Conversation

@artyhoo

@artyhoo artyhoo commented May 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Phase 4: Stack Detector v1 — deterministic read+write bridge over AIF artifacts. Adds detector core (manifest + config + AIF priority 1–5), CLI bin, 7 fixture snapshot tests, framework-self-detect CI gate, and AIF skill-context override emission (touchpoint 4 closed early instead of deferring to Phase 11).
  • Independent reviewer cycle complete (Opus 4.7): initial GO with reservations → M1 caught vacuous L1 (a) idempotence test → fix in 1555fb1 → re-review final GO. Full audit trail in docs/meta-factory/retros/phase-4.md versioning entries (score evolution 9 → 7 → 8 traceable).
  • 30 detector tests + 7 fixture snapshots stable; all 10 probes from PHASE-4-PROMPT.md green; self-audit 24/24, core 95/95, preset 38/38.

Reviewer trail (last 4 commits)

  • 6a2b5ff retro + initial GO verdict
  • f0c4e8c post-review calibration (M1: vacuous L1 (a) flagged)
  • 1555fb1 fix(setup): husky templates path → packages/core/templates/shared/ (closes M1)
  • a0af459 post-fix re-calibration (L1 (a) non-vacuous, score 7 → 8)

Test plan

  • Detector test suite 30/30 (read-aif 9 + index 9 + snapshot 7 + write-skill-context 5)
  • Fixture snapshots 7/7 + self-detect frozen-match
  • Self-audit 24/24, core 95/95, preset 38/38
  • L1 (a) idempotence non-vacuous post-fix (dry-run x2 diff-clean, includes 4 husky [dry-run] would: cp/chmod lines)
  • CI gate framework-self-detect passes locally (snapshot frozen)
  • No --no-verify, no force push, no destructive ops

Pre-existing typecheck errors (NOT Phase 4 regressions)

Documented in retro Open #5; all exist on main:

  • packages/core: probes/audit-r4.ts (ts-morph missing), render/render-rules.ts (ajv default constructor)
  • packages/preset-next-15-canonical/templates: playwright.config.ts (@playwright/test), vitest.config.ts (@vitejs/plugin-react + poolOptions overload)

Flagged for Phase 5 entry hygiene; out of scope here.

Phase 5 entry open questions (in retro)

  1. Multi-stack monorepo detection (deferred per phase-4-research §6 watch-list)
  2. Tailwind v3/v4 version-aware extension (Phase 5+ candidate)
  3. setup.sh husky templates path — CLOSED in 1555fb1
  4. AIF v3+ schema validation (subscribe AIF release notes)
  5. packages/core / preset typecheck pre-existing errors
  6. setup.sh react-next templates path rot (sister bug to chore(meta-factory): Phase 3 Step 0 retrofit + 3 MAJOR fixes #3, stack-gated, doesn't affect ts-server / L1 (a) on this repo) — discovered post-fix; sharable architectural decision with reviewer's M2 (architecture.md §2.3 reconciliation)

artyhoo added 12 commits May 8, 2026 03:42
Applied EXECUTION-PLAN §5.5 Step 0 gate to Phase 4 (Stack Detector v1)
per memory rule (context7-only research + phase-entry checklist):

- phase-4-research.md (138/200 lines): 6-capability matrix
  × 3 reuse + 3 build, reviewer GO after closing 2 MAJOR + 3 MINOR
  - 4.1 hybrid REUSE (read AIF artifacts as primary source)
  - 4.3 REUSE convergent (adopt AIF severity/weight schema)
  - 4.6 REUSE skill-context format (write side, touchpoint 4 close)
  - 4.2/4.4/4.5 build (semver, CLI bin, vitest snapshots)

- PHASE-4-PROMPT.md (v0.1.2): 7-task implementation prompt + Task 1.5
  (--dry-run flag prereq for L1 idempotence verify), reviewer GO after
  closing 4 MAJOR + 4 MINOR + 2 NEW MINOR

Step 0 paper trail before implementation begins. Phase 4 capabilities
split into priority 1-3 (read AIF artifacts) + priority 4-5 (manifest
heuristic fallback) — detector positioned as deterministic bridge over
AIF, not parallel implementation.
…4-5)

Detector v1 in packages/core/detector/ as deterministic bridge over
AIF artifacts (read-aif.ts wired in but priority 1-3 fixtures + tests
land in Task 2). Priorities 4-5 implemented end-to-end: package.json
deps + lockfile signature (priority 4, severity=warn, weight=1), and
next.config.* / tsconfig.json presence (priority 5, severity=info,
weight=0). Single emit, dual contract per phase-4-research §3.3 +
§4.3: AIF severity/weight schema + human confidence label.

Version-aware logic via npm semver (transitive via
@typescript-eslint/rule-tester — no explicit dep added per
phase-4-research §3.2, §4.2 hard constraint). semver.coerce handles
canary tags, RCs, ranges; self-rolled regex rejected.

Tests: 9 paired cases covering both stack outputs, version-major
extraction, fallback chain (priority 4 → 5 → unknown), and
self-application sanity check on this repo (ts-server, source:
package.json, confidence: medium). detector-v0/ preserved untouched
per research §5 #5.
…mpotence verify)

Adds --dry-run flag (DRY_RUN env, dryguard wrapper) so setup.sh can
be run twice on the same repo without side effects, closing the L1
acceptance gap from self-application.md:33 ("setup.sh --stack=$(detect)
idempotent на собственном репо"). All side-effect paths now report
"[dry-run] would: <op>" instead of executing: ai-factory init,
install.sh, depcruise --init, storybook init, husky init, hook copies,
npm install (deps + zod), and jq mutation of package.json.

Verified: bash -n syntax OK; bash setup.sh --stack=ts-server
--dry-run --skip-aif-init --skip-deps emits ≥1 dry-run line; mtime
of .husky/pre-commit unchanged before/after. Hard-constraint check
from PHASE-4-PROMPT (complete, no leaky abstraction): every cp,
mkdir, chmod, npm install, husky init, jq mutation is under guard.
Adds dedicated read-aif tests + 7 fixture trees for AIF priority 1-3
(DESCRIPTION.md, ARCHITECTURE.md, skill-context/<skill>/SKILL.md).
read-aif.ts ships with Task 1; this commit closes its acceptance with
9 paired tests covering:
- priority 1-3 fixture detection (high confidence, severity=pass)
- priority order DESCRIPTION → ARCHITECTURE
- mandatory schema validation (AifSchemaError when canonical heading
  missing — bidirectional break risk per phase-4-research §5+§6, with
  file path in error message for actionable diagnostics)
- graceful degradation (null when .ai-factory absent or empty;
  caller falls back to manifest priority 4)

Verify probes:
  npx tsx detector/index.ts ./detector/fixtures/with-aif
    → confidence: high, source: ".ai-factory/DESCRIPTION.md"
  npx tsx detector/index.ts ./detector/fixtures/no-aif
    → confidence: medium, source: "package.json"
Adds rules-as-tests-detect npm bin (cli.ts, ~30 LOC argv parsing per
phase-4-research §3.4 + Hard constraint — no yargs/commander). detect
script now points at the new CLI; the legacy detector-v0 entrypoint
moved to detect:install-rules so existing manifest-applicable-rules
tests keep their entrypoint.

Exposes `./detector` from packages/core/package.json `exports`, lets
@rules-as-tests/meta-factory re-export detectStack via
src/detector/index.ts (was a throw-not-implemented stub).

write-skill-context.ts ships as a stub returning "not yet implemented
(Task 6)" — keeps cli.ts compiling with the lazy import for
--emit-skill-context flag wiring; full implementation lands in Task 6.

semver.d.ts: minimal local declaration shim for the `semver` package
(only coerce/valid/satisfies surface). semver itself is transitive
(no explicit dep added — Hard constraint upheld), but @types/semver
is not in the dep tree. Shim also referenced from meta-factory
tsconfig include so re-export typechecks cleanly.

Verify:
  npm --prefix packages/core run detect → JSON, exit 0
  /tmp/fake-next-16 (next@16, react@19) → stack=react-next, major=16
  grep -c '"semver"' packages/core/package.json → 0 (no explicit dep)
  npm ls semver --workspaces → semver@7.7.4 deduped (still transitive)
Adds detector/snapshot.test.ts with vitest toMatchSnapshot per fixture.
7 frozen snapshots: with-aif, no-aif, next-15, next-16, react-only,
ts-server, aif-skill-context. Snapshots cover the full
DetectionResult shape (stack/framework/runtime/confidence/severity/
weight/source/rules) — surfaces drift on any field change.

Verified stable on two consecutive runs (probe 6 in PHASE-4-PROMPT
verification block) — no spontaneous snapshot updates needed.

Stop-rule trigger watchpoint per phase-4-research §5.5 + EXECUTION-PLAN
§5.5: if these snapshots churn >3 update/week, signals detector is
over-fit (read-config too detailed) or fixtures too coupled to
implementation details. Track in retro phase-4.md fragility column.
… a+b)

Adds new audit-self job framework-self-detect that:
  (a) verifies setup.sh --stack=$(detect) --dry-run is idempotent on
      this repo (two consecutive runs produce identical output) — L1
      acceptance criterion (a) from self-application.md:33
  (b) diffs the detector output on the root repo against
      packages/core/detector/expected-self-detect.json (committed,
      frozen) — L1 acceptance criterion (b)

(a) checked first per the source-of-truth ordering in
self-application.md:33; (b) closes the snapshot-stability angle.

Frozen snapshot reflects this repo's current state: ts-server,
source=package.json, confidence=medium (no react/next in deps,
no tsconfig.json marker triggered before package.json fallback).

Verified locally:
  actionlint .github/workflows/audit-self.yml → exit 0
  zizmor .github/workflows/ → no findings (6 suppressed)
  diff <(detect | jq -S .) <(jq -S . expected-self-detect.json) → ✓
  setup.sh --dry-run x2 → no diff
…oint 4 close)

Implements write-skill-context.ts (was a Task 3 stub) to emit
.ai-factory/skill-context/{aif-fix,aif-implement,aif-architecture}/
SKILL.md from a DetectionResult. Closes touchpoint 4 from
aif-comparison.md §5 inside Phase 4 instead of deferring to Phase 11
(per phase-4-research §5 scope-delta #2 + §4.6 reuse decision).

Each emitted SKILL.md embeds the full detection state (stack,
framework name + major + version, runtime, source priority, confidence
with severity/weight) plus a skill-specific guidance line so the AIF
runtime can branch on stack without re-deriving from package.json.

Tests cover: file emission for all 3 top skills; mandatory schema
header check (canonical "# <skill> — project-level overrides" first
line, per Task 6 verify probe in PHASE-4-PROMPT — schema validation
mandatory per phase-4-research §6 watch-list); embedded fields;
graceful handling of ts-server (framework.name=null) without
"null"/"undefined" string leaks in markdown; idempotence on
repeated emit.

Verified end-to-end:
  cd /tmp/fake-next-16 (next@16, react@19)
  rules-as-tests-detect $(pwd) --emit-skill-context=./.ai-factory/skill-context
    → exit 0, 3 SKILL.md files emitted with canonical headings.
Phase 4 closes with GO verdict for Phase 5 entry. All 7 tasks done;
all 10 verification probes from PHASE-4-PROMPT green; both L1
acceptance criteria (a+b) closed at the level required by
self-application.md:33; both CI gates (snapshot diff + dry-run
idempotence) live in framework-self-detect job.

Reuse posture validated: 3 reuse decisions closed (4.1 read AIF,
4.3 severity/weight schema, 4.6 emit skill-context). Detector v1
positioned as deterministic bridge over AIF artifacts (read+write),
not parallel implementation — confirmed by single-emit dual-contract
output (severity/weight + confidence label).

Self-application score 9/10 (‑1 because the "≥3 недели stability"
window can only be point-in-time at retro). Time-vs-plan well under
1.5x.

Open questions for Phase 5 orchestrator surfaced: multi-stack
monorepo, Tailwind version-aware, pre-existing setup.sh templates
path mismatch, AIF v3+ schema check, packages/core typecheck
pre-existing errors.
…(a))

Independent review (Opus 4.7) flagged L1 (a) idempotence test as vacuous on
this repo: husky cp/chmod blocks at setup.sh:339,345 gate on a templates
path that doesn't exist (templates moved in Phase 3.1), so dry-run
idempotence proves only non-husky steps. Lowering self-app score 9→7,
promoting Open question #3 from out-of-scope cleanup to Phase 5 entry
blocker. M2 (arch.md §2.3 drift) and m1 (aif-comparison.md §5 sync)
deferred to Phase 5 entry hygiene. Probes 1–10 still green; verdict
remains GO.
setup.sh:339,341,345,347 referenced $PKG_DIR/templates/shared/ but the
shared templates moved to $PKG_DIR/packages/core/templates/shared/ in
Phase 3.1. With the broken path the husky cp/chmod blocks were dead
code on this repo, which made the L1 (a) idempotence test
(framework-self-detect CI gate) vacuous — two dry-run invocations
diff-clean only because the husky branches never fired, not because
they're idempotent.

Verification (this repo, ts-server stack):
- dry-run x2 still diff-clean
- husky branches now appear in dry-run output (4 [dry-run] lines:
  cp+chmod for pre-commit, cp+chmod for pre-push)
- L1 (a) non-vacuous; self-app reversal-condition from
  docs/meta-factory/retros/phase-4.md:99 met

Closes reviewer M1 from post-Phase-4 review session.

Note: setup.sh:230,231,238,242 still reference templates/react-next/
which also doesn't exist at root post-Phase-3.1 — separate finding,
out of scope for this commit; logged as Phase 5 entry follow-up.
… 7→8

Reflects state after 1555fb1 (setup.sh templates path fix). L1 (a)
idempotence criterion no longer vacuous: dry-run output includes 4
husky [dry-run] would: cp/chmod lines, proving the install path is
exercised. Score evolution now visible in retro:
  9/10 (initial Phase 4 close)
  7/10 (post-review calibration, reviewer M1)
  8/10 (post-fix, reversal-condition met)

Open question #3 closed; new Open question #6 logged for wider
Phase 3.1 fallout (react-next templates path rot in setup.sh:230-244,
stack-gated so doesn't affect this repo's L1 self-app, but will break
react-next consumers).

Probes 1-10 still green; verdict remains GO.
@socket-security

socket-security Bot commented May 8, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednext@​15.5.1881100919870
Addednext@​16.2.681100919870
Addedreact@​19.2.61001008497100
Addedreact@​18.3.11001008497100
Addedzod@​3.25.769810010096100

View full report

artyhoo added 8 commits May 8, 2026 11:19
Reviewer m1 finding: aif-comparison.md §5 matrix didn't reflect that
touchpoint 4 (/aif-fix /aif-architecture skill-context overrides) was
closed in Phase 4 instead of deferred to Phase 11. Single-line append
to the matrix row marking closure with commit ref b5e16b7.
…import

Two pre-existing typecheck errors closed via diagnosis (retro Open #5
packages/core half):

probes/audit-r4.ts is a CONSUMER-PROJECT script template (ships via
setup.sh to consumer's scripts/audit-r4.ts), not a local probe.
Evidence: audit-self/audit-ai-docs.sh:11 explicitly says
"scripts/audit-r4.ts in consumer project"; ts-morph is a CONSUMER dep
added by setup.sh COMMON_DEPS, not a packages/core dep; manifest path
is "scripts/audit-r4.ts" (consumer view), not "probes/audit-r4.ts".
Excluding probes/ from local typecheck is the architecturally correct
fix — the file is meant for consumer's tsconfig with consumer-installed
ts-morph, not ours.

render-rules.ts: ajv@8 d.ts exports Ajv BOTH as named class AND default
(see node_modules/ajv/dist/ajv.d.ts:3,8). Under Node16 module resolution
+ CJS interop, default import resolves to namespace ("This expression
is not constructable"). Named import gives the class directly.
Same architectural pattern as packages/core/probes/ (closed in prior
commit): templates/ contains files that ship to CONSUMER projects
(playwright.config.ts, vitest.config.ts) and reference CONSUMER deps
(@playwright/test, @vitejs/plugin-react), not preset deps. Local tsc
shouldn't typecheck them — consumer's tsconfig with consumer-installed
deps does.

Closes retro Open #5 (preset half).
Three items closed in orchestrator session post-PR #4 push:
- m1 (9f61a02) aif-comparison.md §5 touchpoint 4 status sync
- Open #5 core (4f0f226) typecheck via consumer-template diagnosis
- Open #5 preset (ef8262a) typecheck via templates/ exclusion

All architectural realizations: probes/ and templates/ contain
consumer-deployed files referencing consumer-installed deps. Local
exclusion is the correct fix, not adding deps locally.

Full regression green: self-audit 24/24, core 95/95, preset 38/38,
all 3 workspaces typecheck clean.

Q2 + M2 + Open #6 architectural choice still open — research bundle
mapped (5 option bundles); awaits Art's decision.
…v storybook json from preset

Creates packages/core/templates/react-next/{.storybook/main.ts,.storybook/preview.ts,storybook-package-additions.json}
(last moved from packages/preset-next-15-canonical/templates/ via git mv — history preserved).
Updates setup.sh:230,231,238,242 from $PKG_DIR/templates/react-next/ to $PKG_DIR/packages/core/templates/react-next/.
Closes Open #6 (option b): establishes scalable per-stack templates pattern symmetric with packages/core/templates/shared/.
…ption b, 8cc6cef)

Marks Open #6 CLOSED in phase-4.md; adds versioning entry for 8cc6cef.
…rtial-extended)

Extends DetectionResult with optional missing[] (absent known packages,
fed to Layer 2 Research Agent) and patterns[] (observable stack patterns
e.g. nextjs-app-router). Ships 5 known packages and 5 patterns. Defers
language/structure/router to v2. Annotates arch.md §2.3 with v1.1 subset
note. Declares detector-v0 maintenance-only. Snapshot diff is additive only.
Open #2 (Tailwind v3/v4 version-aware) closed by ea3e006 via
patterns[] field including tailwind-v3-config + tailwind-v4-css-tokens.
Mantine/Chakra additions remain additive scope.

New versioning entry covers Bundle 4-partial-extended completion:
- missing[] + patterns[] schema additions (v1-active partial)
- arch.md §2.3 v1.1 subset annotation (Q2/M2 partial closure)
- detector-v0 deprecation declared
- Test count semantics clarified per prior reviewer note

Phase 5 entry closures total: 5 of 6 (Open #2, #3, #5, #6 + m1).
Q2/M2: partial via annotation; full v2 deferred to Phase 7+.
@artyhoo

artyhoo commented May 8, 2026

Copy link
Copy Markdown
Owner Author

Phase 5 entry sealed — final state

This PR started as Phase 4: Stack Detector v1 deliverable, then absorbed the Phase 5 entry hygiene batch (closing open questions from the Phase 4 retro) plus Bundle 4-partial-extended schema work. Two independent reviewer cycles verified each scope expansion. Documenting change-of-state per reviewer recommendation; PR body left intact for history.

Two reviewer cycles complete (Opus 4.7)

Cycle 1 — Phase 4 close + M1 fix
Initial GO with reservations → M1 (vacuous L1 (a) idempotence test) caught → fix 1555fb1 (setup.sh husky templates path → packages/core/templates/shared/) → re-review GO. Self-app score 9 → 7 → 8 (audit trail in retro versioning).

Cycle 2 — Phase 5 entry hygiene + Bundle 4-partial-extended
4-instance "consumer-deployed-but-locally-housed" pattern verified (probes/audit-r4.ts, preset templates, husky templates) → 5-bundle architectural research mapped via Sonnet agent → Bundle 4-partial-extended chosen: ship `missing[]` + `patterns[]` v1.1 subset, annotate §2.3 with explicit deferral → schema acceptance criteria verified (additive-only snapshot diff, typecheck clean, detector-v0 deprecation declared) → re-review GO.

Phase 5 entry closures (5 of 6 actionable)

Item Commit Status
Open #2 — Tailwind v3/v4 extension `ea3e006` ✓ closed (via `patterns[]`)
Open #3 — husky templates path `1555fb1` ✓ closed
Open #5a — packages/core typecheck `4f0f226` ✓ closed
Open #5b — preset templates typecheck `ef8262a` ✓ closed
Open #6 — react-next templates path rot `8cc6cef` ✓ closed (option b)
m1 — aif-comparison.md §5 sync `9f61a02` ✓ closed
Q2 / reviewer M2 — arch.md §2.3 schema drift `ea3e006` 🟡 partial (subset annotation; full v2 → Phase 7+)

Final regression

  • `make self-audit` 24/24 ✓
  • `npm test --workspaces` core 124/124 + preset 38/38 ✓
  • `npm run typecheck --workspaces` 3 workspaces clean ✓
  • L1 (a) idempotence: dry-run x2 diff-clean (non-vacuous post-`1555fb1`) ✓
  • pre-push hook: self-audit re-ran, exit 0 ✓

Deferred (not blocking)

Open #1 (multi-stack monorepo, Phase 9+), Open #4 (AIF v3+ schema validation), m2 (regex order until first false-positive), m3 (spec-validation gh-API flake → network-tagged suite), Q2/M2 full v2 schema (`language`, `structure`, `router` → Phase 7+ if Layer 2 Research Agent demand emerges).

Source of truth

`docs/meta-factory/retros/phase-4.md` — versioning entries trace 9 → 7 → 8 self-app score evolution + per-commit closure log + Bundle 4-partial-extended completion entry. `detector-v0` is now `@deprecated` (removal trigger = v1 R8 parity in Phase 6+).

Re: PR body "Pre-existing typecheck errors (out of scope here)"

Those were closed in this same PR (`4f0f226` + `ef8262a`) — body left intact for history; this comment documents change-of-state.

@artyhoo
artyhoo merged commit ac8bb1e into main May 8, 2026
23 checks passed
artyhoo added a commit that referenced this pull request May 8, 2026
…s-reuse passes its own framework gates

Extend Phase 8.8 description with explicit recursive self-validation
section. P-build-vs-reuse principle is subject to the same gates as
every other rule in the framework (recursive thesis per
aif-comparison.md §10 differentiator #4).

Three forms of recursion documented:

1. L4 6-gate validation table — gate-by-gate status for the new
   principle. Gate 1 (schema) ✓ — manifest entry; Gate 2 (rule-tester)
   n/a — manual rule (G2 Pages-Router precedent); Gate 3 (mutation)
   n/a v1; Gate 4 (tautology) ✓ MUST — pre-push hook companion must
   fire when violated; Gate 5 (two-AI review) DEFER per §13.10 entry
   #4; Gate 6 (cross-rule conflict) ✓.

2. Self-application at creation — chicken-and-egg resolved: the
   2026-05-08 analog research session IS the consult evidence for
   creating P-build-vs-reuse. Strongest form recursion: rule about
   prior-art consult is itself prior-art-consulted. Documented in
   Phase 8.8 retro as «self-application at-creation evidence».

3. 6 Уровней as defense-in-depth — each level catches a distinct
   class of violation failure mode (drift / staleness / phase-entry /
   session-start / implementation-time / time-decay). Violation must
   pass all 6 layers undetected to slip through.

Mutation testing note: when gate 3 activates (Phase 9+ Path B), invert
pre-push trailer regex → meta-test must fail. Otherwise the test of
the test is itself tautological.

Acceptance criteria extended: P-build-vs-reuse passes applicable L4
gates + self-application-at-creation evidence documented in retro.
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
The triggering incident for this Phase 8.8.1 session: phase-9-entry-research.md
§4.A1 closed §13.10 entry #2 ROI re-evaluation negative on a 5-candidate
context7 base (Cursor, Continue, Factory, Cody, Aider) and missed two
production-grade analogs in adjacent paradigms — AIF /aif-evolve (skill-context
rule synthesis from accumulated patches; already an integrated dep) and
Oh My ClaudeCode family (multi-agent orchestration in our exact runtime).

Patch maps the gap onto checklist items §1.1 (own-stack sweep —
#own-stack-blind-spot), §1.3 (semantic-distance — #semantic-anchor), §1.5
(prompt-list anchoring — #prompt-list-anchoring), with #negative-existence-claim
applying as the umbrella mode. Prevention rules are operationalisable
(«before closing X, also check Y»), not generic ergonomics. Tags seed the
aggregation table — first datapoint per tag.

Verdict (A1 DEFER) is unchanged; only the recorded reason is corrected.
No re-litigation of the matrix per Hard Constraint #4.

43 LOC ≤100 cap.

Prior-art: skipped — docs-only patch documenting an incident, no new capability surface or explicit dependency added (per CLAUDE.md capability-commit definition).
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
…pointer + m3 forward-finding)

Closes 1 MAJOR + 2 MINOR review findings on Phase 9 prompt drafting:

- M1 — A8 «calibration» framing was overclaim. PHASE-9-PROMPT.md §4
  T3 said weights become «data-backed» — but 5 mutants × 3 dimensions
  ≈ 1 sample per dimension = regression guard for the formula, NOT
  statistical calibration of 0.40/0.40/0.20 values. Updated §4 T3
  «Why» paragraph + sub-task #4 doc-note text + verification grep
  probe (regression guard, not data-backed) to distinguish «formula
  behaves as designed» (what corpus proves) from «weight values are
  right» (still requires real divergent-plan data per Phase 8 retro
  Self-reflection #9).

- m2 — retro Versioning section did not point to next-step push +
  gh pr create. Added explicit «Next:» line at end of Versioning +
  2026-05-09 entry recording T6 review delta-fix landing. Also
  corrected the branch-name reference (drafting work is on
  docs/phase-9-prompt-draft after Phase 8.8.1 T0 split, not on
  docs/phase-9-coverage-gap).

- m3 — drafting session did not apply (yet-to-be-formalized) Phase
  8.8.1 search-coverage methodology to its own decisions (own-stack
  sweep on A8 corpus shape; adversarial check on N=15; surfaced
  Open Q from drafting itself = 0). Circular: Phase 8.8.1 not yet
  merged → methodology not formally required. Documented as Open Q
  #4 forward-finding for post-Phase-8.8.1-merge audit.

Reviewer-flagged BLOCKER B1 (drafting work «lost») was misdiagnosed
— drafting commits live on this branch (origin synced at 1c422cd);
T0 of Phase 8.8.1 meta-prompt split correctly. Reviewer's coverage
was bounded to one branch, missed the parallel docs/phase-9-prompt-draft.

PHASE-9-PROMPT.md = 307 LOC ≤500 cap unchanged; retro 119 → 124 LOC
(+5 from m2 closing + m3 Open Q #4) ≤200 cap.
artyhoo added a commit that referenced this pull request May 11, 2026
…+ §13.23 self-review

Updates self-reflection skill (.claude/skills/self-reflection/SKILL.md) by
adding the §1.7 enforcement layers table: 3 active + 1 deferred → 4 active.
The 4th layer (pre-push trailer check) shipped in Commit 1 of this batch.

Ships paired self-review patch at
docs/meta-factory/research-patches/2026-05-11-§13.23-4th-layer-self-review.md
per §13.23 promotion path #4. The patch walks the new layer through §1.7
forward+backward checks applied to its own motivating gap (local-push-
bypasses-CI + discipline-theatre risk).

§13.23 status update in open-questions.md folds into sub-wave 7.5.c per
kickoff §«Sub-wave 7.6.d» (atomic with §13.27 + §13.28 closures); NOT
modified in this commit.

§1.7 self-application: this commit touches .claude/skills/self-reflection/
SKILL.md AND adds new ## §1.7 enforcement layers section heading. The C4
predicate fires on this commit. The §1.7 check shipped in Commit 1 of this
batch (one commit earlier in the push range) and is in warn-only calibration
mode — B1 bootstrap trailer below satisfies §1.7 for this commit.

Prior-art: skipped — doc-only update to self-reflection skill description + self-review patch in research-patches/; no new packages/ subdirectory, no new file ≥80 LOC.

§1.7 Bootstrap: updating skill ladder from 3 active + 1 deferred → 4 active layers and shipping self-review patch immediately after introducing the §1.7 enforcement layer — §1.7 check itself is in 30-day calibration (warn-only) at this commit; B1 exemption per 7.6.a research §4 + 7.6.b §2 Problem 2 verdict (mirrors Commit 1 bootstrap exemption; both commits are in the same push, same bootstrap context).
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 27, 2026
…verables (α-σ shapes + claims + inflight) (#1176)

* feat(scripts): add prune-worktrees.sh — safe-by-default sweep of landed CC worktrees

Idempotent preserve-then-prune sweep that reclaims worktrees whose work
already landed in origin/staging (ff-merged, squash-merged PR, or closed PR).

Safety properties:
- DRY-RUN by default; --apply to actually remove.
- NEVER removes: unmerged-with-open/no-PR worktrees, locked worktrees,
  the primary checkout, or the worktree the script runs from.
- PR-floor guard (>=100 records): aborts if 'gh pr list' flakes, so a
  partial result can't misclassify squash-merged branches as active and
  under-prune.
- T17 preserve-before-destroy: gitignored orchestrator-prompts content
  is reconciled into $CANON via scripts/link-coordination.sh before any
  removal (best-effort; conflict -> skip, leave intact).
- dirty worktrees: classified but skipped without --force-dirty.

shellcheck-clean (no dead-variable warnings).

* docs(role-context): research pre-codification — progressive disclosure per role (review + 4 options)

Design doc preparing material for a fabla on whether/how to codify
role-based context shaping in this repo. No rule proposed yet.

Research findings:
- External: Anthropic + LangChain + arXiv converge on 'progressive disclosure'
  (canonical term) / 'context isolation'. Anti-patterns: context pollution,
  context rot (Anthropic official); lost-in-the-middle (Liu et al. TACL 2024,
  5300+ citations, the mechanism-level citation).
- Internal: the repo's inject-matching-rule.sh implements progressive
  disclosure for PATHS and SESSIONS but never for ROLES (no hook branches on
  subagent_type). 6-block input contract (claude-glm-executor-handoff:52-71)
  is flagged for promotion but never promoted.
- SDD: strong CATEGORICAL discipline (brief-as-file, reviewer allowlist
  task-reviewer-prompt.md:38-50) but zero QUANTITATIVE discipline (no token/
  char budget, unbounded ## Context slot, no enforcement script — the 42k-char
  warning is prose only).

Lays out 4 design options (full / spike / surgical / defer) + 5 open
questions for the brainstorm.

* docs(role-context): rev 2 — wave-2 audit of superpowers plugin + wrapper skills

Wave 2 delegated research (4 parallel agents: DeepWiki obra/superpowers +
web + wrapper-skill audit + exhaustive skills audit) revised the picture:

Three corrections to wave-1 framing:
1. superpowers DOES ship a real injection hook — hooks/session-start
   (hooks/hooks.json:3-15) — but it's ONE flat SessionStart dump of
   using-superpowers/SKILL.md, identical for every role, no other hook
   events. Wave 1 missed the plugin's hooks/ dir entirely.
2. Progressive disclosure + injection are NOT a deliberate pair in either
   superpowers or this repo. Disclosure is prose about the host harness
   (writing-skills/anthropic-best-practices.md:235,1049,1099); injection
   is one flat bootstrap. Never cross-referenced.
3. The 3 wrapper skills (arch/pipeline/dispatcher) add verdict + routing
   on top of SDD — they do NOT add context-shaping. All three consume
   SDD's templates as-is.

New design candidate: Option E — extend the SessionStart/SubagentStart
hook to be per-role. Plumbing partially exists (inject-subagent-context.sh:62
already preserves subagent_type). Hook IS the enforcement — earliest
reachable channel per invariant #4.

Doc adds: §3.7 (plugin hooks), §4.7 (wrapper skills), §5.5 (deliberate-pair
verdict), Option E + matrix row, open question #6 (is the pair load-bearing),
17 internal citations including superpowers source repo.

* docs(role-context): rev 3 — wave-3 in-flight compat + scratchpad Seat B + 4 GLM hypotheses

Wave 3 reconciled two live threads wave-1/2 missed:

1. session-start-token-audit umbrella (operator-commissioned today,
   branch claude/session-start-token-audit-77d224) — owns the context
   budget vocabulary; ~140KB/~36-40k tokens injected at session start;
   S2 trims by re-scoping channels (target <=20-25k). Natural host for
   role-context-budget work.

2. AIF scratchpad Seat B (feature-scratchpad-d49985) — already answered
   the operator's exact question. VERDICT: REVISE. 4 of 5 per-role axes
   already match SDD; only gap is axis 5 (ambient injection uniform).
   LOAD-BEARING (F2): uniform digest is DELIBERATE anti-drift machinery
   (2026-05-09 incident — reviewers pattern-matched on EXECUTION-PLAN
   'north star' and reinforced wrong goal across cycles).

New: Option F (hybrid one-line anchor = Seat B's Option C) — keep uniform
digest (preserve anti-drift) + add per-role prime line. Resolves F2 tradeoff
that Option E ignores. 6 options now A/B/C/D/E/F.

Split: main design doc now 595 lines (<=600 husky pre-commit limit);
wave-3 dossier holds §10 full content (compat matrix, 4 GLM hypotheses
H1-H4 with evidence+falsifier+confidence, honest §10.6 disclosure,
§10.8-10.9 full tables moved from main doc).

Sequencing constraint: do not touch inject-matching-rule.sh surface
until PR #1175 (getff-honest-signals S6) merges; do not start
role-context-budget work until session-start-token-audit S2 lands.

* docs(research-patch): raw research for Opus cold-verify — per-role context shaping

Replaces the earlier design doc + dossier (wrong format — those were spec/
design output, my job is raw material for Opus to filter, then fabla writes
the spec).

Format: docs/meta-factory/research-patches/ — matches the existing R-phase
research-patch convention (e.g. 2026-07-18-zcode-parity-s7-subagentstart.md).

Content:
- R1 external: progressive disclosure / context isolation are the canonical
  terms; anti-patterns context pollution/rot/lost-in-the-middle (Anthropic +
  LangChain + Liu et al. TACL 2024).
- R2 internal: 3-layer path/session-gated inject mechanism; NO per-role
  branch in any hook (inject-subagent-context.sh:62 preserves subagent_type
  but does not branch).
- R3 superpowers plugin: ships ONE hook (SessionStart flat inject); SDD has
  categorical discipline only (no quantitative budget; 42k-char warning is
  prose).
- R4 wrapper skills (arch/pipeline/dispatcher): add verdict+routing, NOT
  context-shaping; consume SDD templates as-is.
- R5 in-flight: session-start-token-audit umbrella (operator-commissioned
  today) owns the budget vocabulary; PR #1175 OPEN on inject-matching-rule.sh
  surface; AIF scratchpad Seat B already answered the operator's exact
  question (REVISE — only gap is axis 5 ambient injection uniform; F2: the
  uniform digest is DELIBERATE anti-drift machinery per 2026-05-09 incident).

10 falsifiable claims (C1-C10) with explicit 'wrong if' falsifiers.
8-item verify-list for Opus (digest size, 2026-05-09 incident root cause,
  skill-context generalization, BUILD re-trigger, re-grep, version check,
  orthogonality).
5 forks PARKED (no verdict picked, per reviewer-discipline §2 + the
  operator's explicit instruction that GLM builds material, Opus filters,
  fabla decides).

* docs(role-context): reframe as raw material — 18 candidate shapes (α-σ), no recommendation

Reverses the earlier spec framing. The fabla should generate its own shapes
first, then skim this catalogue for anything it didn't think of — not the
other way around. GLM's job is resource-intensive raw material; Opus filters;
fabla decides and writes the spec.

Changes:
- Design doc renamed 'Design — Role-Based Context Budget' → 'Candidate
  shapes — per-role context (raw material for fabla, NOT a spec)'.
- §6 replaced 'Options A-F with pros/cons/effort/risk + comparison matrix'
  with 18 candidate shapes (α-σ), each described neutrally: shape, channel,
  interacts-with, unverified-assumptions. No scoring, no ordering.
- New shapes beyond the original 6: η (templates), θ (token gate), ι (tool
  surface), κ (upstream plugin), λ (artifact-handoff), μ (two-layer digest),
  ν (role-from-content), ξ (observability), ο (absorb into token-audit),
  π (codify MUST stay uniform), ρ (consumer generator), σ (measure-first).
- TL;DR rewritten as 'raw observations' — no 'natural host', no 'decision
  in front of us', no direction.
- §7 'Open questions' → 'Forks surfaced (no answer implied)'.
- §9 'Next step' → 'What this file is (and is not)'.
- Dossier §10 reframed: 'in-flight context, not analysis'. §10.3 'changes
  3 options' → 'Overlap with Seat B fork (awareness)'. §10.7 'Recommended
  next action' → 'What the fabla may want to do (suggested, not prescriptive)'.

Pressure-language scan: 0 hits on best/strongest/recommend/we-should/
pros/cons/effort. Lint clean. 18 shapes (α-σ).

3 files total: design doc (580) + dossier (138) + raw research patch (263,
companion with 10 falsifiable claims C1-C10 + 8-item verify-list for Opus).

* docs(per-role-context): rename files to honest names (was misleading)

Old names implied a spec/design that doesn't exist; new names say what
the files actually are:
- design.md → candidate-shapes.md (18 shapes α-σ, no spec)
- wave3-dossier.md → inflight-context.md (in-flight surface, not analysis)

Cross-references updated. Content unchanged.

* feat(scripts): runtime-probe kickoff — verify static research claims in live CC

Prior research (3 waves, 8 subagents) read .claude/hooks/*.sh source code
and inferred behavior. This kickoff dispatches a task to a LIVE Claude Code
container to verify or refute those static claims.

6 probe targets (P1-P6):
- P1: inject-subagent-digest.sh emits identical payload regardless of
  subagent_type
- P2: inject-subagent-context.sh preserves subagent_type but doesn't branch
- P3: inject-matching-rule.sh path-glob + once-per-session dedup
- P4: CC-native loader injects rules without paths: frontmatter
- P5: session-bootstrap digest size (~500 tokens estimate, unverified)
- P6: 3 wrapper skills don't add per-role context shaping

Dispatched to aif-handoff: taskId=f164e807-191a-4336-9fe1-52145255c00e
Board: http://localhost:5180/projects/441c1c0c-b633-4612-a34c-2cc0c4d0eaf2

Output: per-role-context-runtime-probe-report.md in the task project root.
Read-only investigation — no code/rule changes. The probe reports
measurements; the fabla decides later.

* feat(scripts): triangulation kickoffs — deep-project-research + cold-review

Parallel to the runtime-probe (taskId f164e807), dispatch 2 more tasks
for triangulation:

1. deep-project-research (taskId f4dc0bff) — exhaustive sweep of 12
   surfaces the prior 3-wave GLM research only sampled: packages/runtime-bridge
   full tree, plugin/, packages/core/, tests/, scripts/ rest, .github/,
   setup.d/, all agents/, .claude/skills/**, .zcode/skills/**,
   ~/.claude/skills/**, docs/meta-factory/ SSOT deep, .claude/rules/ deeper
   grep, CLAUDE.md + ~/.claude/CLAUDE.md + MEMORY.md. Answers 7 load-bearing
   questions (Q1-Q7). Output: per-role-context-deep-research-report.md.

2. cold-review (taskId 4e73e54e) — skeptical external review of the 3 prior
   GLM deliverables (raw research patch + candidate-shapes + inflight-context).
   8-checklist audit (A-H): format honesty, claim quality, hidden pressure
   language, completeness, verifiability of the 8-item verify-list, internal
   consistency, framing bias, token-economy. Output:
   per-role-context-cold-review-report.md.

Both: read-only, no recommendations, no rule proposals. The fabla decides
after Opus filters all 3 angles (runtime + deep + cold).

* docs(research-patch): add §1.7 self-review marker (principle 13 compliance)

Pre-push hook caught the missing marker. Forward-check applied against
the active disciplines (recommendation-laziness, reviewer-discipline,
ai-laziness-traps, attention-is-not-a-mechanism, doc-authority-hierarchy);
backward-check sweeps 12 sibling 2026-07-* patches (SWEPT-CLEAN) and
verifies no scope duplication.

* feat(scripts): bundle-for-cc kickoff — assemble 5 sources into 1 for Opus review

Operator defined the session boundary: GLM (this worktree) produces raw
material; aif containers verify it (3 parallel tasks); Opus in CC accepts
the bundle for review + fabla-prep. GLM does NOT filter; Opus is the filter.

This task waits for the 3 parallel verify tasks to finish (runtime-probe
f164e807, deep-project-research f4dc0bff, cold-review 4e73e54e), then
assembles ONE bundle document preserving all findings + contradictions.

Output: per-role-context-bundle-for-opus-in-cc.md (read in CC, not in aif).

Bundle structure:
- §1 substrate: operator question + 3 GLM deliverables
- §2 runtime-probe results (live verification)
- §3 deep-project-research results (exhaustive sweep)
- §4 cold-review findings (audit of deliverables)
- §5 contradictions to resolve (LOAD-BEARING — preserves both sides)
- §6 parked forks + 18 candidate shapes
- §7 what the bundle does NOT do (no filter, no pick, no recommend)

Dispatched: taskId=73519b9c-f27e-4bd2-b0b6-002bedcebdbb

* chore(per-role-context): remove dispatched kickoffs + prune-worktrees.sh

- 4 per-role-context kickoffs already dispatched to aif (taskIds
  f164e807 / f4dc0bff / 4e73e54e / 73519b9c recorded in aif-handoff);
  they are dispatch-inputs, not repo utilities — keep scripts/ clean.
- prune-worktrees.sh isolated to chore/prune-worktrees-script branch
  for separate PR (unrelated to research track).

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 6, 2026
…ope (#1235)

Two dispatch inputs for the same umbrella, both gated on state that only just became true.

**S3 (new)** — one beat + per-stack research paths, spec §6. Its gate (S2b merged, #1233
`a66c0cb9aa`) cleared minutes ago, so this is the first moment S3 could be planned against a
tree that exists. Anchors re-verified live at `a66c0cb9aa`, not carried from the spec:
`agents/rule-researcher.md` has 0 hits for python/pyproject/ruff (wall #4 confirmed);
`INSTALL-FOR-AI.md` has 0 hits for the continuation clause; the delivered starter AGENTS.md
comes from `packages/core/templates/shared/AGENTS.md.template` via `setup.d/45-python.sh:808`;
`_py_join_researched_rules` is at `setup.d/45-python.sh:161`; the F-A bundling precedent
`synth-and-wire.bundle.mjs` is 395 149 bytes as committed.

S3 owns fork F-A (Node for the generation CLI: bundle vs declare honestly) — spec §12 assigns
it to «the trace umbrella's planner» and gives size-and-maintenance-cost criteria, so the
kickoff instructs RESOLVE-with-measurement, not park, and hands it a real maintenance datapoint:
the 2026-08-07 S2b egress hit a synth-bundle drift caused by root-vs-packages/core lock
disagreement on semver (7.7.4 / 7.8.1 / 7.8.5).

Scope note carried explicitly: `setup.d/47-go.sh` + `do_go_lane()` landed 2026-08-06 (#1171).
S3 stays python + a rust pointer per spec §6.2 and is told not to widen to go, but also not to
let the researcher-agent edit read as lane-complete while omitting it.

**R1 §8 (amendment)** — round-2 rework scope after a `FIDELITY: STOP` at the egress seam.
Round 1's patch is on `research/getff-any-stack-trace-r1` (`1479f54741`) with no PR opened.

The BLOCKER is a false negative-existence claim about the go lane — and the amendment records
that this was NOT worker error. Verified mechanically rather than inferred:

    git show 0e2d366:install.sh | grep -cE 'do_go_lane|TOOLCHAIN=go'    -> 0  (R1's base)
    git show origin/staging:install.sh | grep -cE 'do_go_lane|TOOLCHAIN=go' -> 6  (today)
    PR #1171 merged 2026-08-06T22:04Z

R1's search was honest and correct when it ran on 2026-08-01; the go lane landed five days
later, fifty minutes before the egress. The audit aged out. Round 2 must verdict all 7 go cells
against the live tree AND record the staleness itself as a finding — a parity audit's verdicts
are only as live as the tree they were taken against.

Three MAJORs in the same amendment are NOT staleness and stand on their own against §2/§3 as
originally written: npm rung 6 is the only EXISTS with no firing evidence (plus four line
anchors that no longer resolve); five further EXISTS cells rest on analogy («same machinery
as…») rather than a cited firing artefact; and a hybrid `EXISTS*` token breaks «exactly three
verdicts», leaving the §7 GAP counts unreconcilable with the matrix.

New round-2 trap T-R1-D — patching the auditor's findings instead of re-deriving them: the
line numbers in §8 are a map, not evidence, and a round-2 patch citing them second-hand would
launder exactly the failure class it exists to fix.

Both kickoffs validated locally: principle 12 (traps enumeration) 7/7; `host-verify.sh --list`
resolves S3's three-command contract and accepts R1's 161-char prose-only opt-out.

Prior-art: skipped — dispatch-input authoring only; no capability, no dependency, no code module.

Co-authored-by: Test <test@example.com>
artyhoo pushed a commit that referenced this pull request Aug 7, 2026
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
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
…e 13)

The pre-push principle-13 gate correctly rejected the new patch: a research patch must carry an
actual §1.7 self-review, not merely name the section. Added Forward + Backward + T15.

The backward-check is a real outward sweep, not a restatement of this diff — the change class is
"a post-merge artefact that revises figures already published in a merged research patch", and
six surfaces are verdicted, of which four are GAP-FOUND and left unedited by ownership:
- the sibling p3d-p11 patch shares the falsified 4 B/t constant at its §5/§7/§8, so §8.1 applies
  to it identically — named in DECISION-NEEDED #4's Option A as required scope;
- the token-economy spec's tag convention (the constant under one of its tags is wrong);
- ADR-3 (repo-owned share measures below its 29-39% band under BOTH conversions);
- the S-A profile patch (closed historical artefact, its authoring session owns it).

T15 records the reflexive fact that this file exists only because the parent hit the 600-line
markdown gate — a document about document cost split by a size discipline.

Prior-art: skipped — self-review section required by principle 13 on an existing patch, no new capability
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 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 8, 2026
…GUOUS + host-verify contract (#1310)

* docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev)

Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A +
description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded
with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1
iteration, external cold fidelity mandatory; Park-4 CC detection =
CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park
did not fire (live runtime-profiles probe, no substring collision); Park-6 ship
the full functional set (create-worktree.sh + worktree-node-modules.sh +
link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added.
Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3).

* docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract

Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300).
The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores
only the env-var NAME in the profile, #4(ii) writes the value to
~/.config/getff/glm.env, and #3 requires the validation ping to run through the
created profile. Verified against source: aif resolves the key from its own runtime
process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219,
:247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) —
so glm.env is a dead end as written and #3 is unreachable without dereferencing the
value, which #1 forbids.

§7b closes it by binding the OUTCOME (value reachable in the aif runtime process env
under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure)
while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked
as a dispatcher-authored completion, not an operator decision — the operator can
override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full
body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id
(:90), both defaults halves, ping through the profile, 10-skills.sh untouched.

§4.1 adds the missing host-verify contract — the stage ships a shell helper, an
install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host
surface destination-environment-verification.md §1 exists for; the umbrella had no
contract on any of its six kickoffs (exit 2).

Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an
ambiguity in an existing stage kickoff against in-repo source citations.

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 8, 2026
… path, bind run-3 blockers (#1319)

* docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev)

Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A +
description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded
with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1
iteration, external cold fidelity mandatory; Park-4 CC detection =
CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park
did not fire (live runtime-profiles probe, no substring collision); Park-6 ship
the full functional set (create-worktree.sh + worktree-node-modules.sh +
link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added.
Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3).

* docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract

Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300).
The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores
only the env-var NAME in the profile, #4(ii) writes the value to
~/.config/getff/glm.env, and #3 requires the validation ping to run through the
created profile. Verified against source: aif resolves the key from its own runtime
process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219,
:247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) —
so glm.env is a dead end as written and #3 is unreachable without dereferencing the
value, which #1 forbids.

§7b closes it by binding the OUTCOME (value reachable in the aif runtime process env
under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure)
while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked
as a dispatcher-authored completion, not an operator decision — the operator can
override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full
body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id
(:90), both defaults halves, ping through the profile, 10-skills.sh untouched.

§4.1 adds the missing host-verify contract — the stage ships a shell helper, an
install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host
surface destination-environment-verification.md §1 exists for; the umbrella had no
contract on any of its six kickoffs (exit 2).

Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an
ambiguity in an existing stage kickoff against in-repo source citations.

* docs(beta-delivery-ux): S4 kickoff §7d — retract the unreachable host path, bind run-3 blockers

§7d.0 retracts the §1 + §7a #1 instruction to re-read
`~/code/aif-handoff/packages/api/src/routes/runtimeProfiles.ts` at stage entry. That path is
host-only: the agent container mounts `$PROJECTS_DIR` (= `$PROJECTS_HOST_ROOT/projects`), so the
repo root holding `packages/api/` sits one level above the mount. The instruction was unexecutable
and run 3 guessed instead of parking — same class as state.md §2 decision 14.

Replacement is an oracle the container can actually reach: the live aif API. §7d.1 pins two probes
(required-field discovery + route-existence discovery) with the dispatcher's own 2026-08-09 outputs
recorded for reproduction, not for trust.

Run-3 blockers bound as corrections: §7d.2 `runtimeId`+`providerId` required in the create body;
§7d.3 the ping route is `POST /runtime-profiles/validate` (the guessed `/<id>/v1/messages` → 404);
§7d.4 §7b #1 still undelivered (the helper verifies reachability but never establishes it);
§7d.5 companion install is prose in a field `engine.sh:18` early-returns past; §7d.6 (MAJOR) the
suite mocks the non-existent endpoint and a body-blind 201, so §4.1 passed over §7d.2/§7d.3 —
the stub becomes fail-closed with a paired-negative that fails on the run-3 helper, and
`it.fails()`-as-delivery is banned.

Adds T-BDU-D: unreachable instructed source → probe, else PARK; never substitute a plausible shape.

Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.1 — name the real base-URL var, and the container trap under it

Own cold-QA of #1319 caught two defects in §7d.1. (1) It invented `$AIF`; the helper actually
resolves `AIF_URL="${RUNTIME_BRIDGE_AIF_URL:-http://localhost:3009}"`
(scripts/getff-glm-onebutton.sh:94), so the probe block now uses that name.

(2) The more load-bearing half: that shipped default is unreachable from the agent container.
Measured 2026-08-09 from aif-handoff-agent-1 — `http://localhost:3009/runtime-profiles` → curl
exit 7 (000), `http://api:3009/runtime-profiles` → 200. `RUNTIME_BRIDGE_AIF_URL` is unset in the
container, so an in-container run of the run-3 helper could not have reached aif at all, which is
consistent with the suite mocking every call (§7d.6). §7d.1 now instructs the export for probing,
explicitly forbids changing the shipped consumer default, and §4 item 1 must quote the base URL
alongside any live end-to-end transcript.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.3/§7d.4 — fire my own falsifier; the ping is two halves

§7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through
`POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a
reachability check. The dispatcher fired that falsifier itself rather than shipping the trap:

- for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey`
  and `baseUrl` are non-empty — no network call (aif-handoff
  packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the
  Qwen3.8-Max-Preview profile returned ok:true sub-second.
- `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue
  (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider.

So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves —
`/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK
from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is
stated as dispatcher-authored and operator-overridable, matching §7b.

§7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and
`resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env
(packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS
proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 9, 2026
…, name frozen against the live registry (#1336)

* docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev)

Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A +
description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded
with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1
iteration, external cold fidelity mandatory; Park-4 CC detection =
CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park
did not fire (live runtime-profiles probe, no substring collision); Park-6 ship
the full functional set (create-worktree.sh + worktree-node-modules.sh +
link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added.
Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3).

* docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract

Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300).
The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores
only the env-var NAME in the profile, #4(ii) writes the value to
~/.config/getff/glm.env, and #3 requires the validation ping to run through the
created profile. Verified against source: aif resolves the key from its own runtime
process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219,
:247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) —
so glm.env is a dead end as written and #3 is unreachable without dereferencing the
value, which #1 forbids.

§7b closes it by binding the OUTCOME (value reachable in the aif runtime process env
under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure)
while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked
as a dispatcher-authored completion, not an operator decision — the operator can
override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full
body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id
(:90), both defaults halves, ping through the profile, 10-skills.sh untouched.

§4.1 adds the missing host-verify contract — the stage ships a shell helper, an
install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host
surface destination-environment-verification.md §1 exists for; the umbrella had no
contract on any of its six kickoffs (exit 2).

Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an
ambiguity in an existing stage kickoff against in-repo source citations.

* docs(beta-delivery-ux): S4 kickoff §7d — retract the unreachable host path, bind run-3 blockers

§7d.0 retracts the §1 + §7a #1 instruction to re-read
`~/code/aif-handoff/packages/api/src/routes/runtimeProfiles.ts` at stage entry. That path is
host-only: the agent container mounts `$PROJECTS_DIR` (= `$PROJECTS_HOST_ROOT/projects`), so the
repo root holding `packages/api/` sits one level above the mount. The instruction was unexecutable
and run 3 guessed instead of parking — same class as state.md §2 decision 14.

Replacement is an oracle the container can actually reach: the live aif API. §7d.1 pins two probes
(required-field discovery + route-existence discovery) with the dispatcher's own 2026-08-09 outputs
recorded for reproduction, not for trust.

Run-3 blockers bound as corrections: §7d.2 `runtimeId`+`providerId` required in the create body;
§7d.3 the ping route is `POST /runtime-profiles/validate` (the guessed `/<id>/v1/messages` → 404);
§7d.4 §7b #1 still undelivered (the helper verifies reachability but never establishes it);
§7d.5 companion install is prose in a field `engine.sh:18` early-returns past; §7d.6 (MAJOR) the
suite mocks the non-existent endpoint and a body-blind 201, so §4.1 passed over §7d.2/§7d.3 —
the stub becomes fail-closed with a paired-negative that fails on the run-3 helper, and
`it.fails()`-as-delivery is banned.

Adds T-BDU-D: unreachable instructed source → probe, else PARK; never substitute a plausible shape.

Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.1 — name the real base-URL var, and the container trap under it

Own cold-QA of #1319 caught two defects in §7d.1. (1) It invented `$AIF`; the helper actually
resolves `AIF_URL="${RUNTIME_BRIDGE_AIF_URL:-http://localhost:3009}"`
(scripts/getff-glm-onebutton.sh:94), so the probe block now uses that name.

(2) The more load-bearing half: that shipped default is unreachable from the agent container.
Measured 2026-08-09 from aif-handoff-agent-1 — `http://localhost:3009/runtime-profiles` → curl
exit 7 (000), `http://api:3009/runtime-profiles` → 200. `RUNTIME_BRIDGE_AIF_URL` is unset in the
container, so an in-container run of the run-3 helper could not have reached aif at all, which is
consistent with the suite mocking every call (§7d.6). §7d.1 now instructs the export for probing,
explicitly forbids changing the shipped consumer default, and §4 item 1 must quote the base URL
alongside any live end-to-end transcript.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.3/§7d.4 — fire my own falsifier; the ping is two halves

§7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through
`POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a
reachability check. The dispatcher fired that falsifier itself rather than shipping the trap:

- for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey`
  and `baseUrl` are non-empty — no network call (aif-handoff
  packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the
  Qwen3.8-Max-Preview profile returned ok:true sub-second.
- `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue
  (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider.

So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves —
`/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK
from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is
stated as dispatcher-authored and operator-overridable, matching §7b.

§7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and
`resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env
(packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS
proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): R1 stage kickoff — npm release mechanics (A6)

R1 is the last unstarted stage of the umbrella (S1-S5 all merged). This is its
dispatch input, scoped from the spec (§4 A6 `:278-284`, §11 F-C' `:467-468`) and
the binding input `launch-preannounce-track/s6-u10-handoff.md`.

Three things the kickoff decides, because a worker cannot:

1. Dispatchability (§0.1) — R1 IS aif-dispatchable. `private: true` STAYS on
   `packages/core/package.json` for the whole stage; npm refuses to publish a
   private package, so the publish guard is mechanical rather than a reminder
   (attention-is-not-a-mechanism §1). Verified `npm pack --dry-run` works on a
   private package, so the tarball loop runs with the guard armed.

2. Name architecture (§0.2) — probed the live registry, not the spec's prose:
   unscoped `getff` ALREADY EXISTS at 0.0.1, published 2026-06-23 by the
   maintainer as a reservation stub. So the registry-side freeze is done, 0.0.1
   is spent as a version, and `@getff` SCOPE ownership stays unproven (a 404 on
   `@getff/core` proves the package is free, not the scope) — that is an entry
   probe with a STOP attached.

3. Scope boundary (§0.3) — R1 does NOT execute the `@rules-as-tests/*` rename.
   Measured blast radius 33 tracked files / 55 occurrences; umbrella §3 puts the
   rename in U9 (post-announce), and the acceptance gate is name-independent
   because `npm i <tarball>` installs by path.

Also recorded: two gates of the binding input's §0 are SUPERSEDED by the
2026-07-23 amendment (U9 no longer the rename window; the private-drop ordering),
and the honest gap R1 does not close — U10's `npx getff init` gate needs a package
with `bin: getff`, which does not exist in this repo and which A6 does not ask R1
to build.

Host-verification contract declared (no opt-out): the new tarball cell plus the
existing pnpm start cell, which is the file-copy fallback the §4 rollback doctrine
requires to keep working. `scripts/host-verify.sh --list` parses both, exit 0.

Prior-art: skipped — dispatch-input authoring only; a markdown kickoff adds no
capability, no dependency, and no code module (CLAUDE.md doc-file carve-out).

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Sep 2, 2026
…T14 (#1565)

The entry-probe table pinned the landing container base at `733197e`. That is the
pre-PR-#4 commit, i.e. exactly the staleness this session verified and had already
seen fixed before dispatching: the base clone, the container, and
`gh api repos/artyhoo/getff-landing/commits/main` all read `b65ff4b534`.

So the kickoff text and the tree the worker actually gets disagreed at row 1 — the
one row whose job is to tell the worker what a correct starting state looks like,
and which instructs a STOP-and-report on a base anomaly. Best case the worker
distrusts row 1 from its first probe; worst case it reports a false anomaly. Pin
corrected, with the three surfaces the SHA was verified against named inline so the
next reader can re-check rather than re-trust.

Two additions in the same pass, both aimed at gate row 4 (the physical `/docs/<slug>.md`
twin), which is where an assumed route is most likely to survive as prose:

- T10 — enumerate `ls -R out` and paste the route inventory BEFORE asserting which
  routes exist.
- T14 — coverage bounds the verdict: six rows passing with two skipped is «coverage
  insufficient to conclude», not leg A green. The report must say which ran.

T12 now also names the two symbols that shorten the docs search (the static search
client under `fumadocs-core/search/client`, and the llms-text helper commonly shown
as `getLLMText`) while explicitly requiring both to be confirmed against current
docs — a rename is itself a doc-drift finding, and this line must not become the
memory it warns against.

The base-pin defect and the T10/T14 gap were both caught by session
zen-panini-3ac9cf-c3 reviewing the merged kickoff after standing down from this
stage.

Verification: principle 12 green (9/9); markdownlint 0 errors; prettier clean;
extractProfileHint(<this file>) -> undefined.

Prior-art: skipped -- corrections inside one dispatch-input document; no new
dependency, no code change, no new capability under packages/.

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Sep 2, 2026
…healthy worktree (#1567)

Entry-probe row 1 said «if your worktree is dirty at start … STOP and report».
Measured in this stage's own live worktree, a fresh aif task worktree is never
clean: it carries exactly two untracked entries that aif itself puts there.

    $ docker exec aif-handoff-agent-1 git -C <task worktree> status --short
    ?? .ai-factory/
    ?? AGENTS.md
    $ docker exec aif-handoff-agent-1 git -C <task worktree> log --oneline -1
    b65ff4b Merge pull request #4 from artyhoo/feature/bs-pre-smoke-cf8942

So the literal instruction fires a STOP on the worker's very first probe, on a
perfectly healthy tree. On a stage that forbids fix-forward and whose whole output
is a STOP-or-GO verdict, a spurious STOP is not a harmless false alarm — it is
indistinguishable in the report from the real thing this stage exists to detect.

The clause the row was reaching for is real but narrower: it is the BASE CLONE that
is unsafe, and the isolation actually works — the base clone shows `M package.json`
plus ~16 untracked installer entries, and none of that reaches the per-task
worktree, which is cut clean from HEAD. Row 1 now says so: the two-entry untracked
set is expected and is not a STOP; STOP on a modified or deleted TRACKED file, or on
finding yourself in `/home/www/getff-landing` itself; and in every case stage only
paths under `smoke/`, never `git add -A`.

Found by inspecting the live worktree at `planning` rather than from the kickoff
text — the same class as the stale base pin in #1565, and the second time this stage
shipped a row 1 that disagreed with the tree the worker actually gets. Row 1 is the
one row a worker acts on before it has any other context, which is exactly why it
keeps being worth measuring against reality instead of reading.

Verification: principle 12 green (9/9); markdownlint 0 errors; prettier clean.

Prior-art: skipped -- correction inside one dispatch-input document; no new
dependency, no code change, no new capability under packages/.

Co-authored-by: Test <test@example.com>
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