infinity-harness 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,140 @@ All notable changes to this project are documented here.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.3.1] — 2026-08-24
8
+
9
+ *(2.3.0 was staged at the registry and never completed; 2.3.1 is that release plus the last two
10
+ fixes below, and is the first version of this work anyone can install.)*
11
+
12
+ Five bugs were reported against 2.1.0 by someone actually using the thing. Every one of them was
13
+ real, none of them could be seen by a test that mocks pi, and finding out why led to the change
14
+ that matters most in this release: **the suite now drives a real `pi` process.**
15
+
16
+ ### Added
17
+
18
+ - **A fresh pi session per boundary.** A harness that never starts a new session is a harness whose
19
+ context window only ever grows: by the tenth task the model is re-reading the history of the
20
+ first nine in order to do the tenth. It pays for those tokens on every call, compacts them into a
21
+ lossy summary once the window fills, and on a small model simply drowns.
22
+
23
+ Nothing the harness knows was ever in the conversation — the plan, the phase, the gate history,
24
+ the retry budgets and the escalation ladder are all files. So a session boundary costs one thing:
25
+ the brief, which is what the agent should have been working from anyway. `session.handoff` is
26
+ `phase` by default, `task` for the cleanest possible context, `off` for the old behaviour; any of
27
+ them also hands off early once the context passes `session.contextThreshold`, because a handoff
28
+ that arrives after compaction has arrived too late to be the thing that prevented it.
29
+
30
+ - **A run that outlives its sessions.** `harness/run.json` holds whether a run is armed, its id, and
31
+ how many sessions it has spent. `/reload`, `/resume`, closing the terminal, and every handoff now
32
+ resume the same run instead of quietly starting a new one with fresh budgets.
33
+
34
+ - **A start-up wizard that asks what is being built.** Choosing "autopilot" used to start the run
35
+ immediately, with no idea and no scope, so the first thing the harness did was invent a project
36
+ and start building it. Autopilot was being read as "you decide everything, including what I
37
+ want". Two questions were tangled together and are now separate: *what are we building* (asked in
38
+ both modes) and *who signs off on what* (the mode's actual meaning).
39
+
40
+ - **Approval gates on RESEARCH, DEFINE and PLAN.** The gate is a good referee for execution and a
41
+ poor one for intent: it can prove a feature has acceptance criteria and cannot prove they are the
42
+ right ones. In copilot all three are yours. In autopilot you tick the ones you want and forfeit
43
+ the rest — forfeiting all three is the walk-away setting. `/infinity:approve` continues;
44
+ `/infinity:approve <what is wrong>` sends the phase back carrying your words. A rejection is
45
+ pinned to the state of the project when you made it, so you are not asked the same question again
46
+ until the agent has actually changed something.
47
+
48
+ - **An optional RESEARCH phase**, before DEFINE, with its own role, gate and phase doc. The human
49
+ gives an idea; the model comes back with prior art, the constraints that are real, at least two
50
+ options with what each costs, a recommendation, what would falsify it, and the questions only a
51
+ human can answer — which become the DEFINE interview.
52
+
53
+ - **All five plan levels on screen.** Goal → sprint → feature → task → subtask. Only two of them
54
+ used to reach the human: the widget drew features and tasks, goals were a single title line, and
55
+ sprints appeared *nowhere*, so a plan organised into sprints looked exactly like a plan that was
56
+ not. `src/ui/planTree.ts` is one shared model of the plan's shape; the widget windows it and the
57
+ dashboard renders all of it as collapsible tiers with counts at every level. A feature pointing
58
+ at a goal that has since been deleted is still drawn — a task nobody can see is a task that gets
59
+ stuck forever.
60
+
61
+ - **A scrollable plan widget.** Nine rows of a sixty-row plan read as a truncation, because the
62
+ other fifty-one were unreachable without opening the dashboard. It is a window now: `alt+j` /
63
+ `alt+k` scroll, `alt+o` expands, and `/infinity:scroll up|down|top|bottom|expand|collapse|follow`
64
+ does the same without a keyboard. (`alt+`, not `ctrl+`: pi already binds ctrl+j, ctrl+k and
65
+ ctrl+o in its editor, and a widget is not worth shadowing an editor key for.)
66
+
67
+ - **`scripts/rig/` — the suite drives a real pi.** A scripted OpenAI-compatible model server plus an
68
+ RPC client that types prompts and slash commands, answers `ctx.ui.select` dialogs, and reads back
69
+ the widget, the status line and the notifications a human would actually see. The new `realpi`
70
+ e2e scenario covers startup, the wizard, a run spanning several real sessions, real
71
+ auto-compaction, an approval round-trip, and `pi -p` not hanging.
72
+
73
+ - Three commands: `/infinity:approve`, `/infinity:handoff`, `/infinity:scroll`. Two settings groups:
74
+ **Your approvals** and **Sessions**.
75
+
76
+ ### Fixed
77
+
78
+ - **The harness did not survive compaction.** Its rules lived in the transcript, and a transcript is
79
+ what compaction summarises: the agent came out the other side still holding the plan (it is on
80
+ disk) but no longer knowing it was supposed to work from it, stop at a failing gate, or never
81
+ mark its own work complete. Those rules now go in the **system prompt**, via
82
+ `before_agent_start`, which is rebuilt every turn and is never summarised. The post-compaction
83
+ re-brief is also delivered as `steer` rather than `nextTurn` when a turn is running — `nextTurn`
84
+ waits for a human to type, which in an unattended run never happens, so the message that was
85
+ supposed to rescue the agent sat in a queue while it carried on without it.
86
+
87
+ - **The loop did not survive its own handoff.** "Is a run armed?" was a `let` inside the extension
88
+ closure, so it died with the pi session that held it. Worse, the run id was a `randomUUID()` per
89
+ session: every new session looked like a brand-new run to `loadLoopState`, resetting the iteration
90
+ ceiling, the wall-clock budget, the no-progress streak and the escalation ladder — every guard
91
+ that makes walking away safe, reset by the mechanism that makes walking away possible.
92
+
93
+ - **`pi -p` hung forever on a harness project.** The session-start brief was queued with
94
+ `deliverAs: "nextTurn"`, which waits for a user prompt. Print mode never has one. Every headless
95
+ run hung at startup and nothing in the suite could see it, because nothing in the suite ran pi.
96
+
97
+ - **A second, hand-written copy of `PHASE_ROLE`** in `core/config.ts`. Adding a phase compiled
98
+ cleanly and then silently reported the wrong role for it.
99
+
100
+ - **A rejected approval could spin forever.** The first version of the fix returned early from the
101
+ loop when a rejection was still outstanding, which skipped the no-progress detector — an unbounded
102
+ loop, the one thing this product exists to prevent. It falls through the normal failure path now,
103
+ so strikes, budgets and the escalation ladder all apply, and a run whose agent ignores a rejection
104
+ stops and says exactly that.
105
+
106
+ - **Commands that dead-ended before init.** `/infinity:next` printed a full page of pipeline
107
+ instructions for a pipeline that did not exist; `/infinity:scroll` said nothing at all. All
108
+ fifteen commands that need a harness now say so and name `/infinity:init`.
109
+
110
+ - **The phase picker did not offer RESEARCH** — a feature nobody could find.
111
+
112
+ - **A handoff in a one-shot `pi -p` run replaced the session out from under the instance that
113
+ asked for it**, so every handler afterwards touched a torn-down context and pi reported
114
+ "This extension ctx is stale after session replacement" on every turn. A headless run has no
115
+ next turn to hand anything to, so it does not hand off — and the extension now stops touching
116
+ pi the moment its session is shut down, whatever the reason.
117
+
118
+ - **A new phase counted as a stall.** The first failure of a phase was compared against the
119
+ fingerprint taken when the *previous* phase passed — identical, because nothing had happened
120
+ yet — so the run spent `retry` and `reframe` on the opening turn of every single phase, and
121
+ arrived at the rungs that matter with the cheap ones already gone. A stall is the agent
122
+ producing nothing when asked; a fresh brief has not asked yet.
123
+
124
+ - **The gate history counted every pass twice.** `runChecks` recorded the verdict and
125
+ `transitionPhase` recorded it again, so the history read `define:pass → define:pass`, which says
126
+ a phase had to be attempted twice — the opposite of what happened. Repeated *failures* are still
127
+ every one of them: that is the fact a human comes back to read.
128
+
129
+ ### Changed
130
+
131
+ - The dashboard shows every subtask, not only the active task's. The widget has nine rows and a job
132
+ to do with them; the browser has a whole page and a scrollbar, and hiding four of the five plan
133
+ levels there made it a worse copy of the widget rather than the place you go for the full picture.
134
+ - The widget carries `session N` once a run has spanned more than one, and says loudly when a phase
135
+ is waiting for your signature — a run parked on a human otherwise looks identical to a run that
136
+ quietly died.
137
+ - `describeInit` no longer tells you to describe what you are building. The wizard just asked.
138
+
139
+ ---
140
+
7
141
  ## [2.2.1] — 2026-08-23
8
142
 
9
143
  ### Fixed
package/README.md CHANGED
@@ -9,26 +9,31 @@ stops with a clear reason when it genuinely needs you.
9
9
  ```
10
10
  ╭──────────────────────────────────────────────────────────────────────────╮
11
11
  │ ∞ INFINITY ──────────────────────────────────────────────── BUILD rev 42 │
12
- Ship the payments rewrite behind a flag │
12
+ Ship the payments rewrite behind a flag │
13
13
  │ │
14
14
  │ ● define ─ ● plan ─ ◉ BUILD ─ ○ verify ─ ○ review ─ ○ ship │
15
15
  │ │
16
16
  │ ▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱ 45% 5/11 tasks · 1/2 features │
17
- │ ⚠ 1 blocked · ↷ 1 rework · retry 2/10
17
+ │ ⚠ 1 blocked · ↷ 1 rework · retry 2/10 · session 7
18
18
  │ │
19
- feature-002 · Checkout flow
20
- ● 4 validate cart totals against catalogue prices
21
- 5 apply stacked discount codes with precedence rules ← #4
22
- 6 handle partial refunds across split tenders ← #4, #5
23
- unit tests for tender split
24
- integration test against sandbox
25
- · audit log entries
26
- ○ 7 emit refund webhook ← #6
27
- 8 reconcile ledger nightly ← #6 │
28
- 3 more
19
+ Checkout 5/11
20
+ feature-002 · Checkout flow 2/5
21
+ 4 validate cart totals against catalogue prices
22
+ 5 apply stacked discount codes with precedence rules ← #4 │
23
+ ◐ 6 handle partial refunds across split tenders ← #4, #5
24
+ unit tests for tender split
25
+ integration test against sandbox
26
+ · audit log entries
27
+ 7 emit refund webhook ← #6 │
28
+ ⚠ 8 reconcile ledger nightly ← #6
29
+ │ ⋯ 3 below alt+j/k scroll · alt+o expand │
29
30
  ╰──────────────────────────────────────────────────────────────────────────╯
30
31
  ```
31
32
 
33
+ All five levels of the plan — goal, sprint, feature, task, subtask — with the window
34
+ centred on the work. `alt+j` / `alt+k` scroll it, `alt+o` expands it, and
35
+ `/infinity:dashboard` shows the whole thing in a browser.
36
+
32
37
  ---
33
38
 
34
39
  ## Why it exists
@@ -37,15 +42,23 @@ Left alone, a coding agent drifts. It declares work finished that isn't, forgets
37
42
  did, re-solves the same problem three different ways, and — worst of all — keeps going long after it
38
43
  stopped making progress. Pair it with a small or cheap model and all of that gets worse.
39
44
 
40
- infinity-harness fixes that by taking two decisions away from the model:
45
+ infinity-harness fixes that by taking three decisions away from the model:
41
46
 
42
47
  1. **When work is done.** A deterministic gate decides, not the agent. Same tree, same verdict, every
43
48
  time. No agent marks its own homework.
44
49
  2. **What happens next.** The phase pipeline is forward-only and one step at a time. The agent cannot
45
50
  decide it's bored of BUILD and jump to SHIP.
51
+ 3. **What to remember.** The plan, the phase and every budget live in files, so the run starts a
52
+ **fresh pi session at each boundary** instead of dragging the whole history of the run into every
53
+ request. Small models stop drowning; long runs stop turning into summaries of summaries.
54
+
55
+ Everything else — the retries, the model routing, the escalation ladder — exists to keep those
56
+ decisions honest over a very long run.
46
57
 
47
- Everything else the plan, the retries, the model routing exists to keep those two decisions
48
- honest over a very long run.
58
+ And one decision stays yours: **what gets built**. The gate can prove a feature has acceptance
59
+ criteria; it cannot prove they are the right ones. So the run stops and asks you to sign off
60
+ RESEARCH, DEFINE and PLAN — always in copilot, and in autopilot for whichever of them you choose to
61
+ keep.
49
62
 
50
63
  ## Install
51
64
 
@@ -83,9 +96,18 @@ Then, once, in that project:
83
96
  /infinity:init
84
97
  ```
85
98
 
86
- That is the whole setup. It detects your stack and its lint/test/build commands, writes
87
- `harness/` with the config, an empty plan, the phase and role docs, and starters for the
88
- documents the review gate will demand — then hands the model its first brief. It never
99
+ It detects your stack and its lint/test/build commands, then asks you five questions:
100
+
101
+ | | |
102
+ |---|---|
103
+ | **How involved do you want to be?** | copilot — you approve the definition and the plan · autopilot — you choose what to approve, if anything |
104
+ | **What are you building?** | One or two sentences. Asked in *both* modes, because a run with no goal has no business inventing one. |
105
+ | **Research it first?** | Adds an optional RESEARCH phase before DEFINE: prior art, constraints, options with costs, a recommendation, and the questions only you can answer. |
106
+ | **Which phases do you sign?** *(autopilot only)* | RESEARCH, DEFINE, PLAN — tick any, all or none. None is the walk-away setting. |
107
+ | **When should it start a fresh session?** | Every phase (default) · every task · never |
108
+
109
+ Then it writes `harness/` — the config, an empty plan, the phase and role docs, and starters
110
+ for the documents the review gate will demand — and hands the model its first brief. It never
89
111
  overwrites a file that already exists, and `/infinity:init force` restores anything you
90
112
  deleted without touching what you wrote.
91
113
 
@@ -95,7 +117,10 @@ the craft skills that match the work, and what to do next. Do the work, then:
95
117
  ```
96
118
  /infinity:validate run the gate for this phase
97
119
  /infinity:run hand it the wheel: validate → advance → re-brief, until done or stuck
120
+ /infinity:approve sign off the phase waiting for you — or send it back with a note
98
121
  /infinity:status where the run is right now
122
+ /infinity:scroll move the plan widget: up · down · top · bottom · expand · follow
123
+ /infinity:handoff continue this run in a fresh session, by hand
99
124
  /infinity:config change any setting, including which model runs which tier
100
125
  /infinity:models what models pi has, and how they are being routed
101
126
  /infinity:dashboard open the live web view
@@ -107,6 +132,61 @@ the craft skills that match the work, and what to do next. Do the work, then:
107
132
 
108
133
  `/infinity:run` is the point of the tool. It keeps the loop turning without you.
109
134
 
135
+ ## Who decides what
136
+
137
+ The two words are about **who signs off**, not about how autonomous the agent is. Both modes
138
+ run the same pipeline, the same gates and the same loop.
139
+
140
+ | | copilot | autopilot |
141
+ |---|---|---|
142
+ | RESEARCH, DEFINE, PLAN | you approve each one | you pick which, if any |
143
+ | Everything after PLAN | the gate decides | the gate decides |
144
+ | When it is right | you care what gets built | you have said what you want and you are leaving |
145
+
146
+ When a phase you signed up for passes its gate, the run **stops and asks you** rather than
147
+ advancing. Approving continues it; answering with a sentence sends the phase back carrying your
148
+ words, so it is redone against your objection rather than redone identically:
149
+
150
+ ```
151
+ /infinity:approve
152
+ /infinity:approve the criteria say nothing about refunds
153
+ ```
154
+
155
+ A rejection is pinned to the state of the project when you made it, so the run will not ask you
156
+ the same question again until the agent has actually changed something in response. If it never
157
+ does, the run stops and says so instead of nagging forever.
158
+
159
+ ## One run, many sessions
160
+
161
+ A harness that never starts a new pi session is a harness whose context window only ever grows.
162
+ By the tenth task the model is re-reading the history of the first nine in order to do the
163
+ tenth — paying for those tokens on every call, compacting them into a lossy summary once the
164
+ window fills, and, on a small model, simply drowning.
165
+
166
+ Nothing the harness knows lives in the conversation. The plan, the phase, the gate history, the
167
+ retry budgets and the escalation ladder are all files under `harness/`, so a session boundary
168
+ costs one thing: the brief — which is what the agent should have been working from anyway.
169
+
170
+ So the run hands itself to a fresh session at each boundary, and the replacement picks up
171
+ exactly where the last one stopped:
172
+
173
+ | Setting | Fresh session when |
174
+ |---|---|
175
+ | `phase` *(default)* | the pipeline advances a phase, or a goal pass finishes |
176
+ | `task` | that, plus every time the run moves to a different task |
177
+ | `off` | never — one session for the whole run |
178
+
179
+ Any of them also hands off early once the context passes `session.contextThreshold` (0.7 by
180
+ default), because a handoff that arrives after compaction has arrived too late to be the thing
181
+ that prevented it.
182
+
183
+ The run itself — its id, its wall-clock budget, its iteration ceiling, its no-progress strikes
184
+ and its position on the escalation ladder — lives in `harness/run.json` and is the same run
185
+ across every session it spans. `/reload`, `/resume`, closing the terminal and reopening it all
186
+ resume the same run rather than quietly starting a new one with fresh budgets.
187
+
188
+ The widget shows `session 7` once a run has spanned more than one.
189
+
110
190
  ### The first pass through
111
191
 
112
192
  DEFINE wants acceptance criteria on every feature, so start by telling it what you are building.
@@ -144,6 +224,8 @@ valid; the menu is the same data with prompts and bounds checking attached.
144
224
  |---|---|
145
225
  | **Models** | Which model runs each difficulty tier, the master model, consultation budget |
146
226
  | **Pipeline** | Which phases run, copilot vs autopilot, role strictness, pause |
227
+ | **Your approvals** | Which of RESEARCH / DEFINE / PLAN stop and wait for your signature |
228
+ | **Sessions** | Fresh session per phase or per task, the context threshold, the carry note |
147
229
  | **Project commands** | lint / test / coverage / build — what the gate actually runs |
148
230
  | **Gates** | Enable, coverage threshold, placeholder rejection |
149
231
  | **Continuous run** | Iteration ceiling, wall-clock budget, no-progress strikes |
@@ -152,11 +234,12 @@ valid; the menu is the same data with prompts and bounds checking attached.
152
234
  ## The pipeline
153
235
 
154
236
  ```
155
- define → plan → build → verify → [simplify] → review → ship
237
+ [research] → define → plan → build → verify → [simplify] → review → ship
156
238
  ```
157
239
 
158
240
  | Phase | What it's for | Gate opens when |
159
241
  |---|---|---|
242
+ | **research** | Find out what it actually has to be *(opt-in)* | `harness/docs/RESEARCH.md` says something a human could argue with |
160
243
  | **define** | Write down what's being built and how you'll know it's done | Every feature has acceptance criteria |
161
244
  | **plan** | Break features into ordered, dependency-aware tasks | Tasks exist and criteria are set |
162
245
  | **build** | Implement, one task at a time, tests alongside | Lint, tests, coverage pass; no placeholders; every task complete |
@@ -165,7 +248,14 @@ define → plan → build → verify → [simplify] → review → ship
165
248
  | **review** | Judge it as if someone else wrote it | Rubric, README, architecture doc and decisions are real; branch level with upstream |
166
249
  | **ship** | Tag, changelog, leave it clean | Clean tree, tagged, changelog, README, licence, no placeholders |
167
250
 
168
- Enable or disable phases in `harness/config.json` under `phases.enabled`. SIMPLIFY is off by default.
251
+ Enable or disable phases in `harness/config.json` under `phases.enabled`, or in the wizard.
252
+ RESEARCH and SIMPLIFY are off by default.
253
+
254
+ RESEARCH runs before anything is specified, and answers the question DEFINE assumes: is this the
255
+ right thing to build at all? It writes prior art, the constraints that are real, at least two
256
+ options with what each costs, a recommendation, what would make that recommendation wrong, and the
257
+ questions only you can answer — which become the DEFINE interview. Turn it on when the human gave
258
+ an idea rather than a specification.
169
259
 
170
260
  ## When it gets stuck
171
261
 
@@ -259,11 +349,22 @@ The agent edits it by submitting the **complete** task list through the `infinit
259
349
 
260
350
  ## Watching it work
261
351
 
262
- **In the terminal** — the widget above updates on every turn. It's responsive down to ~58 columns,
263
- degrades to ASCII when the locale isn't UTF-8, and drops colour under `NO_COLOR`.
352
+ **In the terminal** — the widget above updates on every turn, showing all five levels of the plan:
353
+ goal, sprint, feature, task, subtask. It is a *window*, not a truncation — the rows above and
354
+ below are counted, and one keypress away:
355
+
356
+ | Key | |
357
+ |---|---|
358
+ | `alt+j` / `alt+k` | scroll the plan down / up |
359
+ | `alt+o` | expand — every subtask, three times the rows |
360
+ | `/infinity:scroll follow` | back to tracking the active task |
361
+
362
+ It is responsive down to ~58 columns, degrades to ASCII when the locale isn't UTF-8, and drops
363
+ colour under `NO_COLOR`.
264
364
 
265
365
  **In a browser** — `/infinity:dashboard` serves a live page on loopback: phase rail, stacked progress
266
- meters that show stuck work as colour rather than absence, the full task tree, and the last gate
366
+ meters that show stuck work as colour rather than absence, the whole plan as a collapsible
367
+ goal → sprint → feature → task → subtask tree with counts at every level, and the last gate
267
368
  verdict. It refreshes itself every 5 seconds and reconnects with backoff if the run ends.
268
369
 
269
370
  The dashboard is strictly read-only and binds to `127.0.0.1`. It never writes, and never bumps
@@ -345,8 +446,12 @@ infinity-harness/
345
446
  │ ├── core/ types · paths · fsx · config · phases · gates · brief
346
447
  │ │ · featureList (the SSOT) · lock · exec
347
448
  │ │ · skills (match) · skillsAudit (guard)
348
- │ ├── ui/ theme · widget (terminal) · dashboard (web)
449
+ │ ├── ui/ theme · planTree (the five levels, once)
450
+ │ │ · widget (terminal) · dashboard (web) · wizard · config
349
451
  │ ├── loop.ts the continuous-run driver and its stop conditions
452
+ │ ├── runState.ts is a run armed, and which run is it — on disk, across sessions
453
+ │ ├── handoff.ts when to continue in a fresh session, and what to tell it
454
+ │ ├── approval.ts · intake.ts human sign-off · what the start-up wizard's answers mean
350
455
  │ ├── escalate.ts the ladder's actuator: chooses a rung and takes it
351
456
  │ ├── goal.ts the outer loop: is the thing asked for actually done?
352
457
  │ ├── taskList.ts atomic plan editor
@@ -358,11 +463,15 @@ infinity-harness/
358
463
  ├── harness/
359
464
  │ ├── features/feature-list.json the plan
360
465
  │ ├── config.json pipeline state and settings
466
+ │ ├── run.json the armed run — survives every session it spans
361
467
  │ ├── model-router.json optional routing
362
468
  │ ├── docs/ architecture · decisions · phase and role docs
363
469
  │ └── skills/ 28 craft skills the brief points at
364
- ├── tests/ 28 files, plain node:assert
365
- └── scripts/run-tests.mjs
470
+ ├── tests/ 31 files, plain node:assert
471
+ └── scripts/
472
+ ├── run-tests.mjs
473
+ ├── e2e.mjs 16 scenarios, including one against a real pi process
474
+ └── rig/ the real-pi driver: a scripted model + the RPC protocol
366
475
  ```
367
476
 
368
477
  The extension is deliberately thin. Every decision lives in `src/`, where it's typed and tested —
@@ -372,13 +481,28 @@ there is one implementation, and the adapter calls it.
372
481
 
373
482
  ```bash
374
483
  npm install
375
- npm run check # tsc --noEmit
376
- npm test # 28 test files
377
- npm run e2e # end-to-end against a live model
484
+ npm run check # tsc --noEmit, strict
485
+ npm test # 31 test files
486
+ npm run e2e # 16 end-to-end scenarios
487
+ npm run e2e -- --only realpi # just the ones that drive a real pi process
488
+ npm run e2e -- --list # what the scenarios are
378
489
  ```
379
490
 
380
491
  Tests are plain `node:assert` run under `--experimental-strip-types`. No framework, no build step.
381
492
 
493
+ The scenario worth knowing about is **`realpi`**. Everything else drives our own modules, or drives
494
+ the adapter against a *fake* pi — a fair test of our contracts and a poor test of pi's. Every bug
495
+ that has reached a user so far lived in the gap between the two: a BOM that made every config read
496
+ fail, a run that ended at its first session handoff, a brief queued in a delivery mode that
497
+ deadlocks `pi -p`.
498
+
499
+ `realpi` closes the gap. `scripts/rig/` starts a real `pi --mode rpc` against a scripted model
500
+ server and speaks the RPC protocol to it — typing prompts and slash commands, answering wizard
501
+ dialogs, and reading back the widget and notifications a human would actually see. It covers
502
+ startup, the wizard, a run spanning several real sessions, real auto-compaction, an approval
503
+ round-trip, and `pi -p` not hanging. When something is wrong in the product rather than in a
504
+ module, this is the scenario that notices.
505
+
382
506
  ## Licence
383
507
 
384
508
  MIT