AGENTS.md@packages/omo-senpi/scripts/qa · git:20261012.07bd37e · 2026-10-12 · sha256 e2df96b2b9afa495

AGENTS.md@packages/omo-senpi/scripts/qa git:20261012.07bd37eA

Immutable. This exact content never changes and is served at /api/v1/blob/e2df96b2b9afa495.

# scripts/qa

Live Senpi QA harness: E2E drivers, continuation probes, scenario fixtures, and mock providers that drive the REAL `senpi` binary against built plugin artifacts. ~80 files. Unit tests prove wiring; these drivers are the harness proof the root AGENTS.md QA mandate demands. Earned by score: largest and most referenced directory in the package.

## Lanes

| Lane | Drivers |
|------|---------|
| Task | `task-e2e.mjs` (single/batch lifecycles), `task-lane-spill-e2e.mjs`, `task-fallback-notification.mjs`, `task-category-unavailable-e2e.mjs`, `task-id-race-qa.mjs`, `task-parent-restart-e2e.mjs`, `task-summary-e2e.mjs`, `task-load-skills-e2e.mjs`, `task-13.test.ts` (engine wiring), `isolation-e2e.mjs [--evidence-dir <d>] [--plugin-root <p>] [--control-plugin-root <p>] [--senpi-cli <p>] [--only applied,conflict,control] [--keep-sandbox] [--self-test]` (checkout isolation on the real surface: a temp git checkout, one FOREGROUND child spawned with `isolated: true`, `applied` = the child's `hello.txt` reaches the parent checkout with `merge_result.kind === "applied"` and the clone gone, `conflict-not-applied` = a parent-side edit landing mid-run leaves the parent copy untouched with a verbatim conflict, a readable `root.patch`, a `git apply --3way` manual command and the clone renamed aside as `<base>.retained-*`, `control` = the SAME script against another plugin root records no isolation at all. Forces `task.default_execution_mode: "in-process"` so the lane mock provider answers both turns; scripts and assertions live in `isolation-e2e-scenarios.mjs`; writes `e2e.json`) |
| Team | `team-e2e.mjs`, `team-resume-e2e.mjs`, `team-delete-6413-qa.mjs`, `team-e2e-crash.mjs`/`-crash-state.mjs`, support modules `team-e2e-{support,runtime,process,scripts,analysis}.mjs`, `team-e2e-mock-provider.ts` |
| RPC | `task-rpc-e2e.mjs`, `-helpers.mjs`, `-scenarios.mjs` (+`.test.mjs`), `task-rpc-e2e.windows.test.ts` |
| Daemon host | `task-host-e2e.mjs --bin <compiled omo binary> --out <dir> [--baseline] [--legacy-bin <pre-wave-2 omo>] [--second-bin <newer omob>] [--newer-senpi <bare senpi>] [--legacy-engine-cli <cli.js>] [--only A,B] [--self-test]` (live QA that a `process` child is a SESSION of `omo daemon`: A one daemon / two parents / 16 children each, B detach-attach, C team members, C2 idle parking, D DAG child toolset, E-E3 generation handoff, E4 profile guard, F zombie budget, G CLI exit codes + a tmux pty attach, H a pre-wave-2 host, H2 a fail-closed legacy client, I the default-mode rule). Support modules `task-host-e2e-{sandbox,process,support,scenarios,team,ops,gated,baseline,engine-probe,selftest}.mjs`. Sandbox rules the compiled binary forces: ALL THREE of `OMO_`/`SENPI_`/`PI_CODING_AGENT_DIR` point at the sandbox (the binary reads `OMO_` first), `HOME` is sandboxed BEFORE the first call (the runtime provisions under `$HOME/.omo/binary-runtime/<ver>/`), the run root is a short `/tmp/dh41.*` so `<agentDir>/rpc/rpc.sock` fits `sun_path`, and the mock provider is copied into the sandbox's provisioned plugin root because the daemon's extensions come only from its launch spec. Every scenario writes a JSON result, a transcript and a cleanup receipt; a scenario whose input is absent is `skipped` with the exact command that would run it. |
| Shard cost | `task-host-e2e-shard-cost.mjs --bin <compiled omo from the branch> --before-bin <compiled pre-change R0 omo> --out <dir> [--samples 20] [--target id=value]... [--skip idle\|marginal\|totals\|latency\|idle_exit]... [--control] [--keep-sandbox]` or `--reevaluate <shard-cost.json>` (rpc-host-sharding todo 15: memory and latency of per-parent shards against the pre-change shared host, both configurations provisioned under their own sandbox HOME in one short `/tmp/dh41.*` run root, keyless mock provider seeded into each launch spec). Sections: `idle` (one shard, 0 sessions, 30 s after its session-start pre-warm; control `omo daemon run`), `marginal` (the same shard after 1, 2, 4 children each completed a turn, sampled 15 s after settle), `totals` (N = 1, 2, 4 parents x 4 children; the control asserts ONE `rpc.sock` holding every child before it samples), `idle_exit` (d1 all parents quit, d2 A and B quit while C keeps a child mid-turn, under defaults and under a NON-DEFAULT 1 h session eviction; asserted at +16 min by `kill -0`, never by a socket probe, since a probe is a connection that restarts the idle window), `latency` (20 observations per scenario kept as `samples_ms`, nearest-rank p50/p95: cold first child, pre-warm `first-turn`, pre-warm `session-start`, reattach after a host SIGSEGV, `host ensure` at 1 and 4 live hosts, plus the user-shaped `user_first_child_{off,first_turn,session_start,default,control}` from `task-host-e2e-shard-cost-user-latency.mjs`: a fresh session, its first turn, a mock model latency cycling 1-3 s, then the `task` call, measured from the parent's `task` tool start to the child's first provider request, every configuration interleaved per round and the control on an already-running R0 shared host with one discarded warm-up child), `prewarm_idle` (default and `session-start`, a session that never spawns: host physical footprint 20 s after its warm-up and proof it exits on a 45 s idle window while the parent lives). Memory = RSS plus darwin `vmmap --summary` Physical footprint / linux `smaps_rollup` Pss, summed over each supervisor's process tree. `host status --all` (senpi #2245) is used when the engine has it and degrades to per-socket status on a usage error. Verdict logic lives in `task-host-e2e-shard-cost-eval.mjs` (+`.test.mjs`): exit 0 every target PASS, 2 measured but a target FAIL, 1 a missing section, null cell or fewer than 20 samples; `--control` runs only the control half of `idle_exit`. Writes `shard-cost.json` + `shard-cost.md`; support module `task-host-e2e-shard-cost-support.mjs`. |
| Shard cost | `task-host-e2e-shard-cost.mjs --bin <compiled omo from the branch> --before-bin <compiled pre-change R0 omo> --out <dir> [--samples 20] [--target id=value]... [--skip idle\|marginal\|totals\|latency\|idle_exit]... [--control] [--keep-sandbox]` or `--reevaluate <shard-cost.json>` (rpc-host-sharding todo 15: memory and latency of per-parent shards against the pre-change shared host, both configurations provisioned under their own sandbox HOME in one short `/tmp/dh41.*` run root, keyless mock provider seeded into each launch spec). Sections: `idle` (one shard, 0 sessions, 30 s after its session-start pre-warm; control `omo daemon run`), `marginal` (the same shard after 1, 2, 4 children each completed a turn, sampled 15 s after settle), `totals` (N = 1, 2, 4 parents x 4 children; the control asserts ONE `rpc.sock` holding every child before it samples), `idle_exit` (d1 all parents quit, d2 A and B quit while C keeps a child mid-turn, under defaults and under a NON-DEFAULT 1 h session eviction; asserted at +16 min by `kill -0`, never by a socket probe, since a probe is a connection that restarts the idle window), `latency` (20 observations per scenario kept as `samples_ms`, nearest-rank p50/p95: cold first child, pre-warm `first-turn`, pre-warm `session-start`, reattach after a host SIGSEGV, `host ensure` at 1 and 4 live hosts). Memory = RSS plus darwin `vmmap --summary` Physical footprint / linux `smaps_rollup` Pss, summed over each supervisor's process tree. `host status --all` (senpi #2245) is used when the engine has it and degrades to per-socket status on a usage error. Verdict logic lives in `task-host-e2e-shard-cost-eval.mjs` (+`.test.mjs`): exit 0 every target PASS, 2 measured but a target FAIL, 1 a missing section, null cell or fewer than 20 samples; `--control` runs only the control half of `idle_exit`. Writes `shard-cost.json` + `shard-cost.md`; support module `task-host-e2e-shard-cost-support.mjs`. |
| Shard faults | `task-host-e2e-shards.mjs --bin <compiled sharded omo> --older-bin <same-version sharded omo stamped with a lower engine ordinal> --before-bin <compiled R0 omo> --out <dir> [--control] [--only crash,nested,handoff,handoff-successors,index,rollback,retain,contracts] [--keep-sandbox]` (rpc-host-sharding todo 14: one short `/tmp/dh41.*` run root, sandboxed HOME/agent dirs, Bun transpiler cache disabled, and no socket override variables). Every one of the 25 rows is a LIVE scenario against the compiled binary: `crash` (two parents own distinct `p-*` supervisors and host children, SIGSEGV of A's host child only, B's children finish inside A's fault window, A reattaches on a new generation; each parent then reads `task_output`, and neither parent's notice list may contain `host_shard_crash:` or `host_shard_crash_done:` lines after automatic recovery; `host status --all` must report crashes >= 1 and a new instance for A's endpoint and crashes 0 for B's), `nested` (a grandchild reuses its tree shard; a SIGSTOPped own supervisor fails a new spawn with `own_host_unreachable`), `handoff` (a host-launched parent on an older engine ensures a `p-*` shard with its own engine and launch-spec env; an outside handoff converges, the older build answers reuse), `handoff-successors` (nested spawns after a handoff land on the successor for `p-*`, `i-*` and alt-root endpoints; a nested resume never ensures its own host, and a SIGSTOPped own host is either parked `own_host_unreachable` or left waiting as busy per #9069, with zero spawn), `index` (cross-endpoint open hazard, idle exit + gc + rollback from the store index, index-unwritable registration precondition, in `process` mode and in the default `auto` mode, whose gate ask would ensure a host), `rollback` (live-endpoint refusal, drain `--wait` gate, three-store migration, R0 resume on `rpc.sock`, no-prepare park), `retain` (idle and mid-turn resume, recorded-socket migration, incompatible host park, per-entry isolation with a deleted session directory). `--older-bin` must carry the SAME version of omo as `--bin` (the runtime directory and launch profile are keyed by it). The `contracts` gate runs the targeted product tests as an extra gate that can fail but never substitutes a row. `--control` runs the two-parent crash against R0 and passes only when one `rpc.sock` holds all six workers and both parents cascade. Pure verdict logic and mutation proof live in `task-host-e2e-shards-eval.mjs` (+ `.test.mjs`); the crash rows' notice and status rules (`task-host-e2e-shards-crash-rules.mjs`) are proven by `task-host-e2e-shards-crash.test.mjs`, the alt-root sweep by `task-host-e2e-shards-handoff-successors-alt.test.mjs`. Writes `fault-matrix.json` + `fault-matrix.md` and per-scenario cleanup receipts (alt-root `/tmp/omo-rpc-*` roots are matched by realpath, so the `/private/tmp` form darwin records is swept too). |
| Resume | `task-resume-e2e.mjs`, `task-resume-failure-e2e.mjs`, `task-resume-e2e-scenarios.mjs`, `resume-e2e-runtime.mjs` |
| Memory | `memory-e2e.mjs`, `memory-model-fallback-e2e.mjs`, `memory-skill-startup-e2e.mjs`, `facts-backlog-e2e.mjs`, `memory-write-visual-qa.mjs [--keep-sandbox]` |
| Kibitzer sidecar | `kibitzer-sidecar-e2e.mjs [--scenario happy\|provider-429\|context-reseed\|category-unavailable\|refused-pinned-model\|cjk-auto\|all] [--evidence-dir <d>] [--plugin-root <p>] [--senpi-cli <p>\|--command-file <f>] [--keep-sandbox] [--self-test]` (offline RPC proof of the RESIDENT sidecar against the built bundle: `happy` = the first fresh candidate seeds ONE child under `recall/sidecars/<base64url(session)>/`, its nudge reaches the parent on the next turn, the repeated candidate starts no provider turn, and a fresh candidate revives the SAME child through a followUp wake; `provider-429` = the child's provider answers 429 with an hour-plus wait, the turn fails in one request, the lease is released, and the replacement child's seed spans every cursor the failed one was handed; `context-reseed` = the mock reports 40k prompt tokens, so the next candidate seeds a replacement whose first message is the `<kibitzer-reseed>` envelope carrying the delivered path; `category-unavailable` = the lane's omo config leaves the recall category on its builtin chain while only `omo-mock` is connected, so every wake is refused - three or more refusals produce exactly ONE `omo-kibitzer:unavailable` notice naming the category, zero `omo-kibitzer:gate` escalations, zero child turns, and wake records that are non-diagnostic and carry the `configuration` state; `refused-pinned-model` = `categories.quick.model` pins `omo-mock/refused-1` outside the builtin quick chain and the mock refuses every request for it with a 403 `permission_denied` while the builtin rung `deepseek/deepseek-flash` is served, so the wake falls back inside the same turn: the child transcript holds the refusal and a fallback answer, the wake record is a non-diagnostic completion on `deepseek/deepseek-flash`, the nudge reaches the parent and no gate notice is raised (#9111); `cjk-auto` = with no recall setting, a Korean prompt that shares only an inflected verb form with a stored memory still offers that memory to the sidecar, because recall picks bm25 with CJK bigrams on its own, and the nudge reaches the parent). Every lane asserts ALL agent-dir lanes (`OMO_`/`SENPI_`/`PI_CODING_AGENT_DIR`) point at the sandbox, the shape `isolatedChildEnv` produces since #8967. Summary line carries `resident`, `childSessions` (sidecar lineages per parent session, always 1) and per-scenario `childGenerations` / `wakes` / `nudged`. `kibitzer-sidecar-manifest-check.mjs [--plugin-root <p>] [--source <root>] [--evidence-dir <d>] [--expect-tools a,b] [--forbid-aliases x,y] [--remove-sidecar-asset]` (packaging guard that never starts the binary: bundle + build marker, `#omo-task-runtime` mapping and runner export, staged `kibitzer-persona.md` byte-identical to its memory-core source, sidecar literals, the exact five-name registry array, one label per closure, no alias registry; `--remove-sidecar-asset` runs the same checks over a temp copy without the persona and must exit 1 with `missing-asset`; writes `manifest.json`). `kibitzer-sidecar-recovery-probe.mjs [--manifest <manifest.json>] [--command-file <f>\|--senpi-cli <p>] [--plugin-root <p>] [--evidence-dir <d>]` (starts the packaged binary, re-hashes the artifact against the manifest, drives one wake to an accepted + delivered nudge; findings are layered `probe` vs `host-runtime` so harness trouble is never read as a runtime failure). `kibitzer-legacy-absence-probe.mjs [--source <root>] [--plugin-root <p>] [--evidence-dir <d>]` (one-shot modules, symbols, per-run `recall/runs` literals, engine switch and retired drivers absent from production code and from the shipped bundles; markdown and test fixtures listed, never failed; prints `legacy_runtime`, `compaction_epoch`, `per_run_artifacts`, `dual_mode`, `stale_drivers`). `package-dir-hygiene-probe.mjs --mode clean\|contaminated [--set NAME=VALUE]... [--evidence-dir <d>] [--self-test]` (the environment audit every lane runs BEFORE a green result is trusted: spawns ONE child lane under this bun with the caller's environment and reads back every `*_PACKAGE_DIR` that lane sees - `OMO_PACKAGE_DIR`, `SENPI_PACKAGE_DIR`, `PI_PACKAGE_DIR` are always listed, any other `*_PACKAGE_DIR` is discovered; `clean` exits 0 only when none is set and writes `package-dir-hygiene.json` naming every inspected variable with its `source` (known / inherited / lane-discovered) and value; `contaminated --set` injects the variable into its own child lane and exits 1 with `package-dir-contamination-receipt.json` naming the contaminating variable(s) and `probe: "sound"`; exit 2 means the injection was NOT observed and the probe itself is broken). `dependency-diff-check.mjs --base <rev> --head <rev> [--repo <dir>] [--evidence-dir <d>] [--self-test]` (the supply-chain control behind "no new npm dependencies": lists every `package.json`, `bun.lock`, `package-lock.json` / `npm-shrinkwrap.json`, `yarn.lock` and `pnpm-lock.yaml` tracked at either commit - nothing under `node_modules` - and diffs their dependency entries through `dependency-diff-parsers.mjs` (manifest sections incl. overrides / resolutions / bundled; bun.lock workspace sections plus the resolved `packages` map, overrides, patches, catalog; npm `packages` and the v1 tree; yarn and pnpm re-keyed by package name so a bump is a change, never an addition); exit 0 only when no entry was ADDED anywhere - bumps and removals are reported and counted; exit 1 names every added entry as `ADDED <file> :: <key> = <value>` and in the summary's `added`, and a changed file the parser cannot read (binary `bun.lockb`, corrupt JSON) is `unverifiable` and red too; exit 2 is a usage or git error; writes `dependency-diff.json`; `dependency-diff-check.test.mjs` proves green and red on scratch git repositories). `kibitzer-sidecar-package-probe.mjs --manifest <manifest.json> --evidence-dir <d> [--plugin-root <p>] [--senpi-cli <p>] [--self-test]` (the bridge between the two packaging controls, layer `probe`, starts nothing: re-hashes `omo.js` / `omo-task.js` / `kibitzer-persona.md` against the manifest, refuses an exported `*_PACKAGE_DIR`, resolves the senpi CLI the plugin root itself resolves to - `@code-yeongyu/senpi/dist/cli.js` along the plugin root's `node_modules` chain, the walk production's `worker/senpi-command.ts` performs - checks it is that package's declared `bin.senpi` and matches the exact senpi pin of `packages/omo-senpi/package.json`, and writes its canonical absolute path as the ONLY line of `omo-command.txt` for `--command-file`; on any failure the command file is removed and a reason is named: `manifest-missing`, `manifest-control-red`, `artifact-drift:<file>`, `package-dir-contaminated`, `host-unresolved`, `senpi-version-drift`; receipt `package-probe.json`). `kibitzer-sidecar-tui-qa.mjs --evidence-dir <d> [--plugin-root <p>] [--senpi-cli <p>\|--command-file <f>] [--cols <n>] [--rows <n>] [--chrome-bin <p>] [--keep-sandbox] [--self-test]` (the live terminal proof: the REAL senpi TUI runs in a Bun-native PTY - `Bun.spawn` with `terminal`; node-pty delivers no data under bun - and its byte stream is rendered by a REAL xterm.js terminal in headless Chrome through puppeteer-core (system Chrome, `--chrome-bin` or `CHROME_BIN`); the corpus is seeded through the memory tool, the rollout prompt is typed, the accepted nudge (pending file) and the settled first answer are awaited, a candidate-free follow-up is typed and xterm.js must show `✦ Kibitzer !` over `recalled memory: <hint>`; writes `terminal.png` (the viewport), `terminal.txt` (the viewport text - the assertion surface), `terminal-buffer.txt` (scrollback + viewport), `terminal-ansi.txt`, `metadata.json` (every awaited signal with its timing, the matched lines, cleanup receipts) and `kibitzer-sidecar-tui-qa.json`; readiness is the `mem:` memory footer because the editor glyph is drawn before senpi enables submission; the interactive host's background session-title request is answered on a router lane of its own so it never consumes the parent script; every wait is a rendered-text predicate re-evaluated per PTY chunk, a file change or a process exit with a bounded timeout). Shared harness `kibitzer-sidecar-support.mjs`: sandbox prep (`PI_OFFLINE=1`, scrubbed `*_PACKAGE_DIR`), omo config, sidecar-vs-parent routing by the `nudge` tool plus the Kibitzer persona (independent lane scripts, exhausted scripts close the turn with text; `createRouter({ lanes })` answers a surface's own provider calls - the TUI's title generation - on named lanes that consume nothing), RPC launch/teardown, `watchUntil` (fs.watch + bounded timeout, never a fixed sleep), `waitForAccepted` (pending file OR nudged entry), sidecar readers (`sidecarDirs`, `childTranscripts`, `leaseFiles`, `readPending`), RPC-session seeding (a `-p` run lands in `transient-runs/`, never the durable identity), `createCleanup` (reverse-order teardown whose `run` is shared by a finishing driver and its interrupt handler, so neither exits while the other is still killing processes). |
| Components | `computer-use-e2e.mjs` (`SENPI_BIN=<senpi> ... [--engine <p>] [--evidence-dir <d>]`: the real senpi and the built plugin against the real `senpi-desktop-engine` on its fake backend. Scenarios: a screenshot passes `computer:exec=deny`; a click is refused by that rule and never reaches the engine; a click without a rule is audited by the engine; `eval` sees the `computer` global once the tool is active. It fails if `~/.senpi` or `~/.omo` credentials change), `computer-control-rpc-e2e.mjs` (`SENPI_BIN=<senpi> ... [--engine <p>] [--evidence-dir <d>] [--only no-answer,declined,approved]`: the foreground-control confirm (#9651 B5b) through senpi's REAL `--mode rpc`. One `computer` run calls `desktop.control.acquire`; the confirm leaves senpi as an `extension_ui_request` and the driver plays the client. `no-answer` never answers: the confirm times out (45 s, under the 60 s default run budget), the run reports `active:false`, and the engine audit has no `control.grant`. `declined` answers `confirmed:false`: no grant. `approved` answers `confirmed:true`: `control.grant` is audited and `agent_end` audits `control.revoke`; it is the positive control that keeps the other two from passing vacuously), `fallback-architect-e2e.mjs`, `git-master-attribution-e2e.mjs`, `skill-pointers-e2e.mjs`, `ultrawork-receiver-e2e.mjs` (`SENPI_BIN=<senpi> ... [--evidence-dir <d>]`: the real senpi and the built plugin under the keyless mock provider with two model ids; the `gpt-6-astra` receiver must get `SENPI_ASTRA_ULTRAWORK_DIRECTIVE` and `mock-1` the baseline, read back from the session JSONL, no variant marker leaked), `mass-ulw-prompts-e2e.mjs`, `ulw-prompts-e2e.mjs`, `ulw-goal-footer-tui.mjs`, `todo-fanout-reminder-e2e.mjs`, `no-todo-continuity-e2e.mjs`, `variant-thinking-e2e.mjs`, `task-tui-{e2e,scenarios}.mjs`, `task-stats-renderer.mjs` |
| Runtimes | `lsp-e2e.mjs` (largest, ~1.4k LOC: staged runtime, extension loading, tool behavior, post-edit flows), `ast-grep-mcp-e2e.mjs`, `curated-agents-e2e.mjs`, `parallelism-eval-e2e.mjs`, `plan-gated-agents-e2e.mjs`, `dag-gate-proof.ts`, `dag-wait-detach-qa.ts`, `dag-lease-handoff-qa.ts` (paused run whose previous host is a real still-alive process: stays paused, resumes by itself once that pid exits), `probe-continuation.mjs`, `probe-cross-session.mjs` |
| Thread | `thread-tools/` cross-surface suite: `cli-surface.mjs` (CLI session drives create/send/steer on a peer), `desktop-client.mjs` (same ops through the REAL desktop provider client), `terminal-to-ui.mjs` and `desktop-to-cli.mjs` (each surface's sessions addressable from the other), `run-all.mjs`, shared `lib/harness.mjs` |
| Resilience | `task-14/` fault injections against a real socket host: `kill-mid-turn.mjs`, `version-capability.mjs` (incompatible unmanaged host is refused, never adopted or replaced), `run-all.mjs`, shared `common.mjs` |
| Agent toolkit eval SDK | `agent-toolkit-eval-sdk-qa.mjs` (real-surface QA: a detached worker builds a real AgentSession from the built plugin and drives the agent toolkit eval SDK through js/py eval cells (7 checks); run `bun packages/omo-senpi/scripts/qa/agent-toolkit-eval-sdk-qa.mjs --evidence-dir <dir>`; exit 0 only on 7/7), `agent-toolkit-eval-sdk-qa-support.mjs`, `agent-toolkit-eval-sdk-docs.mjs` |
| Infra | `drive.mjs`, `task-e2e-{analysis,process}.mjs`, `mock-completions-server.mjs`, `mock-provider/` |

## Shared hubs

- `drive.mjs`: `createSandbox`/`seedSandbox`/`digestDirectory`/`credentialDigest` (+ `--self-test`). The isolation seam every driver imports.
- `task-e2e-analysis.mjs`: JSONL event parsing, `jsonlSignature`, ordered-subsequence matching, filesystem snapshot diffing, `classifyRealSenpiChanges`.
- `resume-e2e-runtime.mjs`: bounded `pollUntil`, task-record readers, kill-group cleanup.
- `mock-provider/index.ts`: `registerMockProvider`, `selfTest`, `loadMockScript`, `stepToAssistantMessage`, stream/result guards. The `*-mock-provider.ts` files default-export senpi extension registrations and are loaded via `senpi -e`.
- `mock-completions-server.mjs`: local HTTP mock provider. A child in-process MUST ALWAYS exit through a real HTTP client, never an in-process shortcut. Steps are `tool_call`, `text`, and `error` (`{ type: "error", status, body }` writes the status and JSON body instead of a stream, the only way to exercise a provider outage); a step's optional `usage` (`{ prompt_tokens, ... }`) rides the finish chunk exactly where an OpenAI-compatible provider reports it (the sidecar's context estimate reads it); one step per request off a single global cursor, so a body-routing `steps(body)` must place its step at that index.
- `thread-tools/lib/harness.mjs`: the ONE harness for the thread lanes - scratch dirs, fake model and child tracking come from the sanctioned `qa-app-server/lib/*` modules, assertions read target state (`get_messages`, `getShellSnapshot()`) rather than logs, and `verifyCleanup` proves no survivor matched this run's own scratch path.
- Cross-checkout roots are env-overridable, never hard-coded: `THREAD_QA_SENPI_ROOT` and `THREAD_QA_DESKTOP_ROOT` (harness), `THREAD_QA_SENPI_ROOT` and `THREAD_QA_EVIDENCE_ROOT` (`task-14/common.mjs`). Specifiers into another checkout MUST be dynamic `import()` of an env-resolved path, or the suite fails module resolution on every other machine.

## CONVENTIONS

- ESM `.mjs` drivers with `import.meta.url` entry guards and `node:*` built-ins; scenario and provider modules duplicate named token markers deliberately, and tests pin marker equality plus structured event/state evidence.
- Sandboxes own HOME/XDG dirs and `SENPI_CODING_AGENT_DIR`; drivers build their own isolated agent dir and IGNORE a caller-provided one; credential digests prove the real agent dir stayed untouched.
- Final output is machine-readable JSON with PASS/FAIL checks; a missing `senpi` binary yields SKIP/FAIL, never a pass.
- Process QA is defensive: owned process registries, process-group/tree termination, bounded polling and deadlines, PID liveness checks, Windows shim/native executable resolution.

## ANTI-PATTERNS

- NEVER run QA against the real `~/.senpi/agent` or `~/.omo`; sandbox isolation and digest proof are mandatory.
- NEVER treat output text or process residency alone as proof of spawn/revival: require structured events, PID plus child-session JSONL, mailbox state, or exact transcript markers.
- NEVER accept in-process fallback as proof of RPC process execution: process mode plus PID/child-session JSONL (or a named spawn-path failure).
- Ultrawork QA: `update_plan`, `multi_agent`, `spawn_agent` are forbidden transcript/tool markers; the directive must be a hidden custom message, never user transcript text. Secrets (`TOKEN|SECRET|PASSWORD|COOKIE|CREDENTIAL|API_KEY`) are filtered from env snapshots.
- No unbounded waits, no orphaned children, no leaking parent/child selectors, and task/team child providers never consume the parent's scripted sequence.

## COMMANDS

```bash
node scripts/qa/drive.mjs --self-test
node scripts/qa/task-rpc-e2e.mjs --self-test
node scripts/qa/task-load-skills-e2e.mjs --self-test
node packages/omo-senpi/scripts/qa/policy-continuation-e2e.mjs --self-test   # deterministic half; also runs inside `bun test packages/omo-senpi`
BUN_BIN="$(command -v bun)" node packages/omo-senpi/scripts/qa/policy-continuation-e2e.mjs   # live; needs the staged agent-toolkit
bun packages/omo-senpi/scripts/qa/kibitzer-sidecar-e2e.mjs --self-test
env -u OMO_PACKAGE_DIR -u SENPI_PACKAGE_DIR bun packages/omo-senpi/scripts/qa/kibitzer-sidecar-e2e.mjs --scenario all --evidence-dir <d>   # needs the built bundle
env -u OMO_PACKAGE_DIR -u SENPI_PACKAGE_DIR bun packages/omo-senpi/scripts/qa/package-dir-hygiene-probe.mjs --mode clean --evidence-dir <d>/package-dir   # exit 0 gates every Kibitzer lane below
bun packages/omo-senpi/scripts/qa/package-dir-hygiene-probe.mjs --mode contaminated --set SENPI_PACKAGE_DIR=/tmp/ulw-contaminated --evidence-dir <d>/package-dir-red   # the RED control: must exit 1
env -u OMO_PACKAGE_DIR -u SENPI_PACKAGE_DIR bun packages/omo-senpi/scripts/qa/kibitzer-sidecar-manifest-check.mjs --evidence-dir <d> && env -u OMO_PACKAGE_DIR -u SENPI_PACKAGE_DIR bun packages/omo-senpi/scripts/qa/kibitzer-sidecar-package-probe.mjs --manifest <d>/manifest.json --evidence-dir <d>/package-probe && env -u OMO_PACKAGE_DIR -u SENPI_PACKAGE_DIR bun packages/omo-senpi/scripts/qa/kibitzer-sidecar-recovery-probe.mjs --manifest <d>/manifest.json --command-file <d>/package-probe/omo-command.txt --evidence-dir <d>/recovery
env -u OMO_PACKAGE_DIR -u SENPI_PACKAGE_DIR bun packages/omo-senpi/scripts/qa/kibitzer-sidecar-tui-qa.mjs --command-file <d>/package-probe/omo-command.txt --evidence-dir <d>/tui   # needs Chrome and the built bundle; terminal.png/.txt/metadata.json land in <d>/tui
bun packages/omo-senpi/scripts/qa/kibitzer-legacy-absence-probe.mjs --evidence-dir <d>
bun packages/omo-senpi/scripts/qa/dependency-diff-check.mjs --base <sha> --head <sha> --evidence-dir <d>/dependency-diff   # exit 0 only when no dependency entry was added in any manifest or lockfile
SENPI_BIN="$(command -v senpi)" node scripts/qa/task-e2e.mjs   # live mode; same for team-e2e.mjs
bun test scripts/qa/task-e2e-analysis.test.mjs scripts/qa/resume-e2e-runtime.test.mjs
bun packages/omo-senpi/scripts/qa/thread-tools/run-all.mjs        # cross-surface thread tools
bun packages/omo-senpi/scripts/qa/task-14/run-all.mjs             # resilience fault injections
node packages/omo-senpi/scripts/qa/task-host-e2e-shards.mjs --bin <compiled-omo> --before-bin <R0-omo> --out <evidence-dir>
bun run test:senpi                                             # gate: build + stage + typecheck + test
```

LSP and ast-grep lanes need staged runtimes first (`bun run build:senpi-plugin`). Evidence goes under `.omo/evidence/omo-senpi-adapter/` via the `senpi-qa` skill's resolver; see the package and root AGENTS.md.