infinity-harness 2.0.4 → 2.2.0

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,117 @@ 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.2.0] — 2026-08-23
8
+
9
+ Nine modules shipped in this package, typechecked, and passed their tests while no code path in the
10
+ running product could reach a single one — about 2,800 lines, advertised in the README. They are all
11
+ connected now, and connecting them found four reasons the most important of them had never worked.
12
+
13
+ ### Added
14
+
15
+ - **The escalation ladder actually escalates.** `unstuck.ts` could always *choose* what to do when a
16
+ run stalled — retry → reframe → consult → rework → replan → master, with budgets, fingerprint
17
+ dedup and a cooldown — and nothing ever executed one. It was a chooser with no actuator, so
18
+ `/infinity:run` did the only thing it could when the gate kept failing: count three strikes and
19
+ stop. `src/escalate.ts` is the actuator. On a stall it climbs a rung, does the part that is ours
20
+ (flipping tasks to `rework`, naming the model to escalate to) and hands the agent an instruction
21
+ for the part that is the agent's. Every rung says something different; a run that gives up now
22
+ names every rung it spent first.
23
+ - **The goal loop turns.** `goalSpec`, `goalLoop` and `goalState` are a complete outer loop that
24
+ nothing ever drove, which meant the harness could finish a pipeline and declare "complete" without
25
+ anyone asking whether the thing the human asked for was done. `src/goal.ts` drives it, and the
26
+ mapping is the design: **one goal iteration is one full pass of the pipeline.** `/infinity:goal
27
+ <what you want>` states it; when the pipeline finishes, the run asks whether the GOAL is met, not
28
+ whether the plan is. A verdict of anything but `complete` must name what is still missing, and the
29
+ pipeline rewinds to the first phase with that list carried into the brief — so the next pass plans
30
+ for the remainder instead of rebuilding what the last review already accepted.
31
+ - **Five tools and three commands** for what was previously unreachable: `infinity_unstuck`,
32
+ `infinity_rework`, `infinity_replan`, `infinity_spawn_worker`, `infinity_goal`, and
33
+ `/infinity:goal`, `/infinity:unstuck`, `/infinity:rework`.
34
+ - **A `skills-load` advisory gate check.** The skills audit was the ninth orphan. It now runs at
35
+ DEFINE and REVIEW over any skills a project ships, so a project finds out that pi will print a
36
+ `[Skill conflicts]` block before its users do. Advisory: a malformed skill does not make the code
37
+ wrong.
38
+ - **The widget shows which pass you are on and the last rung taken.** A second pass at a goal looks
39
+ identical to a first one in every other part of the display, which is exactly when someone glances
40
+ at a half-full progress bar and walks away thinking it is nearly done.
41
+ - **Two E2E scenarios** — `escalation` and `goal` — driving both through the real adapter over real
42
+ projects, and the reachability allowlist in the `package` scenario is now **empty**.
43
+
44
+ ### Fixed
45
+
46
+ Wiring the ladder in exposed why it had never worked, none of which its own passing tests could see:
47
+
48
+ - **`reframe` had no budget**, so it was eligible forever and shadowed every rung below it. The
49
+ ladder could not climb past rung two — `consult`, `rework`, `replan` and `master` were unreachable
50
+ through the function whose job was to reach them.
51
+ - **`rework` and `replan` were vetoed unless the working tree had moved.** A stall is *defined* by
52
+ the tree not moving, so the two rungs that exist for exactly this situation could never fire in
53
+ it. That guard is a review-bounce policy — do not bounce REVIEW backwards again if nothing changed
54
+ — and it stays that for review bounces; the stuck ladder opts out explicitly.
55
+ - **The budgets counted effects on disk**, which only appear if the agent acts on the advice. A
56
+ stuck agent does not, so the budget never moved and the ladder jammed, offering `replan` forever.
57
+ Each rung now gets one turn per stall; the on-disk budgets still bound the run across stalls.
58
+ - **MASTER defaulted to a specific third-party model** — in a package whose 2.0.0 release promised
59
+ every routing slot ships empty. The last rung of the ladder silently redirected the hardest work
60
+ in the run to one vendor. It is `null` now, meaning "whatever pi is configured with", unless the
61
+ user chose one.
62
+
63
+ ### Changed
64
+
65
+ - A stalled iteration consults the ladder before the no-progress strike is spent, and a new rung
66
+ resets the streak — a different attempt is not another repetition of the same one. This cannot run
67
+ forever: every rung is bounded, so the ladder runs out, returns nothing, and the run stops with a
68
+ full account of what was tried.
69
+ - `loop-state.json` carries the ladder's position. State written before it existed loads fine.
70
+
71
+ ---
72
+
73
+ ## [2.1.0] — 2026-08-23
74
+
75
+ You could not start, and if you had, you could not have got past the first gate. Both are fixed.
76
+
77
+ ### Added
78
+
79
+ - **`/infinity:init`, and the `infinity_init` tool.** There was no way to create a harness. `pi
80
+ install` put the extension in place and then every command answered *"No harness in this project
81
+ (harness/config.json not found)"* — with nothing anywhere that made one. The package installed,
82
+ loaded, and passed its entire test suite while being unusable.
83
+
84
+ Init detects the stack and its lint/test/build commands, writes the config, an empty plan, the
85
+ phase and role docs the brief points at, and starters for the documents the review gate demands,
86
+ then hands the model its first brief. It asks two questions when there are dialogs and takes the
87
+ detected defaults when there are not, so an unattended run never stalls on a prompt. It never
88
+ overwrites an existing file, and `/infinity:init force` restores what was deleted without touching
89
+ what was written.
90
+ - **Feature names, acceptance criteria and the run's goal are writable through `infinity_plan`.**
91
+ Features are derived from task keys, so there was no input for their metadata — and the DEFINE
92
+ gate requires criteria on every feature. The first gate in the pipeline could only be passed by
93
+ hand-editing the plan file, which the brief explicitly tells you not to do.
94
+
95
+ `features` merges by id and never deletes, because features are inferred rather than submitted;
96
+ `tasks` keeps its omission-means-deletion rule. Leaving `tasks` out entirely is now distinct from
97
+ sending `[]`: absent means "not touching them", empty still means "delete them all". Nesting tasks
98
+ inside a feature — the obvious wrong guess — is refused with the shape that works.
99
+ - **A `coldstart` E2E scenario**: bare directory, `/infinity:init`, brief, plan, gate, advance,
100
+ through the real adapter. Every leg of it was a defect before it was a test.
101
+
102
+ ### Fixed
103
+
104
+ - **Six more shipped documents told the agent to run a CLI this package does not have** —
105
+ `infinity-harness contract propose`, `decision "..."`, `rollback list`, `checkpoint create`. The
106
+ 2.0.2 guard only caught a hardcoded list of verbs, which by construction only ever catches the
107
+ ones already found. It now looks at *where* the claim is made: inside a code fence or span, the
108
+ package name followed by a word is a command line, and there is no command line.
109
+ - **The plan view hid the thing the model is marked on.** Reading the plan listed tasks but not
110
+ features or their criteria — so the DEFINE gate judged something the model could not see. It now
111
+ shows the goal, each feature, and its criteria, flagging any feature that has none.
112
+ - **Comments counted as document content.** `docCheck` stripped headings but not HTML comments, so a
113
+ scaffolded file whose guidance lived in a comment would satisfy the gate that demanded it. A
114
+ comment is instructions to the author, not content.
115
+
116
+ ---
117
+
7
118
  ## [2.0.4] — 2026-08-23
