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 +111 -0
- package/README.md +98 -7
- package/extensions/infinity-harness/index.ts +771 -10
- package/harness/docs/ARCHITECTURE.md +1 -1
- package/harness/docs/phases/define.md +27 -9
- package/harness/docs/phases/ship.md +1 -1
- package/harness/skills/code-review.md +2 -2
- package/harness/skills/context-hygiene.md +1 -1
- package/harness/skills/diagnosing-bugs.md +1 -1
- package/harness/skills/domain-modeling.md +2 -1
- package/harness/skills/prototype.md +2 -2
- package/package.json +1 -1
- package/src/core/brief.ts +14 -0
- package/src/core/gates.ts +45 -4
- package/src/core/init.ts +379 -0
- package/src/escalate.ts +370 -0
- package/src/goal.ts +411 -0
- package/src/loop.ts +158 -12
- package/src/taskList.ts +154 -7
- package/src/ui/widget.ts +15 -0
- package/src/unstuck.ts +46 -19
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
|
-
|
|
81
|
-
|
|
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
|
|
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
|
|
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/
|
|
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 #
|
|
376
|
+
npm test # 28 test files
|
|
286
377
|
npm run e2e # end-to-end against a live model
|
|
287
378
|
```
|
|
288
379
|
|