RELEASE
planning/2.8.0/audits/TOKEN_USAGE_AUDIT.md
sha256 a6a40cdce62e61c0 · 49213 bytes ·
original held in the private archive
# RobCo U.O.S. — Token-Usage Audit
> **FILED UNDER 2.8.0 — PRODUCED-BY, NOT MEASURED-VERSION.** This document *measures* an earlier codebase
> (see its baseline stamp below), but it was *produced* as part of the audit/planning phase that became the
> 2.8.0 overhaul. Planning artifacts are filed by the release whose work produced them, never by the version
> they discuss. The version in the title or baseline is the code it examined, not the folder it belongs in.
> (A 2.5.0 and 2.6.0 folder do exist, holding feature designs for features that shipped in those releases. There
> is no 2.7.0 folder because no artifact has been identified as produced during 2.7.0.)
> **[ARCHIVE-class snapshot — not current truth.]** Per the 2.8.5 U-B1 doc-maintenance model (see `CLAUDE.md`'s
> Reference Pointer Index), this doc is frozen as of the date below and is never updated to track current code. It
> audited a codebase roughly 30% smaller than today's (pre-`test-console.js`, pre-OCR, pre-Diagnostic-Shell,
> pre-CHASSIS/Overseer). Reuse its structural reasoning as historical input only — re-verify every measurement and
> file-split proposal against current code before acting on it.
**Scope:** How to cut token usage across the entire RobCo development workflow **without** downgrading coding quality or thinking depth.
**Status:** Read-only analysis. No code, protocol, or doc changes proposed for implementation here — recommendations only.
**Branch audited:** `origin/dev` @ `ff2450a` (v2.7.0-r4). Measured directly from source.
**Verification status:** Every quantitative claim below was measured directly against the code, then **two independent second-model (Sonnet) red-team passes re-verified the numbers and pressure-tested the reasoning** — the first on the HALF-1/HALF-2 magnitudes, the second on the implementation-safety of the three greenlit changes (its findings are in the "Red-Team Review" section and marked **[2nd-model]**). Both **corroborated the headline conclusions and every change-safety verdict**; several secondary numbers were corrected, and the passes surfaced **a genuine correctness bug** (stale `robco_v7` in RULES.md) plus several cascade conditions. Treat the orchestration-layer (HALF 2) magnitudes as reasoned estimates from the given inputs, not measured, since the Dispatch layer is not visible from the repo.
**Corrected measurements (red-team verified):** JS runner = **618 KB** (not 604); PS runner = **527 KB** (not 516); JS runner `readFile` calls = **322** (not 287); PS string-matches ≈ **1,159**, JS string-matches ≈ **1,828**; PS behavioral assertions = **exactly 30** across 4 Node-sandbox blocks (not "~33"); ESLint scope ≈ **16 files** (not ~23); only **3 of the 4** Playwright checks spin up an HTTP server (`render-check.mjs` uses `file://`); **repomix excludes CLAUDE.md and RULES.md** (so the doc-trim savings apply to direct reads, not the repomix pack).
**Hard constraint on every recommendation below:** efficiency only. Nothing here may weaken the full-gate's final guarantee, the notify-on-everything rule, the free/BYO-key model, game-agnostic feature code, or the self-improving guard ratchet (Protocol 36b). Where a recommendation touches the gate, it preserves the *push-boundary* guarantee bit-for-bit and only moves cost off the *iteration* path.
---
## Executive Summary
Token spend in this project splits into two layers:
- **HALF 1 — Repo/build cost** (what an AI must read, edit, and run per unit). Dominated by **two enormous, fully-duplicated test runners** (`tests/robco-diagnostics.js` = 14,309 lines / 604 KB; `.ps1` = 8,078 lines / 516 KB), which **173 of 289 commits (~60%) edited in lockstep**, plus **five always-touched docs** (CHANGELOG/README/ARCHITECTURE/CLAUDE/RULES changed in 100/100 recent commits). The runners are **static source-text matchers** (1,789 string-match assertions, 287 file-reads in the JS runner; the PS runner is a near-mechanical mirror with 1,185 string-matches and only ~33 real JS executions). Protocol 15 *forces* the two runners to identical coverage — so the PowerShell runner provides **near-zero unique coverage** at ~2× authoring cost.
- **HALF 2 — Orchestration cost** (Dispatch layer). Dominated by **transcript polling** (every 5–10 min for hours across ~90 commits) and **marathon sessions** (600–1,100+ turns, where per-turn cost grows with accumulated context — superlinear). These two together almost certainly dwarf HALF 1 in absolute tokens.
**The single biggest lever in each half:**
- HALF 1: **Demote the PowerShell runner to a thin parity/count check** (or auto-generate it) instead of a hand-authored 8,078-line twin. Saves ~2× test-authoring context on ~60% of commits with **zero coverage loss** (Protocol 15 guarantees the coverage is identical by construction).
- HALF 2: **Stop intermediate transcript polling**; rely on completion signals + the final report. This is the largest single cumulative sink in the whole workflow.
**Ranking of the biggest sinks (rough, descending):**
1. (H2-A) Transcript polling over multi-hour builds — *largest cumulative sink*.
2. (H2-B) Marathon-session context re-processing (superlinear per-turn growth).
3. (H1-1/2) Dual hand-authored test runners (~1.1 MB, edited on 60% of commits).
4. (H2-C) Per-unit full gate + browser verify on every iteration.
5. (H1-6) Five always-touched docs + duplicated Suite-list mega-paragraph.
6. (H2-D) Phantom duplicate sessions (whole units done twice).
7. (H1-4) Four separate Chromium launches in the gate + overlapping manual preview verify.
8. (H1-5 gate.js) Parity step re-runs both runners a second time per gate.
9. (H2-E/F) Verbose dispatch prompts/reports; redundant `git fetch`/`reset`/re-reads.
---
# HALF 1 — Repo / Build Cost
## H1-1. The gate: what runs, how often, dominant cost
**`npm run gate` (full, `scripts/gate.js`), in order:**
1. ESLint `--max-warnings 0` (scans ~23 JS/MJS files)
2. Prettier `--check .` (scans whole tree)
3. Node runner `tests/robco-diagnostics.js` (1,557 tests) — **~0.66 s**
4. PowerShell runner `tests/robco-diagnostics.ps1` (1,557 tests) — **~5.5 s** on Windows PS 5.1 — **then a parity check that RE-RUNS BOTH runners again** (see H1-8)
5. Playwright Chromium availability probe
6. `boot-smoke.mjs` — own Chromium launch + own HTTP server
7. `render-check.mjs` — own Chromium launch (360 px & 412 px)
8. `a11y-check.mjs` — own Chromium launch + own HTTP server + axe
9. `test-html-check.mjs` — own Chromium launch + own HTTP server (runs `test.html`)
**`gate:fast`** = steps 1–4 only (lint, format, both runners, parity). This is what the **pre-commit** hook runs. The **pre-push** hook + **CI** run the full gate.
**Run frequency this build:** 289 commits in 30 days, 48 `WU-` unit commits. Each commit fires `gate:fast` (pre-commit); each push fires full `gate` (pre-push); CI re-runs full `gate` on every push to `dev` **and** on every pull_request to `dev`/`main`. So a single landed unit can trigger the full gate **3+ times** (local pre-push, CI-on-push, CI-on-PR).
**Dominant cost is NOT wall-clock** — the Node runner is 0.66 s and even the full gate is a couple minutes. **The dominant cost is the AI-context cost of reading/editing the two runner files** when a unit adds tests (see H1-2/3), plus repeated CI minutes. The gate's *execution* is cheap; the gate's *maintenance surface* is expensive.
> **Recommendation (gate):** Keep the full gate's guarantee exactly as-is at the push boundary. Apply the **tiered model** from H2-C: during iteration run a fast pre-check (lint + the changed suite subset), full gate only at the commit/push boundary. Fix the gate.js double-run (H1-8). **No guarantee is weakened** — the full gate still runs before anything reaches origin.
## H1-2. Dual-runner parity — the cost vs. the unique value
**Sizes:** JS runner 14,309 lines / 604 KB (~150 K tokens to read in full); PS runner 8,078 lines / 516 KB (~130 K tokens). **Reading both in full ≈ 280 K tokens.**
**Edit frequency:** `--follow` history shows the JS runner touched in **187 / 289 commits** and the PS runner in **180 / 289**, with **173 commits touching BOTH**. So ~60% of all commits edit two files totaling ~1.1 MB, in lockstep.
**What unique value does the PowerShell runner add?** Effectively **none by construction:**
- **Protocol 15 mandates identical coverage** — same suites, same per-suite counts, same 1,557 total. The gate's parity check enforces equality of the total. So the PS runner *cannot* legitimately test anything the JS runner doesn't.
- **Mechanism is the same:** the PS runner is a static source-text matcher — 1,185 `Select-String`/`-match`/`.Contains`/`Get-Content` operations and only ~33 real JS executions. It reads the same `js/*.js` source and asserts the same string/regex patterns the JS runner does. It is a **re-implementation in a second language of the same greps**, not an independent oracle.
- **Is it "what the hook runs"?** No — the pre-commit hook runs `gate:fast`, which runs **both**. On a Windows dev box, Node executes the JS runner and PowerShell executes the PS runner; neither is privileged. The genuinely independent behavioral checks are `test.html` + the Playwright trio (real browser execution), not the PS runner.
- **Theoretical unique value:** a different regex/encoding engine *could* catch a Windows-1252 vs UTF-8 corruption the Node `String.includes` misses. But that exact risk is already covered by **Suite 90** (the U+FFFD / `â€`/`â–` mojibake guard) running in the JS runner. The PS twin adds defense-in-depth here that is marginal at best.
**Net:** the PowerShell runner costs ~2× test-authoring context on ~60% of commits (every new/changed suite is hand-written twice and kept at parity) while delivering near-zero coverage the JS runner doesn't already have.
> **Recommendation (highest-value HALF-1 lever) — pick one, in order of preference:**
> 1. **Demote PS to a thin parity wrapper.** Replace the 8,078-line hand-authored twin with a small script that (a) runs the Node runner, (b) asserts the `ALL N TESTS` total, and (c) optionally re-checks the handful of encoding/cross-platform invariants that genuinely benefit from a second engine (the Suite-90 class). String-match coverage is **identical** (Protocol 15's guarantee is *defined* by the JS runner). **Red-team caveat — must preserve the 4 Node-sandbox blocks:** the PS runner's only genuinely independent execution is 30 behavioral assertions across 4 VM-sandbox blocks (faction-standing matrix, `migrateState`, `scoreZoneForLoc`, generic threshold matrix — the Suite 2a/8/16/77 class). These re-execute extracted functions as a true second oracle. A naïve count-only wrapper would silently **drop** those 30 behavioral checks. A correct thin wrapper must **migrate/preserve the 4 sandbox blocks** (they're tiny — ~30 assertions) while discarding the ~1,500 duplicated string-greps. With that caveat honored, the gate's final guarantee is unchanged and ~half the test-authoring context disappears on ~60% of commits.
> 2. **Auto-generate** the PS runner from the JS runner (a transpile/codegen step) so it is never hand-edited. Same coverage, zero manual parity burden.
> 3. **If the owner wants a true second oracle:** keep PS but **only for the cross-platform/encoding-sensitive suites** (the class Suite 90 covers), not as a full 1,557-test mirror.
> **Guarantee:** none of these reduce what is *verified*; they remove duplicate *authoring*. The thinking depth of the tests is in the JS runner and is untouched.
>
> *Caveat / what to confirm before acting:* this changes Protocol 15's meaning, so it requires an explicit owner decision (it is a protocol change, out of scope for this audit to make). Confirm CI's `pwsh` path and the nightly both-runner sweep are updated in lockstep so the parity contract is redefined, not silently broken.
## H1-3. Test-suite size (1,557 tests) — behavioral vs structural
**Composition (JS runner):** ~1,379 `assert`/`pass` calls, **1,789 string-match assertions** (`.includes`/`.match`/`.test`/`RegExp`), **287 `readFile()` calls** across 130 suites. The overwhelming majority are **static source-text guards**: "read `ui-render.js`, assert it contains class `tracker-row`," "read `index.html`, assert the panel has `<h2>`," "read `sw.js`, assert no `clients.claim()`." Genuinely behavioral tests (constructing state, running `autoImportState`/clamp/migrate logic) are a minority and largely live in `test.html` (the real-browser execution mirror) rather than the static runners.
**Why this matters for tokens:** static guards are individually cheap to run but **collectively enormous to maintain** — 130 suites, each re-reading source files (287 reads = source files read many times over) and asserting brittle string patterns. Every refactor that renames a class or moves a selector can ripple into dozens of string-match guards across **both** runners. The 14 K + 8 K line files are large *because* each guard is spelled out longhand twice.
**Redundancy / consolidation opportunities:**
- Many suites assert "function X is defined" + "function X is called from loadUI" + "X references Y" as 3–8 separate string greps. These could be table-driven (one data array of `{fn, calledFrom, references}` iterated by a single helper) instead of dozens of bespoke `assert(src.includes(...))` lines — shrinking the file and the per-edit surface without losing a single assertion.
- The "every panel has `<h2>`", "every sub-panel has `data-sub-id`", "every tracker uses `.tracker-row`" classes (Suites 88, 71, etc.) are already partly table-driven; extending that pattern repo-wide would cut runner size materially.
> **Recommendation:** Refactor the static guards toward **table-driven assertion helpers** (one helper + a data table per guard family) rather than longhand per-line greps. This is a pure restructuring: **same number of logical assertions, same coverage**, far less text to read/edit. Combined with H1-2, the per-unit "touch the test files" cost drops sharply. **No coverage or guard removed** — Protocol 20 source-invariant guards remain; they're just expressed more compactly. (Lower priority than H1-2 because it's a larger refactor; do it opportunistically.)
## H1-4. Browser verification — overlap & redundancy
The full gate launches **four separate Chromium instances** (`boot-smoke`, `render-check`, `a11y`, `test-html-check`); **three of the four** spin up their **own HTTP server** (`boot-smoke`, `a11y`, `test-html-check`) — `render-check.mjs` loads over **`file://`** instead. On top of that, **most units also did manual `preview_*`/Playwright verification** during development, which overlaps with `boot-smoke` (does it boot? console errors?) and `render-check` (360/412 overflow?).
> **Red-team caveat — manual preview is NOT fully redundant.** Because `render-check.mjs` runs at `file://`, it does **not** exercise service-worker registration, ES-module imports, or SW caching at mobile sizes (those need an HTTP origin — only `boot-smoke` provides one, and it doesn't check mobile overflow). So manual HTTP-origin verification at 360/412 px covers a real gap the gate's render-check leaves open. **Do not** drop manual preview wholesale; drop only the portion that genuinely duplicates `boot-smoke`/`render-check` (boot success, console errors, desktop overflow), and keep an HTTP-origin mobile check for SW/module-sensitive changes.
**Overlap map:**
- `boot-smoke` (boots, zero console errors, body rendered) ↔ manual preview "does it load / any console errors" — **redundant**.
- `render-check` (360/412 px overflow + focus-zoom) ↔ manual preview resize checks (Protocol 10) — **redundant**.
- Each of the 4 mjs checks re-launches Chromium cold (~1–3 s each) and several re-serve HTTP independently — **shared-context opportunity**.
> **Recommendation:**
> - **Consolidate the four Playwright checks into one Chromium launch + one HTTP server** that runs boot-smoke + render-check + a11y + test.html sequentially in the same browser context. Saves ~3 cold browser starts per gate (CI minutes + local wall-clock) with **identical assertions**.
> - **Lean on the gate's automated browser checks instead of redundant manual preview verification** during routine units. Reserve manual `preview_*` for changes the automated checks don't cover (new visual layouts, interaction flows). Protocol 10's render guarantee is already delivered by `render-check.mjs` in the gate — manual re-verification of the *same* 360/412 overflow invariant is the redundant part. **The Protocol 10 guarantee is preserved** (the gate still runs render-check); only the duplicate manual pass is trimmed.
## H1-5. Codebase context cost — how much a unit pulls in
**Source files a unit may need to read:** `ui-core.js` (2,759), `ui-render.js` (2,689), `api.js` (1,673), `reg_nv.js` (1,866), `db_nv.js` (1,050), `ui-audio.js` (1,078), `reg_fo3.js` (1,086), `state.js` (828), `db_fo3.js` (702), `ui-saves.js` (672), `cloud.js` (639) + the two runners (22,387). `index.html` is 2,464 lines; `terminal.css` 2,782.
A typical feature unit (median 10 files changed) touches a render file + `api.js` + `state.js` + `index.html` + **both runners** + **five docs**. The **runners + docs alone** (~26 K lines) can dominate the unit's read/edit context even when the actual feature code change is small.
> **Recommendation:** The biggest context reducers are H1-2 (kill the PS twin) and H1-6 (trim always-touched docs). Beyond that: prefer **targeted Read ranges + Grep** over full-file reads of the 2 K–2.7 K-line UI files (often already done, but worth codifying), and avoid re-reading a file already in session context (see H2-F). No quality impact — same code, fewer redundant loads.
## H1-6. Docs — five files touched on every commit
**CHANGELOG.md (1,128), README.md (334), ARCHITECTURE.md (990), CLAUDE.md (601), RULES.md (601)** — all changed in **100/100** of the last 100 commits (Protocol 2/2a). Two structural duplications:
- **CLAUDE.md and RULES.md are near-identical twins.** The entire protocol body is maintained twice. **Red-team correction:** the two files differ by exactly **one substantive content line, and it is a stale fact, not a title** — RULES.md (line ~594) still says `robco_v7` while CLAUDE.md correctly says `robco_v8` (the current live localStorage key). See the separate correctness finding below — this is a bug, not cosmetics.
- The **"Test suite:" Architecture-Quick-Reference mega-paragraph** that enumerates all 130 suites is **~4,756 words / ~40 K characters (~10 K tokens)** and lives in **both** CLAUDE.md and RULES.md. Every new suite appends prose to this paragraph **twice**, and the whole paragraph is re-read whenever either file is edited. **Red-team caveat:** `repomix.config.json` *excludes* both CLAUDE.md and RULES.md, so this paragraph is **not** in the repomix pack — its cost is paid on **direct session reads/edits** of those two files, not on repomix loads. The H1-7 repomix saving and this doc saving are therefore *separate* cost paths, not the same one.
> **⚠ Correctness finding (not just efficiency) — RULES.md is stale.** RULES.md states `robco_v7` where the codebase uses `robco_v8`. Any agent that reads RULES.md for architectural guidance gets the **wrong state-persistence key**. This is a latent correctness bug in a protocol-enforcing file and should be fixed regardless of the token discussion. It also *demonstrates* why the duplicate-twin model is risky: the copies have already drifted. (Flagged for the owner; not fixed here — this audit is read-only.)
> **Recommendation:**
> - **Make RULES.md a pointer to CLAUDE.md** (or vice-versa) instead of a full duplicate — or generate one from the other. Eliminates a 601-line lockstep-maintained twin. (Protocol-level change → owner decision.)
> - **Stop growing the Suite-list mega-paragraph.** The authoritative per-suite descriptions already live in the runner files; the 10 K-token narrative duplicate in CLAUDE/RULES is the costliest doc artifact to maintain. Replace it with a one-line "130 suites — see `tests/robco-diagnostics.js` headers" pointer, or auto-generate it. **No information lost** (the runners are the source of truth); the redundant prose copy is what's cut.
> - These are the cheapest high-frequency wins after H1-2: they fire on essentially *every* commit.
> *Guarantee:* Protocol 2/2a's intent (docs never drift) is preserved — a generated/pointer artifact can't drift because it isn't hand-maintained.
## H1-7. repomix context pack
`repomix.config.json` includes `js/**`, `tests/**`, `*.md`, etc. A full pack is **~48,816 lines**; the **two runners alone are 22,387 lines (~46% of the entire pack)**. Any AI that loads the repomix output for "full repo context" is spending ~half its context budget on duplicated static-test text of low value for *coding* tasks.
> **Recommendation:** Add an option/profile that **excludes (or includes only one of) the runners** from the coding-context pack — keep a separate "tests" pack for when test work is the task. Solves itself largely if H1-2 lands (one runner instead of two ≈ −130 K tokens off every full pack). **No quality impact** — the source code and docs the AI reasons over are unchanged; only the duplicated test text is trimmed from the *default coding* context.
>
> *Red-team note:* this is a **distinct** cost path from H1-6. CLAUDE.md/RULES.md are already excluded from repomix, so trimming the Suite-list paragraph helps **direct reads** of those files, while excluding a runner helps the **repomix pack**. Don't double-count the two savings.
## H1-7b. Missing sinks the first pass omitted (red-team additions)
- **Embedded CSV data blobs in `db_nv.js` (1,050 lines / ~55 KB) and `db_fo3.js` (702 lines / ~34 KB).** All game data lives as template-literal CSV strings (~22 K tokens combined). They're read by ~14–18 data-integrity suites (78–83). When a unit touches a CSV guard it must load a multi-KB data file *and* the corresponding test suite in *both* runners. Touched in 18 / 14 commits respectively — infrequent but heavy when it happens. **Recommendation:** treat the data files like the runners for context purposes — read targeted ranges, not the whole file; and the H1-2/H1-3 runner fixes also shrink the per-edit surface for these data-guard suites.
- **Prettier parses the 14,309-line JS runner on every commit.** `.prettierignore` excludes `node_modules`, `dist`, min files, and two scripts — but **not** the runners. `prettier --check .` fully parses the 618 KB JS runner each commit (it skips `.ps1` as an unknown extension). Minor wall-cost, but it's a named gate-execution cost, not just context. **Recommendation:** if the runners are kept as-is, consider whether they need Prettier formatting enforcement at all (they're machine-shaped); excluding them trims gate time. (Low priority.)
- **The gate's parity check compares only the *total* count, not per-suite counts.** `gate.js` lines 82–87 grep `ALL N TESTS` and compare totals. Two runners could have identical totals with different per-suite distributions (one suite +3, another −3) and pass. Protocol 15's per-suite-parity intent is enforced only by author discipline, not by the gate. **Relevance:** any move that reduces PS-runner fidelity (H1-2) should not lean on this check as a safety net — it's weaker than it looks. (Correctness-of-guarantee note, not a token saving.)
## H1-8. gate.js parity step re-runs both runners (concrete waste)
In `scripts/gate.js`, step 3 runs the Node runner, step 4 runs the PS runner, **then the parity block calls `capture('node …')` and `capture('pwsh …')` again** to grep the `ALL N TESTS` totals. The totals were **already in the step-3/step-4 output**. So every full gate runs **Node 2× and PS 2×** — on Windows PS 5.1 that's an extra ~5.5 s + ~0.66 s of pure waste per gate, multiplied by every pre-push + every CI run.
> **Recommendation:** Capture the total from the **existing** step-3/step-4 output (have `run()` tee stdout) and compare those, instead of re-invoking both runners. Saves one full Node + one full PS execution per gate. **Identical guarantee** — the parity assertion is unchanged, it just stops paying for it twice. *(This is a script-level efficiency fix, not a protocol change — lowest-risk item in HALF 1.)*
## H1-9. CI — redundant runs
- **`ci.yml` triggers on BOTH `push` and `pull_request`** for `main`/`dev`. A `dev→main` PR re-runs the **entire full gate** (4 browser launches) on commits already gated on the `dev` push. For a release PR this is double work.
- **`nightly-tests.yml`** re-runs both runners on a schedule — this is *intentional* drift-detection and cheap (no browser), keep it.
- **`deploy-staging.yml`** runs on every `dev` push (build + Cloudflare deploy) — necessary for staging; not redundant with CI's gate.
> **Recommendation:** Scope `ci.yml`'s `pull_request` trigger to avoid re-running the full gate when the same SHA already passed on the source branch (e.g. only run PR gate if the head SHA hasn't already been gated, or rely on branch-push CI + a lighter PR check). Saves a full-gate CI run per release PR. **No guarantee weakened** — the SHA that merges has still passed the full gate.
---
# HALF 2 — Orchestration / Workflow Cost
*(These are given inputs from the Dispatch layer, which this audit cannot see directly. Analyzed and prioritized below.)*
## H2-A. Transcript polling — **the largest cumulative sink**
**Mechanism:** the orchestrator called `read_transcript` on each running session every ~5–10 min for hours. Each call returns transcript content, and over a multi-hour, ~90-commit build that content **grows** as the session grows — so late-build polls each return a large transcript, and they happen dozens of times per session across multiple concurrent sessions. Cost ≈ (poll frequency) × (hours) × (sessions) × (growing transcript size). This is plausibly the **single biggest line item in the entire workflow**, larger than any repo-side cost, because it re-pays for transcript content repeatedly with no new information most of the time.
> **Recommendation:**
> - **Stop intermediate polling. Rely on a completion signal + the session's final report.** Protocol 9 already *requires* a detailed end-of-task report; the orchestrator should consume that, not poll the in-progress transcript.
> - If liveness must be checked, poll **far less often** (e.g. only when a long-running step is expected to finish) and request **only a status delta / last-N-lines**, never the full transcript.
> - Prefer an **event/callback completion model** over time-based polling.
> **Guarantee preserved:** notify-on-everything is about the *owner* being told what happened (the final report), not about the orchestrator re-reading transcripts mid-flight. Eliminating polling doesn't reduce what the owner is told or the work's quality — it removes redundant reads of already-seen content.
## H2-B. Marathon sessions (600–1,100+ turns) — superlinear re-processing
**Mechanism:** each assistant turn re-processes the entire accumulated context. In a 1,000-turn session, the last turns each carry ~1,000 turns of history → per-turn cost grows roughly linearly with turn count, making **total session cost grow ~quadratically** with length. One session hit "prompt too long," the hard ceiling of this curve.
**Tradeoff vs. rotation:** rotating to a fresh session every N units re-pays a fixed **context-load cost** (re-read the relevant files + protocols) but resets the per-turn history to near-zero. The break-even favors rotation well before the quadratic tail dominates.
> **Recommendation:** **Rotate sessions every 1–3 units** (a "chapter" of work), not every 600–1,100 turns. Rough reasoning: the re-load cost of a fresh session is bounded (the unit's files + a compact protocol digest, not the full 1,100-turn history), while the marginal cost of continuing a long session keeps climbing every turn. A 2–3 unit cadence amortizes the reload across enough work to be worth it while staying far from the quadratic tail and the prompt-length ceiling. **Quality preserved** — each unit gets a clean, focused context window (often *higher* quality than a context-saturated 900-turn session); the deep-thinking model assignment per Protocol 8 is unchanged. Pair with a **compact handoff** (the unit's acceptance criteria + the prior unit's final report) so rotation doesn't lose continuity.
## H2-C. Per-unit full gate + browser verify → tiered model
**Mechanism:** every unit ran the full 1,557-test gate (both runners) + browser verification + cache-bump check *during iteration*, not just at the commit boundary. The gate's execution is cheap in wall-clock, but when an agent runs it repeatedly mid-iteration and reads its output each time, the **output tokens** (test logs, browser logs) accumulate.
> **Recommendation (tiered, guarantee-preserving):**
> - **During iteration:** fast pre-check = lint + Prettier + **only the changed/relevant suite subset** (and skip the PS twin per H1-2). Quiet output (summary line, not full per-assertion logs).
> - **At the commit boundary:** the **full** `gate:fast` (both runners until H1-2 lands).
> - **At the push boundary:** the **full** `gate` (browser checks included) — **unchanged**.
> **The final guarantee is identical** — nothing reaches origin without the complete gate. Only the *redundant mid-iteration* full runs are removed. This is exactly Protocol 36's existing fast/full split, extended one notch finer for the iteration loop.
## H2-D. Phantom-spawn waste (duplicate sessions)
**Mechanism:** `start_code_task` "timeouts" silently spawned a duplicate session that redid an entire unit — pure 100% waste (whole units executed twice, then one discarded, plus the cleanup of the collision).
> **Recommendation:**
> - **Idempotency guard / dedup on spawn:** tag each unit with a unique key; before spawning, check for an existing live session with that key and attach instead of creating a second.
> - **Treat a spawn "timeout" as ambiguous, not failed** — confirm the session didn't actually start before retrying (the timeout was likely a slow ack, not a non-start).
> - **Protocol 12 (no concurrent pushes)** already forbids two sessions pushing the same repo; extend the same single-flight discipline to *spawning*.
> **Guarantee:** none affected — this is pure waste elimination.
## H2-E. Verbose dispatch + notify-everything
**Mechanism:** every unit gets a long dispatch prompt (often restating context the session could derive) + a detailed report. The **notify-on-everything requirement is a hard constraint and stays** — the owner must be told what happened on every task/push (Protocols 9).
> **Recommendation (trim verbosity, keep coverage):**
> - **Dispatch prompts:** stop restating standing context the session already has (protocols, repo layout, branch model). Send the *delta*: this unit's acceptance criteria + the specific files/selectors + anything non-obvious. A compact "protocol digest" reference replaces re-pasting rules.
> - **Reports:** keep notify-on-everything, but the *report* can be the mandated mobile-format summary (Protocol 9 already specifies: one-line lead, what changed, how to test) **without** re-dumping diffs/file paths/code. The owner gets the same information density; the token cost of restating the diff drops.
> **Guarantee:** notify-on-everything is fully preserved — the owner is still told about every task and push; only redundant restatement is trimmed, which Protocol 9 already discourages (it asks for short, scannable, no-code-dump reports).
## H2-F. Redundant tooling
**Mechanism:** repeated `git fetch` + `reset --hard origin/dev` per unit, re-reading files already in context, large raw command outputs piped back in full.
> **Recommendation:**
> - **`git fetch`/`reset` once per session**, not once per unit, unless a concurrent push is suspected (Protocol 12 already serializes pushes, so the working tree rarely changes underneath a single session).
> - **Don't re-read a file already read this session** unless it was edited (the harness tracks file state).
> - **Bound command output** — pipe test/CI logs through `tail`/summary rather than returning thousands of lines; read the summary line, fetch detail only on failure.
> **Guarantee:** none affected — same operations, fewer repetitions.
---
## Prioritized Action List (biggest savings, lowest risk first)
| # | Action | Layer | Savings | Risk | Touches protocol? |
|---|--------|-------|---------|------|-------------------|
| 1 | **Stop intermediate transcript polling; use completion signal + final report** (H2-A) | Orchestration | **Largest single sink** | Low | No (workflow) |
| 2 | **Rotate sessions every 1–3 units** instead of 600–1,100 turns (H2-B) | Orchestration | Very high (kills quadratic tail) | Low | No (workflow) |
| 3 | **Tiered gate: changed-suite subset during iteration, full gate at boundary** (H2-C) | Both | High | Low — boundary guarantee unchanged | No (extends Protocol 36 split) |
| 4 | **Dedup/idempotency guard on session spawn; treat timeout as ambiguous** (H2-D) | Orchestration | High (eliminates double-built units) | Low | No |
| 5 | **Fix gate.js parity double-run** (capture totals from existing output) (H1-8) | Build | Medium (1 Node + 1 PS run/gate) | Low — needs a `run()` stdout-tee refactor, not a one-liner | No |
| 6 | **Trim dispatch-prompt/report restatement** (keep notify-everything) (H2-E) | Orchestration | Medium-high (fires every unit) | Low | No (Protocol 9 already wants concise) |
| 7 | **`git fetch`/`reset` once per session; stop re-reading in-context files; bound output** (H2-F) | Orchestration | Medium | Low | No |
| 8 | **Demote PowerShell runner to thin parity/count check (or auto-generate it)** (H1-2) | Build | **High — ~½ test-authoring context on ~60% of commits**; −130 K tokens off every full pack | Medium — **redefines Protocol 15** | **Yes — owner decision** |
| 9 | **Consolidate 4 Playwright checks into 1 Chromium/HTTP launch; drop redundant manual preview** (H1-4) | Build | Medium (CI minutes + manual passes) | Low — same assertions | No |
| 10 | **Stop growing the Suite-list mega-paragraph; make RULES.md a pointer to CLAUDE.md** (H1-6) | Build | Medium (fires every commit) | Medium — **protocol/doc-discipline change** | **Yes — owner decision** |
| 11 | **Exclude duplicated runner from default repomix coding pack** (H1-7) | Build | Medium (−46% of full pack) | Low | No |
| 12 | **Scope ci.yml pull_request to avoid re-gating an already-gated SHA** (H1-9) — concretely via a `concurrency:` group keyed on `github.sha` and/or a `skip-duplicate-runs` action | CI | Low-medium (one full-gate CI run/PR) | Low | No |
| 13 | **Table-drive the static-guard suites to shrink runner size** (H1-3) | Build | Medium long-term | Low coverage risk, **but the migration itself is a high-Protocol-15-drift activity** (heavy edits to both runners) — do it alongside H1-2, not before | No |
| 14 | **⚠ Fix stale `robco_v7` → `robco_v8` in RULES.md** (red-team correctness find) | Build | N/A (correctness, not tokens) | Low | Touches RULES.md content — owner |
**The two highest-leverage moves overall** are #1 (stop polling) and #2 (rotate sessions) — both pure orchestration changes with no protocol impact and no quality cost. The highest-leverage *build* move is #8 (kill the PS twin), but it requires an owner decision because it redefines Protocol 15.
**Every recommendation above preserves:** the full-gate's final guarantee at the push boundary, notify-on-everything, the free/BYO-key model, game-agnostic feature code, and the self-improving guard ratchet. The savings come exclusively from removing **duplicated authoring, redundant runs, and re-read/re-polled content** — never from testing less, thinking less, or reporting less.
---
# Red-Team Review — Implementation-Safety of the 3 Greenlit Changes
*This section combines the author's own direct-code pressure-test (Opus) with an independent second-model (Sonnet) red-team that returned after the first draft and **corroborated every verdict**, adding sharper conditions (folded in below, marked **[2nd-model]**). The owner greenlit changes #9, #10, #11 only if they cause no problems and lock nothing down. Verdicts are evidence-backed against the actual runners, hooks, CI, and guards.*
**[2nd-model] cascade-guard warning that applies to ALL three changes:** several gate guards assert the *infrastructure* of the dual-runner setup, and they fail the gate if it's altered carelessly. Any of the three changes must keep these green **in the same commit**: **Suite 128.4/128.5** (`gate.js` must reference both `robco-diagnostics.js` AND `.ps1`; `nightly-tests.yml` must invoke `robco-diagnostics.(js|ps1)`), **Suite 50** (`gate.js` must keep its `--fast` flag and the `powershell` fallback), and **Suite 28**'s structural suite-name list. None of these block the changes — they just must be updated/preserved alongside.
## Change #10 — RULES.md to a pointer + drop the duplicated 130-suite paragraph
**Verdict: SAFE-WITH-CONDITIONS — but RISKY if done naively.** A *bare* one-line pointer ("see CLAUDE.md") **breaks CI immediately**; a pointer that retains the required literals is safe. No guard parses the suite-list paragraph or counts suites from RULES/CLAUDE prose (the only `Suites: N` parse is **Suite 96.3**, which reads `tests/test.html`, not these docs). Dropping the 130-suite paragraph from RULES.md breaks nothing — *provided* the slimmed RULES.md keeps the small set of literal strings the guards DO grep.
**[2nd-model] the failure is a hard CI break, not a silent skip.** Suites **89.12** and **96.8** read RULES.md *unconditionally* (`fs.readFileSync` / `readFile()` with no existence guard) and assert `Protocol 38` / `Protocol 40` are present — a pointer lacking those literals fails the gate on the first push. And **Suite 28** does **not** skip a present-but-countless file: with `rulesExists === true` but no `\d+ tests` match, `!rulesExists || (match && equal)` evaluates to `false` → the assertion **fails**. So a bare pointer with no test-count string actively fails Suite 28 (it does not skip). The conditions below are therefore mandatory, not optional.
**Complete list of literals any guard reads from RULES.md / CLAUDE.md** (both runners, at parity):
| Guard | File read | Pattern required | Conditional? |
|---|---|---|---|
| **Suite 28** (test-count sync) | RULES.md + CLAUDE.md | first `\b(\d+)\s*tests?\b` must equal canonical (`1557`) | Yes — skips if absent |
| **Suite 89.12** (game-agnostic) | RULES.md | `/Protocol 38/` present | No — `readFileSync`, throws if absent |
| **Suite 96.8** (test.html sync) | RULES.md + CLAUDE.md | `/Protocol 40/` present in **both** | No — `readFile()` exits(1) if absent |
| **Suite 128.3** (legacy-name ratchet) | RULES.md, CLAUDE.md (+ many) | must **NOT** contain the old runner name | reads-if-present |
| **Suite 52.6** | `repomix.config.json` (not the docs) | config excludes `'RULES.md'` | n/a — about the config |
**Safe shape for the pointer RULES.md:** it must still contain, as plain text, (1) **"1557 tests"** as its first `\d+ tests` match, (2) the literal **"Protocol 38"**, (3) the literal **"Protocol 40"**, and (4) no legacy runner name (trivially true). Everything else can collapse to: "Canonical rules live in CLAUDE.md. 1557 tests. Protocol 38 (game-agnostic) and Protocol 40 (test.html sync) are defined there." **CLAUDE.md stays full and unaffected.** Alternatively, update Suites 28/89.12/96.8 to read CLAUDE.md only — but keeping the four literals in the pointer is the lower-risk path (no runner edit needed).
**Tracking note:** RULES.md and CLAUDE.md are **git-tracked AND in `.gitignore`** (force-added). So Suite 28's "skip if absent" comment is stale on the dev box, and Suites 89.12/96.8 (non-conditional reads) pass only because the files exist. That *reinforces* keeping the literals — those suites hard-fail if RULES.md lacks "Protocol 38"/"Protocol 40".
**Lockdown impact: none.** All guards still fire; this *reduces* drift risk (the twins already diverged — the `robco_v7` bug).
## Change #9 — Demote / auto-generate the PowerShell runner
> **IMPLEMENTATION DECISION (2026-06-30, owner):** **#9 DECLINED in its literal transpiler form.** A faithful JS→PS codegen cannot be *proven* per-suite-equivalent in-scope — the JS runner has a **dynamic** 1557 count (~1418 static asserts + loop-generated), ~554 asserts over computed vars/lengths/filters, **21 `vm.runInContext` in-process exec blocks**, 5 `eval`s, and 27 count-multiplying loops; and `gate.js` parity compares **totals only**, so a silent per-suite distribution drift could still pass the gate (the D3 failure mode). Rather than ship an unprovable transpiler (or a Node-delegating re-emitter, which changes the runner's character), **both runners stay hand-authored and the PowerShell runner remains an independent second oracle.** The safe win #9 was really after — **#9b: excluding the large PS runner file from the default repomix context pack — WAS APPLIED** (commit `0aeb329` on `dev`: `repomix.config.json` `ignore.customPatterns` + headerText note; verified the pack drops the PS content, ~130K tokens saved; Suite 52 stays green; no cache bump; APP_VERSION 2.7.0). Original SAFE verdict below is retained for the record but superseded by this decision.
**Verdict: SAFE. Auto-generation is the recommended path. No suite requires a real PowerShell engine.**
**Decisive evidence:** the PS runner's only "real execution" is **4 blocks that pipe a JS string into `node`** — `$repScript | node` at **lines 195, 449, 651, 4794** (faction-standing matrix ~149, `migrateState` ~422, detail-current dedup ~618, faction-rep Suite 77 ~4761), each with a `Get-Command node` probe and a "node not found → skip" fallback. These run the **same Node engine** the JS runner uses directly — there is **no second engine**; it is Node-via-pipe in both runners.
**Encoding/CRLF question (the only thing that could justify keeping PS):** the mojibake guard (**Suite 90**) merely *reads* files and string-matches U+FFFD / `â€` / `â–` byte sequences. Both Node `fs.readFileSync(path,'utf8')` and PowerShell `[IO.File]::ReadAllText` detect those identical bytes — PowerShell catches nothing Node misses. Protocol 39's real hazard is *writing* via PowerShell, not a detection capability unique to the PS runner. **Zero suites need a real PS check for coverage.**
- **Preferred — auto-generate the PS runner from the JS runner.** The generated mirror stays a full 1557-test file, so **nothing in CI / hooks / nightly / parity changes** — it still prints `ALL 1557 TESTS PASSED`, exits 0, satisfies the gate.js total-grep. Only *authoring* changes (codegen replaces hand-editing). The 4 node-pipe blocks translate trivially. **Risk:** the generator must translate JS regex/string literals to PS `-match`/`.Contains` correctly; because gate.js parity compares **totals only** (not semantics), a codegen bug could pass parity — so validate the generator once against full reference output and re-validate on each regen.
- **Alternative — thin PS to a parity stub.** Also safe, but it MUST (a) print `ALL <N> TESTS` matching the JS total (gate.js lines 80–82 grep both and compare), (b) exit 0, (c) keep `nightly-tests.yml` green (it runs `pwsh -File tests/robco-diagnostics.ps1` **directly**, line 47, checking exit code). Optionally retain the 4 node-pipe blocks as defense-in-depth (~30 assertions) — **not required for coverage**.
**[2nd-model] additional conditions if thinning (auto-gen avoids all of these):**
1. **Keep Suite 90's PS check.** Suite 90 (UTF-8 mojibake guard, 11 assertions) is the one suite that reads with **explicit** `[System.Text.Encoding]::UTF8`, whereas the PS runner's generic `Read-Src` (line 31) uses `[IO.File]::ReadAllText` with **no encoding** — on Windows PS 5.1 that defaults to the system ANSI code page (Windows-1252), so the rest of the PS string-match suites read on an *ambiguous-encoding* path. **Honest caveat:** this does **not** mean PS Suite 90 catches anything the JS Suite 90 misses (Node also reads explicit UTF-8 and finds the same mojibake bytes), and **[2nd-model D5]** the divergence only exists on a **local Windows PS 5.1 box** — CI/nightly run `pwsh` (PowerShell Core), which defaults to UTF-8 like Node. So the encoding "second oracle" value is *local-only and marginal*. Recommendation: **keep Suite 90 in PS regardless** — it's 11 cheap assertions and the conservative "lock nothing down" choice — but don't justify the whole PS runner on it.
2. **Do not fake the total. [2nd-model D3]** Because gate.js parity compares *totals only*, a stub could emit the right `ALL 1557 TESTS` via dummy `Pass()` calls and silently pass parity while covering nothing. This is the primary sneaky failure mode — a thinned PS must cover real suites, not pad the count.
3. **Cascade guards** (Suites 128.4/128.5, 50, 28 structural list) must stay green — keep the file present and referenced.
**Must update in lockstep (either path):** gate.js parity grep (lines 77–88, fix the H1-8 double-run while here); `ci.yml` (`npm run gate` → PS via gate.js, no change for auto-gen); `nightly-tests.yml` line 47 (direct PS run — must stay runnable + exit 0); git hooks (reach PS via gate.js, no direct dependence). **Protocol 15's meaning** shifts from "two hand-authored runners" to "one canonical + a generated/parity mirror" — the only lockdown-relevant change, needs the owner's explicit nod, but **does not reduce what is verified**.
**Lockdown impact: none on coverage.** Protocol 15's letter changes; its guarantee (both runners agree) is preserved.
## Change #11 — Table-drive the static-guard suites
**Verdict: SAFE for the string-match suites; MUST NOT touch the behavioral/vm blocks.**
**Leave alone (genuinely behavioral, JS runner):** `vm.runInContext` at **lines 285–298, 766–769, 1060–1070, 3659–3660**; `eval()` reconstructions at **3120, 3391, 3434** (`sanitizeImportedContainer` etc.); `execSync('git diff --cached')` at **812**. These execute real logic.
**Safely table-drivable:** the ~1,800 `assert(src.includes(...))` / `assert(/regex/.test(src))` static guards — the dominant mass. Replacing longhand repetition with a `for (const {file, pattern, type, msg} of TABLE)` loop changes expression, not assertions.
**Hazards to preserve:** negative assertions (`!src.includes`) must keep polarity; literal-vs-regex matcher type must be carried per row; per-suite `// N tests` markers (only 2 exist; no guard enforces comment==actual, so no count guard trips — keep them honest anyway); and the **same number of `assert()` calls** so `ALL 1557 TESTS` is unchanged.
**[2nd-model] concrete negative-assertion landmines** (folding these into a positive-only table silently inverts a guard — confirmed in code): Suite 16 PS line 578 `Check (-not ($swInstallStripped -match 'self\.skipWaiting'))`; Suite 2d PS line 245 `Check (-not (... 'id="campaignModeSelect"'))`; Suite 129.2 `assert(!/@media \(min-width:1000px\)\{/.test(css))`. The table schema must carry an explicit **polarity** field. **[2nd-model]** also: **Suite 28's `GATE_SUITES` list checks literal suite-name strings** (`Suite 22`, `Suite 23`, …) — a refactor that renames/renumbers suites drops them from detection; preserve the exact suite-name strings. And some PS suites use regex `-match` with multiline `(?s)` semantics that have no default-equivalent in JS (`.` doesn't cross newlines) — a table that normalizes everything to `.includes()` loses that precision (e.g. Suite 15 CSS invariants). Carry the matcher *mode* per row, don't flatten.
**Safest mechanism — output-diff proof:** (1) capture full stdout of `node tests/robco-diagnostics.js` (every pass/fail line, in order) as a reference; (2) refactor one suite-family at a time, keeping each `assert()`'s message and boolean **byte-identical**; (3) re-capture and assert **byte-for-byte identical** output. Identical output ⇒ identical assertions ⇒ provably zero coverage change. **Do #11 after #9**, then regenerate the PS mirror — never hand-refactor two runners in lockstep (that is the Protocol-15-drift risk to avoid).
**Lockdown impact: none** — same assertions, smaller text.
## Cross-cutting note from the pressure-test
**gate.js parity compares totals only, not per-suite counts** (lines 80–82 grep `ALL (\d+) TESTS`). So none of the three changes can lean on parity to catch a *semantic* divergence — only a count mismatch. This is why #9 (auto-gen) needs one-time full-output validation and #11 needs the output-diff proof. It is also a small latent gap worth noting to the owner (a runner could drift in distribution while holding its total), independent of these changes.
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).