8
119
 
9
120
  ### Changed
package/README.md CHANGED
@@ -77,20 +77,55 @@ cd your-project
77
77
  pi
78
78
  ```
79
79
 
80
- On session start the harness injects a brief — phase, role, current task, acceptance criteria, and
81
- what to do next. Do the work, then:
80
+ Then, once, in that project:
81
+
82
+ ```
83
+ /infinity:init
84
+ ```
85
+
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
89
+ overwrites a file that already exists, and `/infinity:init force` restores anything you
90
+ deleted without touching what you wrote.
91
+
92
+ From then on, every session opens with a brief: phase, role, current task, acceptance criteria,
93
+ the craft skills that match the work, and what to do next. Do the work, then:
82
94
 
83
95
  ```
84
96
  /infinity:validate run the gate for this phase
85
97
  /infinity:run hand it the wheel: validate → advance → re-brief, until done or stuck
98
+ /infinity:status where the run is right now
86
99
  /infinity:config change any setting, including which model runs which tier
87
100
  /infinity:models what models pi has, and how they are being routed
88
101
  /infinity:dashboard open the live web view
102
+ /infinity:goal state a goal and pursue it across passes
103
+ /infinity:unstuck what the escalation ladder would try next
104
+ /infinity:rework send a task and its dependents backwards
89
105
  /infinity:halt take the wheel back
90
106
  ```
91
107
 
92
108
  `/infinity:run` is the point of the tool. It keeps the loop turning without you.
93
109
 
110
+ ### The first pass through
111
+
112
+ DEFINE wants acceptance criteria on every feature, so start by telling it what you are building.
113
+ The agent writes that through `infinity_plan`:
114
+
115
+ ```jsonc
116
+ {
117
+ "goal": "Ship the payments rewrite behind a flag",
118
+ "features": [
119
+ { "id": "feature-001", "name": "Checkout flow", "criteria": ["refunds reconcile against the ledger"] }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ Features carry names and criteria; tasks are a separate list keyed `feature-001/task-001`, and
125
+ arrive in PLAN. Omitting a task deletes it — that is the rule that keeps the plan honest — but
126
+ omitting a *feature* just leaves it alone, because features are inferred from task keys rather
127
+ than submitted.
128
+
94
129
  ## Configuration
95
130
 
96
131
  Everything is configurable from inside pi:
@@ -132,6 +167,49 @@ define → plan → build → verify → [simplify] → review → ship
132
167
 
133
168
  Enable or disable phases in `harness/config.json` under `phases.enabled`. SIMPLIFY is off by default.
134
169
 
170
+ ## When it gets stuck
171
+
172
+ Stopping safely is the easy half. The hard half is trying something *else* first, and that is the
173
+ escalation ladder: when a run stalls — the gate fails and the working tree has not moved, meaning
174
+ the agent produced nothing — `/infinity:run` climbs it before spending a strike.
175
+
176
+ | Rung | What it does |
177
+ |---|---|
178
+ | **retry** | One more attempt. Sometimes a run is just slow. |
179
+ | **reframe** | State the assumption you have been working under, say why the evidence contradicts it, then try a different approach. |
180
+ | **consult** | Escalate to a stronger model, one step up the difficulty ladder. |
181
+ | **rework** | Flip the task and everything that depends on it back to `rework`. Work built on a broken thing is suspect until re-proved. |
182
+ | **replan** | The plan is wrong: something this needed was never planned. Amend it. |
183
+ | **master** | Last resort. State the problem from scratch, including what has been ruled out. |
184
+
185
+ Each rung gets one turn per stall, and each is bounded — reworks and replans have budgets, `consult`
186
+ has a per-task limit, `master` fires once per run. When the ladder runs out, the run stops and names
187
+ every rung it spent. Real progress resets it: a moving tree means a new problem, and a new problem
188
+ gets a fresh ladder.
189
+
190
+ `/infinity:unstuck` shows what it would try next without doing it.
191
+
192
+ ## Goals, and knowing when you are actually done
193
+
194
+ A finished pipeline is not a met goal. The gate decides whether the **work** is done; it has no
195
+ opinion on whether the work was the *right* work, because it only ever sees the plan — and the plan
196
+ is just what you thought the goal needed when you wrote it.
197
+
198
+ ```
199
+ /infinity:goal Ship the payments rewrite behind a flag
200
+ ```
201
+
202
+ That states the goal and starts pass 1. One pass at the goal is one full trip through the pipeline.
203
+ When the pipeline completes, the run does not end: it asks whether the goal is met.
204
+
205
+ - **complete** ends the run.
206
+ - Anything else must name what is still missing — and the pipeline rewinds to the first phase with
207
+ that list carried into the brief, so the next pass plans for the remainder rather than rebuilding
208
+ what the last review already accepted.
209
+
210
+ Bounded by an iteration ceiling and a wall clock, both configurable. The widget shows which pass you
211
+ are on, because a second pass looks exactly like a first one otherwise.
212
+
135
213
  ## Knowing when to stop
136
214
 
137
215
  This is the part that makes an unattended run safe. `/infinity:run` halts on any of:
@@ -166,7 +244,12 @@ brief all read it; nothing caches a second copy.
166
244
 
167
245
  The agent edits it by submitting the **complete** task list through the `infinity_plan` tool:
168
246
 
169
- - **Omission means deletion.** One unambiguous rule beats incremental edits a model loses track of.
247
+ - **Omission means deletion** — for tasks. One unambiguous rule beats incremental edits a model
248
+ loses track of. Leaving the `tasks` field out entirely is different from sending an empty one:
249
+ absent means "not touching them", empty means "delete them all".
250
+ - **Features are a merge, not a submission.** They are inferred from task keys, so they are never
251
+ resubmitted wholesale; `features` supplies names and acceptance criteria by id, and omitting one
252
+ leaves it alone.
170
253
  - **`baseRevision` guards every write.** A stale revision is rejected, so parallel workers can't
171
254
  clobber each other.
172
255
  - **Unknown fields survive.** An update merges onto the stored task, so `difficulty`, `modelHint`,
@@ -241,11 +324,17 @@ that looks like a broken endpoint but is only a small cap.
241
324
 
242
325
  | Tool | Purpose |
243
326
  |---|---|
327
+ | `infinity_init` | Create the harness in this project |
244
328
  | `infinity_brief` | What am I supposed to be doing right now? |
245
- | `infinity_plan` | Read or rewrite the task list |
329
+ | `infinity_plan` | Read or rewrite the plan tasks, features, criteria, goal |
246
330
  | `infinity_validate` | Run the gate for this phase |
247
331
  | `infinity_advance` | Move to the next phase (refuses on a failing gate) |
248
332
  | `infinity_dashboard` | Start/stop/query the web view |
333
+ | `infinity_unstuck` | What should I try next? (recommends; does not act) |
334
+ | `infinity_rework` | Send a task and its dependents back to rework |
335
+ | `infinity_replan` | Add what the plan was missing, mid-run |
336
+ | `infinity_spawn_worker` | Attempt one task in a clean-room worker |
337
+ | `infinity_goal` | State a goal, review it, or check which pass it is on |
249
338
 
250
339
  ## Layout
251
340
 
@@ -258,19 +347,21 @@ infinity-harness/
258
347
  │ │ · skills (match) · skillsAudit (guard)
259
348
  │ ├── ui/ theme · widget (terminal) · dashboard (web)
260
349
  │ ├── loop.ts the continuous-run driver and its stop conditions
350
+ │ ├── escalate.ts the ladder's actuator: chooses a rung and takes it
351
+ │ ├── goal.ts the outer loop: is the thing asked for actually done?
261
352
  │ ├── taskList.ts atomic plan editor
262
353
  │ ├── worker.ts isolated per-task workers
263
354
  │ ├── modelRouter.ts difficulty ladder + consultation
264
355
  │ ├── rework.ts · replan.ts backward rework with BFS impact · mid-build amendment
265
356
  │ ├── unstuck.ts · review.ts escalation strategy matrix · review bounce guard
266
- │ └── goalLoop.ts · goalState.ts · goalSpec.ts
357
+ │ └── goalLoop.ts · goalState.ts · goalSpec.ts goal state machine and its store
267
358
  ├── harness/
268
359
  │ ├── features/feature-list.json the plan
269
360
  │ ├── config.json pipeline state and settings
270
361
  │ ├── model-router.json optional routing
271
362
  │ ├── docs/ architecture · decisions · phase and role docs
272
363
  │ └── skills/ 28 craft skills the brief points at
273
- ├── tests/ 23 files, plain node:assert
364
+ ├── tests/ 28 files, plain node:assert
274
365
  └── scripts/run-tests.mjs
275
366
  ```
276
367
 
@@ -282,7 +373,7 @@ there is one implementation, and the adapter calls it.
282
373
  ```bash
283
374
  npm install
284
375
  npm run check # tsc --noEmit
285
- npm test # 23 test files
376
+ npm test # 28 test files
286
377
  npm run e2e # end-to-end against a live model
287
378
  ```
288
379