bareloop 0.14.0 → 0.16.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 +218 -0
- package/bareloop.context.md +374 -49
- package/package.json +2 -2
- package/src/authorjob.js +2 -2
- package/src/codeversion.js +155 -0
- package/src/index.js +12 -0
- package/src/ledger.js +1 -1
- package/src/promptregisters.js +93 -0
- package/src/replay.js +1212 -0
- package/src/reuse.js +11 -13
- package/src/run.js +14 -0
- package/types/codeversion.d.ts +18 -0
- package/types/index.d.ts +2 -0
- package/types/ledger.d.ts +14 -0
- package/types/promptregisters.d.ts +31 -0
- package/types/replay.d.ts +195 -0
- package/types/reuse.d.ts +10 -11
package/bareloop.context.md
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
# bareloop — Integration Guide
|
|
2
2
|
|
|
3
|
-
> **Current
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
3
|
+
> **Current: v0.16.0.** The verdict classes are **`green` + `soft-green`**. `soft-green` has
|
|
4
|
+
> a real judged floor (`judged-floor`, `src/judged.js`): a pinned haiku-4.5 judge LOCATEs
|
|
5
|
+
> facts with quotes from the real artifact, an arbiter-owned `decide()` renders pass/fail from
|
|
6
|
+
> a signed rubric card, and *unsure* is a red, never a pass — bought by a signed calibration
|
|
7
|
+
> gate (ten graded truths, five resisted lies, floor 10/10, itemized reds). A soft-green pass
|
|
8
|
+
> mints its bridge row QUARANTINED: no learning credit until an `accept` releases it, forward
|
|
9
|
+
> only; a rerun records a disposition and its outcome is a NEW run's own row, born held
|
|
10
|
+
> again. Every run ends at the signer's **review door** — `accept` / `rerun` / `pause` — which
|
|
11
|
+
> records a disposition and **never changes the loop's own verdict** (`runPlan` still returns
|
|
12
|
+
> `green`/`already-green` exactly as before). The door
|
|
13
|
+
> is open **always for `soft-green`**, and **opt-in for `green`** via `reviewDoor: true` /
|
|
14
|
+
> `--review-door` (the flag wins in both directions, never inferred); `accept` re-runs the
|
|
15
|
+
> close's MECHANICAL stages only — never a judged floor, never a person. Shipped in v0.12.0
|
|
16
|
+
> (2026-08-23); see CHANGELOG `## [0.12.0]` onward for the softgreen rung and the fixes/rungs
|
|
17
|
+
> that followed through v0.15.0. **The plan shape is the ONLY shape**: the legacy
|
|
7
18
|
> operator-authored `steps[]` path, config-v1 and the draft-PR hitl step were deleted
|
|
8
19
|
> 2026-07-26 (PRD v1.32), so `interpret`, `validateConfig` and `extractRules` are gone from
|
|
9
|
-
> the public surface — a breaking change.
|
|
10
|
-
>
|
|
11
|
-
> the
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
|
|
15
|
-
>
|
|
16
|
-
> the
|
|
17
|
-
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
> `
|
|
21
|
-
>
|
|
22
|
-
>
|
|
23
|
-
>
|
|
24
|
-
> gets a `judged-floor` stage behind a signed rubric card and a signed calibration set.
|
|
25
|
-
> **Everything documented in this file describes the code as it actually is on this branch**,
|
|
26
|
-
> where `hitl` IS admitted, `human-confirms` IS live, and the three `hitl-*` terminals exist —
|
|
27
|
-
> integrate against what is written here, and expect the class-level rename when the next rung
|
|
28
|
-
> lands. Records: PRD Addendum v1.71 and
|
|
20
|
+
> the public surface — a breaking change. Runs on two worker surfaces — the API `Loop` and,
|
|
21
|
+
> for `clipipe-subscription`, the CLI's native tool channel (module 4d). API sections fill in
|
|
22
|
+
> as build-ladder rungs land (PRD §10). What is settled — the boundary, the architecture, the
|
|
23
|
+
> refusals, the constraints — is settled for good. Per LIBRARY_CONVENTIONS §3 this file ships
|
|
24
|
+
> with the package and is the complete adopter contract; the README is only the pitch.
|
|
25
|
+
|
|
26
|
+
> **LEGACY — `hitl` (retired by design, PRD Addendum v1.71, 2026-08-17).** `hitl` remains
|
|
27
|
+
> ADMITTED in code today — `VERDICT_TYPES` still lists it, the `human-confirms` stage kind is
|
|
28
|
+
> still live (never offered, at-most-once, must be last), and the two `hitl-*` terminals
|
|
29
|
+
> (`hitl-pause`, `hitl-decision-red`) still exist and still work exactly as documented
|
|
30
|
+
> wherever this file describes them below. It is retired as a *design direction*, not deleted
|
|
31
|
+
> as *code*: `LOCKED_CLASSES` is empty, so nothing refuses a `hitl` job today, but new
|
|
32
|
+
> integrations should target `green` / `soft-green` plus the review door instead — that pair
|
|
33
|
+
> is the forward path for anything needing a person's judgement, and `hitl`'s removal is a
|
|
34
|
+
> future breaking change, not yet scheduled. Records: PRD Addendum v1.71,
|
|
29
35
|
> `docs/product/2026-08-17-softgreen-review-door-design.md` (repo-only, not shipped);
|
|
30
36
|
> evidence F102–F105.
|
|
31
37
|
|
|
@@ -110,8 +116,8 @@ the step plan at run time (gated by `validatePlan`); the human signs only:
|
|
|
110
116
|
| field | shape | notes |
|
|
111
117
|
|---|---|---|
|
|
112
118
|
| `goal` | non-empty text | what the agent plans against |
|
|
113
|
-
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen).
|
|
114
|
-
| `close` | **an ORDERED LIST of named stages** `[{ name, cmd, expect, judged?, gapKeep?, offer?, needs? }, ...]` (PRD v1.28), or a close object (table below)
|
|
119
|
+
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). **All three are ADMITTED today** — `LOCKED_VERDICTS` is empty, so nothing reds `request-red` on the class itself. `soft-green` has its judged floor (`judged-floor`, signed rubric card + calibration gate) and is the forward path for a job needing judgement, paired with the end-of-run review door; `hitl` is LEGACY — retired as a design direction (see the direction note at the top) but still admitted and still works exactly as documented here. Every `request-red` (e.g. for a locked TOOL verb, not a verdict class) also carries `lib` — the territory the demand lands against, stamped at the emit site (`verdictType` → `bareloop`, a locked tool verb → `bare-agent`): the ledger keys and its `suggestedAsk` seed on it, so a bareloop-catalogue refusal never files as an upstream ask |
|
|
120
|
+
| `close` | **an ORDERED LIST of named stages** `[{ name, cmd, expect, judged?, gapKeep?, offer?, needs? }, ...]` (PRD v1.28), or a close object (table below) | the destination, the only thing hand-authored; the check menu DERIVES from it (below). The plan flow executes a staged close directly and adapts a bare `predicate` object into a one-stage list; a `gold`/`rubric`/`hitl` object close still VALIDATES (`CLOSE_TYPES`) but the plan flow refuses all three at runtime as `close-unsupported` — an object close names no command to run. The modern route to a judged or human-decided close is a staged `closeDecl` (`judged-floor` / `human-confirms` stage kinds), not this object form |
|
|
115
121
|
| `closeDecl` | **the AUTHORED close** `{ genre: "TYPES", lang: "js"\|"python", stages: [{ name, kind, params }], notes? }` | the ALTERNATIVE to `close`, and the point of the close-authoring rung: the user answers an interview and an LLM composes a DECLARATION over kinds whose implementations we own — never a script, never a shell fragment, never a new kind. **`close` and `closeDecl` are alternatives**: declaring both reds `close-duplicated` (two closes are two arbiters). It is HARD-class by construction (`CLASS_BY_CLOSE.declared`), so a locked verdict on one reds `close-hierarchy` as well as `request-red`. The declaration is validated by `validateCloseDecl`; the TREE-GROUNDED half of that gate (the path rule, and the scoped-job derivation that arms the F84 one-population law) is DEFERRED at spec-validation time — a job spec is validated with no repository in hand — and the runner re-runs it GROUNDED against the real seed before any stage and before any token. It stores the counting RULE and never a number (D12): there is no seed field, and `baseline: "seed"` is measured at each run's own HEAD |
|
|
116
122
|
| `checks` | **RETIRED** (PRD v1.28/v1.32) | hand-authored checks are gone, not merely discouraged: declaring `checks` reds `checks-derived` by name. The check menu is DERIVED from the close's own stages instead — see **Staged close** below. The hazard this removes is measured, not theoretical: job #5's three hand-written checks were re-implementations of three stages the close already ran, and a hand-carved copy can drift LENIENT (the worker passes the operator's ruler and fails the real inspection) |
|
|
117
123
|
| `tools` | optional unique subset of `TOOL_MENU` (14 verbs, below) | the CEILING every plan step's grant must fit inside (omitting it means the full menu — and the hash is taken over that RESOLVED form, so a `TOOL_MENU` widening flips an omitted-`tools` spec's hash and forces a re-sign; see `jobSpecHash`, MED-1); `run` is `LOCKED_TOOLS` and reds `request-red` — locked-but-listed, and the red IS the admission evidence the ledger tallies (a typo stays `invalid-value`). A ceiling of write-class and store-class verbs ONLY reds `invalid-value`: the scout surveys read-only, so it would be handed an empty menu and survey blind |
|
|
@@ -203,14 +209,15 @@ redo it. Write the goal from the close's own stage list, in the close's own word
|
|
|
203
209
|
a few explicit requirements rather than prose. Preflight already names every stage on the
|
|
204
210
|
`check-menu` spine event — read it back and check your goal against it before you sign.
|
|
205
211
|
|
|
206
|
-
**Close types and the hierarchy — the OBJECT form, which
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
(`human-confirms` inside a `closeDecl`)
|
|
213
|
-
|
|
212
|
+
**Close types and the hierarchy — the OBJECT form, which the plan flow only partially
|
|
213
|
+
supports** (a close is data, never code; verdict-class laundering is a named red
|
|
214
|
+
`close-hierarchy`). The go-forward shape is the staged list above; a `predicate` object is
|
|
215
|
+
legal shorthand for a one-stage list and the plan flow adapts it, but `gold`/`rubric`/`hitl`
|
|
216
|
+
validate and then refuse at runtime (`close-unsupported`) regardless of verdict class — the
|
|
217
|
+
plan flow has never executed an object close beyond `predicate`. `hitl` (LEGACY) is a STAGE
|
|
218
|
+
(`human-confirms` inside a `closeDecl`) and `soft-green`'s judged floor is likewise a STAGE
|
|
219
|
+
(`judged-floor` inside a `closeDecl`) — never close TYPES — so both OBJECT forms below stay
|
|
220
|
+
refused at runtime exactly as they were:
|
|
214
221
|
|
|
215
222
|
| type | fields (exact — extras red) | legal class |
|
|
216
223
|
|---|---|---|
|
|
@@ -484,17 +491,19 @@ signed `writeScope` alone, never a free-text fallback. Menus exported: `EXIT_TYP
|
|
|
484
491
|
The one layer where a human still wrote code. A close used to be a hand-written script
|
|
485
492
|
per patient; now the user PICKS A VERDICT CLASS, answers that class's questions, and an LLM
|
|
486
493
|
composes a DECLARATION over kinds whose implementations bareloop owns. **v1 admits exactly
|
|
487
|
-
ONE genre** (`TYPES`: a type checker stops complaining without breaking the tests)
|
|
488
|
-
|
|
489
|
-
is COUNTED.
|
|
494
|
+
ONE genre** (`TYPES`: a type checker stops complaining without breaking the tests) —
|
|
495
|
+
everything else refuses honestly, and every refusal is COUNTED.
|
|
490
496
|
|
|
491
497
|
**The verdict class is the USER's answer (PRD v1.57 §1), and it DRIVES the authoring.**
|
|
492
498
|
`verdictType` is a declared radio the preflight validates, never inferred:
|
|
493
|
-
`VERDICT_CLASSES` = `green` | `soft-green` | `hitl`, with `LOCKED_CLASSES` =
|
|
494
|
-
`soft-green`
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
`
|
|
499
|
+
`VERDICT_CLASSES` = `green` | `soft-green` | `hitl`, with `LOCKED_CLASSES` = **empty** —
|
|
500
|
+
`soft-green` shipped its judged floor in v0.12.0 (2026-08-23) and is fully admitted
|
|
501
|
+
alongside `green`; `hitl` is LEGACY (retired as a class by the 2026-08-17 design, PRD
|
|
502
|
+
Addendum v1.71) but still admitted too, since removal is a future breaking change. The
|
|
503
|
+
review door (`accept`/`rerun`/`pause`) that re-homes hitl's pause machinery is shipped and
|
|
504
|
+
live — always on for `soft-green`, opt-in for `green` via `reviewDoor: true` /
|
|
505
|
+
`--review-door`. A future LOCKED_CLASSES entry would still return a counted `request-red`
|
|
506
|
+
refusal at ADMISSION — before that class's questions are ever asked. The
|
|
498
507
|
pick is also a PROMISE: every catalogue kind carries the `verdictClass` it can honestly
|
|
499
508
|
render, `closeCeiling(declaration)` reports the highest class a declaration demands, and a
|
|
500
509
|
declaration ABOVE the pick is a `class-ceiling` red naming the kind that raised it (inert
|
|
@@ -505,7 +514,7 @@ a step, or test it without a provider.
|
|
|
505
514
|
|
|
506
515
|
| step | call | what it is |
|
|
507
516
|
|---|---|---|
|
|
508
|
-
| interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. THREE frozen question sets keyed by verdict class (`QUESTION_SETS`; `questionsFor(cls)` / `requiredAnswersFor(cls)`), and the interview asks NOTHING about a genre and NOTHING about the repository. **Ask the library for the set and its numbers — never hardcode a count or a slot number**: the green set has lost two slots since it was frozen (D13's genre confirm, then the repo question, dropped 2026-08-15 because `repoPath` is mandatory structured input and asking for it again invites a second, drifting answer for a fact the machine already holds), and it renumbers CONTIGUOUSLY from 1 each time rather than leaving a gap. Today the green set is five questions, answers keyed by number; the
|
|
517
|
+
| interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. THREE frozen question sets keyed by verdict class (`QUESTION_SETS`; `questionsFor(cls)` / `requiredAnswersFor(cls)`), all three LIVE as of softgreen module 3 — nothing is locked today (`LOCKED_CLASSES` is empty) — and the interview asks NOTHING about a genre and NOTHING about the repository. **Ask the library for the set and its numbers — never hardcode a count or a slot number**: the green set has lost two slots since it was frozen (D13's genre confirm, then the repo question, dropped 2026-08-15 because `repoPath` is mandatory structured input and asking for it again invites a second, drifting answer for a fact the machine already holds), and it renumbers CONTIGUOUSLY from 1 each time rather than leaving a gap. Today the green set is five questions, answers keyed by number; the LEGACY `hitl` set is those five byte for byte plus one more (what the signer is deciding when they look at the result — `human-confirms`' `ask`), which is the spread's next number and moves with a green-side deletion; the `soft-green` set is likewise those five plus two more — Q6 the signed rubric card (what separates a pass from a fail) and Q7 the frozen calibration set (one example to pass, one to fail) — the judged floor's two required inputs. `verdictType` and `repoPath` are STRUCTURED input, never parsed out of prose. Answers are scrubbed at INGEST |
|
|
509
518
|
| survey | `runAuthorScout({ workdir, provider, attempts?, ceilingUsd? })` → `{ state, facts, reason, meta, raw, raws, calls, budgetStop }` | a bounded READ-ONLY LLM survey. Read-only by MENU CONSTRUCTION (`AUTHOR_SCOUT_VERBS` = the full menu minus write-class and store-class verbs), 8 rounds, F59's reserved toolless final round. **`state: 'ABSENT'` means the scout did not complete — never "no special facts are needed"**, and a parsed `{}` is one of its five ABSENT routes. Up to `SCOUT_ATTEMPTS` (**3**, hardcoded — PRD v1.58) attempts: `attempts` is TIGHTEN-ONLY and CLAMPS rather than throws (floor **1**, because a scout that never ran is an ABSENT nobody can act on), the direction every cap in this system runs in. **A retry fires on the typed MALFORMED class alone** (`SCOUT_RETRY_CAUSES` = `empty` \| `unparseable`); what is excluded is excluded for a reason — `call-failed` covers transport and `truncated:max_tokens`, both provider-red with NO redraft; `short` is F59's cut-off population and already has its own instrument INSIDE the attempt; `non-object` and `empty-object` are valid JSON with wrong or vacuous content, which is a SEMANTIC failure, and F38/F39 measured what re-asking one buys (the same distribution, sampled twice) — that is the self-healing line, and it is not crossed here. The re-ask is TOOLLESS over the conversation the survey already produced (the repository was already read; only the emission was unreadable) and names the mechanical parse error and nothing else. **There is no JSON repair behind it and there never will be** — a repairer decides what the model MEANT and writes it down as though the model had said it, in the one artefact whose whole job is to be honest about what a repository contains. `meta.attempts` / `meta.attemptsAllowed` record what was paid for; every attempt is metered under its own label (`author-scout`, `author-scout#2`, …) and leaves its raw behind. **`ceilingUsd` is the operator's money ceiling** (PRD v1.62) — checked BETWEEN attempts and before F59's reserved round, so no paid call escapes it; `null`/absent is UNBOUNDED, and a non-null non-finite value THROWS (the shared `capStop` seam — see `authorClose` below). When it ends the ladder, `budgetStop` names which stop (`cap-halt` \| `pricing-red`) and a survey that was never asked for carries the typed cause `not-funded` — deliberately OUTSIDE `SCOUT_RETRY_CAUSES`, because a retry is precisely what the cap forbade |
|
|
510
519
|
| listing | `buildSeedListing({ workdir, seedRef, sourcePaths, testPaths })` | mechanical, `$0`, no model. `files` is the WHOLE tree (what the validator judges paths against); `block` is scoped to the survey's own paths and capped in ANNOUNCED tiers (what the prompt carries). Handing the validator the scoped half would make a job scoped to `src/` read as whole-tree and silently disarm the one-population law |
|
|
511
520
|
| authoring | `authorClose({ workdir, seedRef, lang, verdictType, answers, scout, listing, generate, ceilingUsd? })` | the grounded loop: author → validate → run EVERY stage at the seed → feed the MEASURED results back → revise, bounded at `MAX_REVISIONS` (2) — a passed `maxRevisions` is TIGHTEN-ONLY, so a caller may LOWER that ceiling and never buy more revise rounds than the constant (floor **0**: one authoring call with no revise round is a legal ask, and unlimited revising would launder thrash as adaptation) — early-stop on an unchanged declaration. The declaration is emitted through a SCHEMA-FORCED TOOL CALL (`declare_close`), never parsed out of prose; the feedback is EXECUTION OUTPUT only — no model ever reviews another model's close. The return carries **`raws`**: what every model call actually SAID, the scout's attempts absorbed together with the declaration's, in the cost book's own order and under the same labels it meters. Each is `{ label, attempt, bytes, trimmed, text, cause, reason }` through the ONE persist helper (`scrubRaw` — redacted over the ONE `SECRET_PATTERNS` inventory, since a raw is the record most likely in this system to carry a live credential; bounded at `RAW_PERSIST_MAX` (8000) with the trim ANNOUNCING its own full size, and the cut walked back off a continuation byte so a multi-byte character is never split). `raws` is present on EVERY path **including the `$0` preflight refusals** — the path that spends nothing more is exactly the one whose evidence used to vanish with the process. `iterations` records what each turn MEANT (declaration, validation, seed read); `raws` records what it SAID, and a malformation is only ever visible in the second. **`ceilingUsd` is the same operator ceiling the survey took** — the scout's spend is ABSORBED into this loop's cost book before its first call is weighed, so spend already incurred folds in and re-entering cannot silently widen it. It is checked immediately before EVERY paid call (the author call, each revise, and each malformed-emission retry). **A MALFORMED ceiling is an ERROR, not a silent unbounded run** (v1.64 §3): the seam every ceiling read goes through (`capStop`, `src/text.js`) THROWS on a non-null non-finite value — `'2.50'`, `NaN`, `Infinity`, `true`, `{}` — before the first paid call, and the throw propagates uncaught rather than being caught into an ABSENT survey, because a caught one would hand you the very silent-unbounded run it exists to prevent. `null`/omitted is the STATED operator choice for unbounded; `0` and negatives are finite, legal, and cap-halt immediately. The advertised ceiling and the enforced ceiling can therefore never be different numbers. A `cap-halt` here can arrive with `ok:true` — a close that was already validated and measured survives the money stop, exactly as a late provider casualty does |
|
|
@@ -1640,13 +1649,17 @@ in the ledger whatever the person then does with it.
|
|
|
1640
1649
|
write}` — the terminal registry write for a runner that drives `runJob` directly** (rather
|
|
1641
1650
|
than through `runReuse`, which writes its own rows). It is what gives that runner's review
|
|
1642
1651
|
door a row to release: without one, `accept` reaches `recordDoor` and reds `no-row-for-run`.
|
|
1643
|
-
STORAGE ONLY — nothing here selects, promotes or reuses a bridge.
|
|
1644
|
-
named `reason`: no `registryDir` (`no-registry` — the registry is operator-supplied, never
|
|
1652
|
+
STORAGE ONLY — nothing here selects, promotes or reuses a bridge. Three refusals, each with
|
|
1653
|
+
a named `reason`: no `registryDir` (`no-registry` — the registry is operator-supplied, never
|
|
1645
1654
|
conjured), `outcome !== 'green'` (`green-predates-run` for already-green — accept confirms a
|
|
1646
|
-
verdict, it never mints one — else `not-green`), no executed `plan` (`no-plan-executed`,
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
row is born
|
|
1655
|
+
verdict, it never mints one — else `not-green`), and no executed `plan` (`no-plan-executed`,
|
|
1656
|
+
R1). Every green class mints (hamr's ruling 2026-08-30): a `soft-green` row is born HELD —
|
|
1657
|
+
its learning credit is quarantined until a signer accepts at the door — and a `green`-class
|
|
1658
|
+
row is born RELEASED, because a machine's own close proved it and there was never a credit
|
|
1659
|
+
to quarantine; `quarantinesCredit(verdictType)` in `greenParts` is the one predicate, read
|
|
1660
|
+
off the record's own `verdictType`, never a flag. `door-accept` on a released row records
|
|
1661
|
+
the disposition and releases nothing — not an error; a run that earned no row at all still
|
|
1662
|
+
refuses `no-row-for-run`. `name` falls back to `job.job`. A
|
|
1650
1663
|
refused underlying write returns `{minted: false, reason: 'write-refused', write}` with the
|
|
1651
1664
|
reds on `write` — never a throw. Minting goes through the same cold-leg green write `runReuse`
|
|
1652
1665
|
uses (shape-fork rule and collision refusals included), so the two runners can never spell
|
|
@@ -2142,6 +2155,318 @@ is not a spend record and no spend-slicing instrument needs to account for it.
|
|
|
2142
2155
|
(`no record` if the run ended before the summary could fire); `scripts/behaviour-readout.mjs`
|
|
2143
2156
|
prints the same line when handed the run's spine file as its optional third positional.
|
|
2144
2157
|
|
|
2158
|
+
|
|
2159
|
+
### `replayRun(spineEvents, auditEvents?, { runId? })` / `formatReplay(summary)` — `src/replay.js`
|
|
2160
|
+
|
|
2161
|
+
PRD build-list item 6: today no failed run can be reconstructed in under five minutes — every
|
|
2162
|
+
byte is on disk (the spine JSONL + its gate-audit sidecar) but only hand-slicing JSONL reads it.
|
|
2163
|
+
`replayRun` is a report-only reader over BOTH files: **reads only, mints no verdict, writes
|
|
2164
|
+
NOTHING to the spine**, same posture as `runBehaviour` above, which it reuses rather than
|
|
2165
|
+
reimplements for the tool-call breakdown. It returns one plain object:
|
|
2166
|
+
|
|
2167
|
+
```
|
|
2168
|
+
{ runId, job, goal, budgetUsd, specHash, branch, verdictType, model, code,
|
|
2169
|
+
outcome, stopReason, spentUsd, spendComplete, wallMs, chainClock,
|
|
2170
|
+
resumed, resumeSeed, thisFileSpend, spendMismatch,
|
|
2171
|
+
timelineKind: 'steps'|'iterations', replans, close,
|
|
2172
|
+
steps: [{ id, occurrence, outcome, rounds, toolCalls, checks: {passed, failed},
|
|
2173
|
+
treeChanged, wallMs, spentUsd, unpricedRounds, tripped }],
|
|
2174
|
+
iterations: [{ iteration, verdict, boundary, closeStage, rounds, toolCalls,
|
|
2175
|
+
wallMs, spentUsd, unpricedRounds, tripped }],
|
|
2176
|
+
ending: { record, before /* the 3 records before it */, escalation },
|
|
2177
|
+
lastEscalation, behaviour, memoryCache, skipped }
|
|
2178
|
+
```
|
|
2179
|
+
|
|
2180
|
+
**`verdictType`/`model`** (F117, PRD TODO #20 — hamr blessed the whole item verbatim, landed
|
|
2181
|
+
2026-08-25): `verdictType` reads `job-start.verdictType` (`src/run.js:303`) — a REQUIRED spec
|
|
2182
|
+
field (`src/job.js:552` reds `missing-required` before job-start ever fires), so `null` here
|
|
2183
|
+
means only "this spine predates the field" (measured: `u-msdsmkid`, an Aug-3 archived run),
|
|
2184
|
+
never "unset". `model` reads `job-start.model`, present only when the shell-owned provider
|
|
2185
|
+
binding itself carried a `.model` string at job-start time — `src/planrun.js:76`'s own comment
|
|
2186
|
+
documents bare-agent's Loop reading `baseProvider.model` directly off that same object, so it is
|
|
2187
|
+
a real, citable, synchronously-available property, not a guess; a native/clipipe call path (or
|
|
2188
|
+
any provider double with no such field — measured on `tests/helpers.js`'s `scriptedProvider`)
|
|
2189
|
+
legitimately has neither, and this reader reports that honestly rather than defaulting one in.
|
|
2190
|
+
`formatReplay` prints both right after `shape:` — `class: softgreen` / `class: not recorded
|
|
2191
|
+
(pre-F117 spine)`, `model: claude-sonnet-5` / `model: not recorded`
|
|
2192
|
+
(the two "not recorded" wordings are deliberately different: a missing `verdictType` is always
|
|
2193
|
+
an archive-age gap, a missing `model` can happen on any spine, old or new). `--all` gains a
|
|
2194
|
+
`class` column right after `shape` and a `model` column right after `class` (the verdict class /
|
|
2195
|
+
the model string, or `-`) — `model` is kept FULL, never truncated, unlike the compact `reason`
|
|
2196
|
+
column: a wrong or uncertain model reading is exactly the thing a directory-wide scan needs in
|
|
2197
|
+
full, not cut.
|
|
2198
|
+
|
|
2199
|
+
**`code`** (F118, the run→code direction — the commit→run direction is the prompt-commit
|
|
2200
|
+
check's `Failure:` run-reference rule; the two are companion halves of one loop, built the
|
|
2201
|
+
same day). `codeVersion()` (`src/codeversion.js`) is a pure, $0, no-shell reader: `version`
|
|
2202
|
+
is bareloop's own `package.json` version (resolved off `import.meta.url`, never a hardcoded
|
|
2203
|
+
path — works whether this module is imported from `src/` or from an installed
|
|
2204
|
+
`node_modules/bareloop`); `sha` is read straight off `.git/HEAD` (following a symbolic ref
|
|
2205
|
+
to its loose ref file, falling back to `packed-refs` when the branch has been packed) **only
|
|
2206
|
+
when `.git` is a real directory at the package root** — a dev checkout has one, an
|
|
2207
|
+
npm-installed copy does not, and that absence reads as `sha: null`, honestly, never a guess.
|
|
2208
|
+
`dirty` is always `null`: whether the tree has uncommitted changes cannot be known without a
|
|
2209
|
+
shell, and this module NEVER shells out — `run` is the one locked verb (hamr's law targets
|
|
2210
|
+
what the agent can DO, not how it's written), and a report-only field is not a license for
|
|
2211
|
+
the library to grow a shell seam. `null` is reported here rather than a defaulted `false`,
|
|
2212
|
+
because a faked "clean" reading is a worse lie than an honest "don't know."
|
|
2213
|
+
|
|
2214
|
+
`job-start` (`src/run.js:303`) carries `code: {version, sha}` alongside F117's
|
|
2215
|
+
`verdictType`/`model` (`dirty` is intentionally omitted from the spine — it is always
|
|
2216
|
+
`null`, and a spine field that can only ever hold one value is not worth a byte).
|
|
2217
|
+
`replayRun` reads it as `{version, sha}` or `null` (absent field, an archive older than this
|
|
2218
|
+
landing); `formatReplay` prints a `code:` line right after `model:` — `code: bareloop
|
|
2219
|
+
0.14.0 @ 43f2812` on a spine with a real sha, `code: bareloop 0.14.0 @ sha unknown` when
|
|
2220
|
+
`sha` is null (an npm-installed run), `code: not recorded (pre-F118 spine)` when the
|
|
2221
|
+
whole field is absent. `--all` gains no new column for this — the row is already at hamr's
|
|
2222
|
+
named limit.
|
|
2223
|
+
|
|
2224
|
+
**Unknown money/time is always `null`/`unknown` — never `0`, and a partial sum is never stamped
|
|
2225
|
+
exact** (F6's rule, carried through every level this module reports at): `spentUsd`/`wallMs` are
|
|
2226
|
+
`null` when the run's own `job-end` is absent or carries no figure; `spendComplete` reads `false`
|
|
2227
|
+
whenever `spentUsd` is `null`, regardless of what the record's own flag says. The SAME rule
|
|
2228
|
+
governs one occurrence/iteration's own `spentUsd`: it sums `worker-round.costUsd`
|
|
2229
|
+
(`src/planrun.js:2288-2297`) over records strictly inside that occurrence's own `seq` window —
|
|
2230
|
+
a single `null` `costUsd` anywhere in the window makes the WHOLE occurrence's `spentUsd` `null`
|
|
2231
|
+
(`unpricedRounds` names how many), never a partial sum. Zero rounds in the window is a real `$0`,
|
|
2232
|
+
not an unknown one.
|
|
2233
|
+
|
|
2234
|
+
`stopReason` is `null` on a green/already-green outcome; otherwise `category — decision —
|
|
2235
|
+
detail` from the last `escalation` record, every part present that record actually carries
|
|
2236
|
+
(`category` dropped when it duplicates the outcome already shown beside it; `detail` trimmed at
|
|
2237
|
+
400 chars with an explicit `…[+N chars]` marker, never silent) — `decision` alone is the generic
|
|
2238
|
+
one-line prose every category shares ("the provider path failed mid-run…" fires for every
|
|
2239
|
+
transport casualty); **`detail` is what actually killed THIS run** (a TLS "bad record mac", a
|
|
2240
|
+
specific mypy error) and is the reason a stop is explained, never the decision prose alone.
|
|
2241
|
+
Falls back to the bare outcome string for a pre-escalation red (`plan-red`/`job-red`/
|
|
2242
|
+
`smoke-red`). `ending.escalation` carries that same last escalation record whenever it falls
|
|
2243
|
+
outside the fixed 3-before window (common on a run that keeps emitting bookkeeping records after
|
|
2244
|
+
its real failure) — never silently dropped. `lastEscalation` is the same raw record exposed
|
|
2245
|
+
unconditionally (not gated on the window check) — `summarizeForAllLine`'s `reason` column reads
|
|
2246
|
+
it directly rather than re-deriving from `ending`.
|
|
2247
|
+
|
|
2248
|
+
**A step id can recur** (a replan re-executes the same step): each `step-start`..`step-end` pair
|
|
2249
|
+
is its own **occurrence** (`occurrence: 1, 2, …`), and every field on it — `rounds`, `toolCalls`,
|
|
2250
|
+
`checks`, `treeChanged`, `wallMs`, `spentUsd` — is scoped to records strictly between THAT pair's
|
|
2251
|
+
own `seq` (the spine's monotonic per-record counter, never `ts` — every record in one spine
|
|
2252
|
+
shares the same seq counter, so it is an exact ordering where two same-millisecond `ts` stamps
|
|
2253
|
+
are not), never pooled across every occurrence sharing the id. `rounds` counts real
|
|
2254
|
+
`worker-round` records (`phase === 'step:<id>'`), never the declared cap; `toolCalls` is the one
|
|
2255
|
+
thing still windowed by `ts` (the gate-audit carries no `step` or shared `seq`). `tripped` is the
|
|
2256
|
+
last `escalation` whose `seq` falls inside the occurrence's window — `{category, detail}`, with
|
|
2257
|
+
`detail` falling back to `decision` when the escalation site carries no `detail` at all (a
|
|
2258
|
+
ladder-exhaustion `cap-halt`, `src/ralph.js`, is the measured example — without the fallback the
|
|
2259
|
+
tripped line would print a bare category and hide the real reason).
|
|
2260
|
+
|
|
2261
|
+
**A run whose spine carries no `step-start` at all** — the plan's only step was
|
|
2262
|
+
already-green-skipped on a resumed leg, or a genuinely loop-only shape (measured: `u-msf70nei`,
|
|
2263
|
+
a resumed leg whose one step was already proven and only the CLOSE-FIX loop ran) — has
|
|
2264
|
+
`timelineKind: 'iterations'` and `iterations[]` populated from `iteration-start`
|
|
2265
|
+
(`src/ralph.js:583`) instead: each row runs from one `iteration-start` to the next
|
|
2266
|
+
`close-verdict`/`run-end`/`escalation` (`src/ralph.js:674,577/657/676/698`), whichever comes
|
|
2267
|
+
first by `seq`. `steps`/`iterations` are never both populated — exactly one is non-empty. Each
|
|
2268
|
+
iteration's own `closeStage` (`{verdict, stages}`) is set when a `close-verdict` ended it, distinct
|
|
2269
|
+
from the run's FINAL `close` (below).
|
|
2270
|
+
|
|
2271
|
+
**`close`** is the run's real, final close — resolved by picking the LAST (by `seq`) record
|
|
2272
|
+
among every `close-verdict` that carries an ARRAY `stages` field and every `outer-close`
|
|
2273
|
+
(`src/planrun.js:3296`). A `close-verdict` with NO `stages` came from a PLAN STEP's own
|
|
2274
|
+
micro-loop, judged by the exit evaluator instead of a command (`src/planrun.js:2907`) — never
|
|
2275
|
+
"the close". Picking by `seq` rather than preferring `outer-close`'s TYPE matters: `outer-close`
|
|
2276
|
+
is the PRECHECK against the real close, fired once before the fix loop even starts, so when it
|
|
2277
|
+
reads red the fix loop's own staged close-verdicts run strictly AFTER it — measured on
|
|
2278
|
+
`u-msf70nei`, whose `outer-close` (seq 14) reads `needs_revision` but whose fix loop's own final
|
|
2279
|
+
close-verdict (seq 97, LATER) reads `satisfied`, which is what the run actually ended on
|
|
2280
|
+
(`job-end.outcome:'green'`). A run that never reached ANY close (e.g. `u-msdsmkid`, stopped by a
|
|
2281
|
+
step-variance meter before `judgeClose()` ever fired) resolves `close: null`, printed as `none —
|
|
2282
|
+
the run ended before any close ran` — never omitted.
|
|
2283
|
+
|
|
2284
|
+
`replans` prefers `plan-executed.replans` (`src/planrun.js:2688`) and falls back to counting
|
|
2285
|
+
`materials` records stamped `phase:'replan'` (`src/planrun.js:2520`) when that field is absent —
|
|
2286
|
+
an older archived spine can predate the field (measured on `u-msdsmkid`, an Aug-3 run). `branch`
|
|
2287
|
+
reads the last `work-branch.branch` (`src/planrun.js:1589`) or `null` when absent — also measured
|
|
2288
|
+
missing on the same era of archive, before the work-branch rung landed.
|
|
2289
|
+
|
|
2290
|
+
`wallMs` is THIS FILE's own `job-start`→`job-end` span, labeled `(this file)` — on a RESUMED leg
|
|
2291
|
+
that is only the latest engagement, not the whole signed chain. `chainClock` is the other true
|
|
2292
|
+
number: the SIGNED wall read off the run's own clock record — `wall-halt` when present (the
|
|
2293
|
+
TERMINAL reading, taken at the moment the wall stopped the run; `src/planrun.js:1269`'s
|
|
2294
|
+
`emitWallHalt`), else the single `wall-clock` record every run emits near its start
|
|
2295
|
+
(`src/planrun.js:1203`) — both spread from `clock.report()` (`src/clock.js:235-247`: `bounded,
|
|
2296
|
+
requestedMs, closeStages, enforcedMs, elapsedMs, remainingMs`). `elapsedMs` on either is
|
|
2297
|
+
chain-scoped by construction (`createClock`'s `priorElapsedMs` fold, `src/clock.js:174`), so it
|
|
2298
|
+
can legitimately exceed this file's own `wallMs` on a resumed run. `wall-halt`'s reading is
|
|
2299
|
+
end-of-run-honest, so `formatReplay` states it plainly: `clock: 60m04s of 60m00s signed (chain,
|
|
2300
|
+
from wall-halt)`. `wall-clock`'s reading is taken at the TOP of the engagement (chain time
|
|
2301
|
+
already inherited, not this run's own duration) — printing that under the same template read as
|
|
2302
|
+
"this run took 0 seconds" on a cold run, so it gets its own honest wording instead: `clock: 30m00s
|
|
2303
|
+
signed · 0.0s inherited from prior legs at start (from wall-clock; no end-of-run reading in this
|
|
2304
|
+
file)`.
|
|
2305
|
+
|
|
2306
|
+
**Money carries the identical split** (hamr, 2026-08-25): a RESUMED run's `job-end.spentUsd` is
|
|
2307
|
+
the CHAIN total (prior legs folded in — `src/run.js:227`'s `chainFoldUsd`, seeded from
|
|
2308
|
+
`job-start.priorSpentUsd`), which can never equal this ONE file's own rounds — measured on
|
|
2309
|
+
`u-msf70nei`: header `spentUsd` reads `$5.3389` while its own 55 `worker-round`s sum to exactly
|
|
2310
|
+
`$1.2127`. `resumed` reads `job-start.priorSpentUsd > 0` — the SAME condition `src/run.js` itself
|
|
2311
|
+
folds on — deliberately NOT `resume-seed`'s presence: `resume-seed` (`src/planrun.js:2622`) is a
|
|
2312
|
+
NARROWER record, fired only when a plan is actually reloaded mid-flight, and its absence does not
|
|
2313
|
+
mean "not resumed" — measured directly on 4 real archived runs (`litectx-maintainer-bareloop`'s
|
|
2314
|
+
`msx7xoe0`/`msx87qqs`/`msxf9129`/`msxf2mwi`) that carry a real `priorSpentUsd` fold with NO
|
|
2315
|
+
resume-seed record at all. `resumeSeed` (when present) supplies the DETAIL —
|
|
2316
|
+
`{phase, completed, skipping, divergence}` — printed as `resumed: yes — phase close, 1 of 1
|
|
2317
|
+
completed step(s) skipped`; a resumed leg with no resume-seed record still reads `resumed: yes
|
|
2318
|
+
(no resume-seed detail recorded)`, honestly, rather than falling back to `no`. `thisFileSpend`
|
|
2319
|
+
prefers `job-end.engagementSpentUsd` (F103, `src/run.js:284` — this engagement's own fold-out
|
|
2320
|
+
spend) when the record carries it, falling back to summing this file's own `worker-round.costUsd`
|
|
2321
|
+
when it does not (measured absent on `u-msf70nei` itself); an unpriced round anywhere in THIS
|
|
2322
|
+
FILE makes the figure `null` regardless of source (F6, never a partial sum stamped exact) — both
|
|
2323
|
+
figures print on the `spent:` line, each labeled with its source, never one silently standing in
|
|
2324
|
+
for the other. `spendMismatch` is the same-class check for a NON-resumed run: there is no fold to
|
|
2325
|
+
explain a gap, so `job-end.spentUsd` disagreeing with this file's own summed rounds by more than
|
|
2326
|
+
$0.0001 is a genuine finding, printed as `MISMATCH vs job-end`, never hidden — measured across
|
|
2327
|
+
every non-resumed run in the available archive (aurora-u, bareagent-u, litectx-maintainer): zero
|
|
2328
|
+
trip it. `--all`'s spend column stays the chain total (what a directory scan asks first) but
|
|
2329
|
+
marks a resumed row with a trailing `*` on its id, with one footnote line
|
|
2330
|
+
(`* resumed run — spend is the chain total, see detail`) printed once, only when the listing
|
|
2331
|
+
holds at least one such row.
|
|
2332
|
+
|
|
2333
|
+
`formatReplay` renders the full page: a header block (`goal:`/`shape:`/`signed:`/`branch:`/
|
|
2334
|
+
`resumed:`/`outcome:`/`spent:`, every absent field printing the literal `none recorded` — never
|
|
2335
|
+
blank, never a fabricated `0`), a `TIMELINE (steps)` or `TIMELINE (iterations — loop-shape run)` section with
|
|
2336
|
+
its own column-header row (`# step result rounds tools checks tree wall spend`, or the
|
|
2337
|
+
iteration equivalent without `checks`/`tree`) and a `↳ tripped:`/`↳ close:` line under any row an
|
|
2338
|
+
escalation or close fell inside, a `CLOSE` section (never omitted — `none — the run ended before
|
|
2339
|
+
any close ran` when nothing ever closed; the failing stage, if any, prints FIRST with `✗`, the
|
|
2340
|
+
rest `name OK`), `ENDING` (last record + 3 before it, plus an out-of-window escalation), the
|
|
2341
|
+
`BEHAVIOUR` block, and a `MEMORY-CACHE` line **always printed** — the existing wording when the
|
|
2342
|
+
run carried a record, `not armed on this run` otherwise. An absent record is never silent.
|
|
2343
|
+
`RESULT` words are `GREEN`/`RED`/`UNKNOWN` — `satisfied`/`green`/`already-green` read GREEN,
|
|
2344
|
+
every other non-null value reads RED.
|
|
2345
|
+
|
|
2346
|
+
`summarizeForAllLine(summary)` builds one `--all` row's raw fields (`id, job, shape, outcome,
|
|
2347
|
+
spend, wall, steps, reason`) — `shape` is `plan`/`loop` (from `timelineKind`, never the
|
|
2348
|
+
always-literal-`'plan'` `job-start.shape` field); `steps` is a bare count for a plan-shape run,
|
|
2349
|
+
`"N it"` for a loop-shape one; `reason` is `category — <first clause of detail, cut at 60 chars,
|
|
2350
|
+
plain "…", no [+N] marker>` off `lastEscalation` — **`detail` only, never `decision`** for this
|
|
2351
|
+
column specifically (unlike the full report's `↳ tripped:` line, which does fall back to
|
|
2352
|
+
`decision` — this compact row has no room for both and the coordinator's own ruling keeps
|
|
2353
|
+
`decision`'s generic per-category prose out of it); `-` on a green row; the distinct, literal
|
|
2354
|
+
`no job-end (killed mid-run)` when the spine never reached a `job-end` at all (`outcome` itself
|
|
2355
|
+
is `null`) — not folded into the ordinary escalation case. `formatAllLines(entries)` renders every
|
|
2356
|
+
row as a FIXED-COLUMN, ALIGNED table with a header row (never CSV — columns are padded, not
|
|
2357
|
+
delimited; the header's own label lengths count toward the column widths) plus any
|
|
2358
|
+
`<name> not-a-spine` lines at their original position (unpadded — a filename carries no
|
|
2359
|
+
outcome/spend/wall of its own). No header prints when there are zero real spines in the listing.
|
|
2360
|
+
|
|
2361
|
+
`scripts/run-replay.mjs <spine.jsonl>` resolves the sibling `-gate-audit.jsonl` automatically and
|
|
2362
|
+
prints the full report; `--all <dir>` is **name-agnostic** — the patient corpus does not agree on
|
|
2363
|
+
one filename convention (`u-<id>.jsonl`, `battery-A1-<id>.jsonl`, `l2accept-L1-<id>.jsonl`,
|
|
2364
|
+
`reuse-<id>.jsonl`, `types-screen-C-<id>.jsonl`, …), so a spine is identified by CONTENT: any
|
|
2365
|
+
`.jsonl` that is not a sidecar by name (`*-gate-audit.jsonl`, `*.lag.jsonl`) and whose records
|
|
2366
|
+
include a `job-start` (present on every real spine checked, nested wrappers included) or
|
|
2367
|
+
`run-start` (present on most, absent on a run that died before ralph's first iteration). A
|
|
2368
|
+
`.jsonl` matching neither is printed as `<name> not-a-spine` — never silently dropped or
|
|
2369
|
+
misread as an empty run. Both modes mirror `scripts/behaviour-readout.mjs`'s own malformed-line
|
|
2370
|
+
handling (skip-and-count, never throw).
|
|
2371
|
+
|
|
2372
|
+
**F117's PRD TODO #20 is now landed in full**: both the verdict CLASS and the worker MODEL
|
|
2373
|
+
name are surfaced (see `verdictType`/`model` above) — hamr blessed the item verbatim
|
|
2374
|
+
("1") on 2026-08-25, so the spine-WRITER change to `job-start` (`src/run.js`) that a
|
|
2375
|
+
report-only reader would not otherwise make on its own authority went in with his explicit
|
|
2376
|
+
word. `judgedCount` on a close-verdict was never a reliable stand-in for the class either way
|
|
2377
|
+
(it appears on plain green closes too, not only softgreen ones) — this reads the real
|
|
2378
|
+
declared field instead.
|
|
2379
|
+
|
|
2380
|
+
**Transport retries and floor reasons (F119, live-proven 2026-08-25)**: every
|
|
2381
|
+
`transport-retry` record (`src/planrun.js:126`) whose `seq` falls inside a step's or
|
|
2382
|
+
iteration's own window prints a `↳ transport retry ×N (recovered|not recovered|partially
|
|
2383
|
+
recovered) — <first 80 chars of the error>` line under that row — the same open-interval
|
|
2384
|
+
`seq` windowing `windowSpend` already uses for rounds. A retry whose `seq` falls outside
|
|
2385
|
+
every occurrence window (e.g. during the scout/plan phase) is counted instead under the
|
|
2386
|
+
`TIMELINE` header as `transport retries outside steps: N`, printed only when that count is
|
|
2387
|
+
non-zero. The header's `spent:` line, whenever `spendComplete` is `false`, appends
|
|
2388
|
+
`· floor because: <reason(s)>` — derived from the spine, never guessed: `transport retry
|
|
2389
|
+
×N` (any `transport-retry` record), `unpriced round(s)` (a `worker-round`/`judge-round`
|
|
2390
|
+
with non-finite `costUsd`), `cut mid-call` (a `wall-halt` record with `cutMidCall:true`),
|
|
2391
|
+
`stall` (a `stall` record), `prior leg floor` (`job-start.priorSpendComplete === false`) —
|
|
2392
|
+
every cause that actually shows evidence prints, never just one; a floor with no derivable
|
|
2393
|
+
evidence on this spine (an older archive, or a resumed leg's floor inherited from a file not
|
|
2394
|
+
in hand) prints `· floor (reason not in spine)` instead. `--all` adds no new column: a run
|
|
2395
|
+
that carried any transport retry gets ` ⟲N` appended directly after its outcome word (same
|
|
2396
|
+
posture as the resumed `*` on `id`), with one footnote
|
|
2397
|
+
(`⟲N transport retry(ies), recovered inline — see the full replay for detail`) printed once
|
|
2398
|
+
when the listing holds at least one such row. Live-proven on `u-mt8yk53k` (F119): two
|
|
2399
|
+
recovered TLS `bad record mac` retries, both inside step `fix-mypy-strict`'s window —
|
|
2400
|
+
`floor because: transport retry ×2` and `green ⟲2` in `--all`, on a run that finished green.
|
|
2401
|
+
|
|
2402
|
+
```js
|
|
2403
|
+
import { replayRun, formatReplay } from 'bareloop';
|
|
2404
|
+
const spine = fs.readFileSync(spineFile, 'utf8').trim().split('\n').map(JSON.parse);
|
|
2405
|
+
const audit = fs.readFileSync(auditFile, 'utf8').trim().split('\n').map(JSON.parse);
|
|
2406
|
+
console.log(formatReplay(replayRun(spine, audit, { runId: 'mszcthk1' })));
|
|
2407
|
+
```
|
|
2408
|
+
|
|
2409
|
+
### `PROMPT_REGISTERS` / `isPromptFile(path)` — `src/promptregisters.js`, and the prompt-commit rule
|
|
2410
|
+
|
|
2411
|
+
PRD build-list item 5 (TODO #8), Q9 answered (hamr, 2026-08-25): "a check". A commit that
|
|
2412
|
+
changes a **prompt register** — one of the model-facing system/strategy/instruction
|
|
2413
|
+
strings a worker or judge actually reads (`SCOUT_SYSTEM`, `AUTHOR_SYSTEM`,
|
|
2414
|
+
`PERSONA_TOOLS`, `READ_SHIM_STRATEGY`, and their neighbors — the full, verified list is
|
|
2415
|
+
`PROMPT_REGISTERS` in `src/promptregisters.js`) — must say, in its own message, three
|
|
2416
|
+
things: what failure caused the change, what it addresses, and what it corrects.
|
|
2417
|
+
Convention-only was rejected: doctrine already on record is "a frozen rule without a
|
|
2418
|
+
wired detector is prose." `isPromptFile(path)` is the pure predicate the check runs
|
|
2419
|
+
against; both are exported from `bareloop`'s root so the inventory has exactly one home.
|
|
2420
|
+
|
|
2421
|
+
hamr's addition (2026-08-25, same day): the `Failure:` line must ALSO cite the **run**
|
|
2422
|
+
that caused the change — `run <id>` or `run u-<id>` (case-insensitive at the `run`
|
|
2423
|
+
keyword, multiple refs fine), matched by `\brun\s+(u-)?[a-z0-9]{8}\b` (tightened from an
|
|
2424
|
+
initial `{6,12}` margin after a PR #23 review found the margin loose enough to accept
|
|
2425
|
+
plain prose — "run failed" is 6 lowercase letters, satisfying `{6,12}` outright — to the
|
|
2426
|
+
measured exact shape: every real archived id is exactly 8 characters. A per-id digit
|
|
2427
|
+
requirement was proposed in that same review and rejected: 23 of 130 re-sampled real ids
|
|
2428
|
+
carry no digit at all, `msdsmkid` among them — a real archived run cited by name
|
|
2429
|
+
elsewhere in this repo — so requiring a digit would reject genuine citations). The commit
|
|
2430
|
+
points at the run; `replayRun`/`formatReplay` (above) close the loop the other way,
|
|
2431
|
+
turning that run id back into the whole story. A `Failure:` line with no run reference
|
|
2432
|
+
fails with a distinct message: `Failure: must cite the run that caused the change (e.g.
|
|
2433
|
+
"Failure: run mszcthk1 — ...")`. FORMAT ONLY — the check never verifies the cited run's
|
|
2434
|
+
spine file actually exists (a run's patient lives outside this repo entirely, so there is
|
|
2435
|
+
nothing here to check it against).
|
|
2436
|
+
|
|
2437
|
+
Enforcement is **local only** — wired into `npm test` (see `package.json`'s `test`
|
|
2438
|
+
script), never into `.github/workflows/*` (CI already runs `npm test`, so the rule is
|
|
2439
|
+
enforced there without an ask-first CI edit). The check itself lives outside the
|
|
2440
|
+
published package, at `scripts/prompt-commit-check.mjs` (pure decision logic in
|
|
2441
|
+
`scripts/promptcommitlib.mjs`, `scripts/` is not in `package.json`'s `files`, so none of
|
|
2442
|
+
this ships):
|
|
2443
|
+
|
|
2444
|
+
```
|
|
2445
|
+
node scripts/prompt-commit-check.mjs --range origin/main..HEAD # validates every commit
|
|
2446
|
+
# in the range that
|
|
2447
|
+
# touches a prompt file
|
|
2448
|
+
node scripts/prompt-commit-check.mjs --message-file .git/COMMIT_EDITMSG
|
|
2449
|
+
# commit-msg hook mode:
|
|
2450
|
+
# validates one message
|
|
2451
|
+
# against staged files
|
|
2452
|
+
```
|
|
2453
|
+
|
|
2454
|
+
A compliant message:
|
|
2455
|
+
|
|
2456
|
+
```
|
|
2457
|
+
fix: tighten PERSONA_TOOLS
|
|
2458
|
+
|
|
2459
|
+
Failure: run mszcthk1 — worker read the arbiter spine after being told it was denied
|
|
2460
|
+
Addresses: PERSONA_TOOLS did not name the spine file explicitly
|
|
2461
|
+
Corrects: spells the denied paths from ARBITER_BOOK_STORES
|
|
2462
|
+
```
|
|
2463
|
+
|
|
2464
|
+
A commit that touches no prompt-register file is passed without inspecting its message.
|
|
2465
|
+
An unresolvable `--range` (no such ref — a fresh clone with no `origin`, a detached
|
|
2466
|
+
checkout that never fetched) is a **skip**, printed and exited 0: a missing baseline is
|
|
2467
|
+
not a rule violation, never a red.
|
|
2468
|
+
|
|
2469
|
+
|
|
2145
2470
|
## Architecture
|
|
2146
2471
|
|
|
2147
2472
|
Three layers. An **outer shell** (dumb, permanent): per-run budget cap via bareguard,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"node": ">=20"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "node --test",
|
|
20
|
+
"test": "node --test && node scripts/prompt-commit-check.mjs --range origin/main..HEAD",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"build:types": "tsc",
|
|
23
23
|
"prepublishOnly": "npm run build:types"
|
package/src/authorjob.js
CHANGED
|
@@ -290,8 +290,8 @@ export function runInterview({ answers, verdictType = null, repoPath = null, que
|
|
|
290
290
|
verb: 'non-green-verdict',
|
|
291
291
|
path: 'repoPath',
|
|
292
292
|
detail: 'This job has no code repository, so there is no seed to measure against and no changed set to read — '
|
|
293
|
-
+ 'nothing deterministic can decide whether it came back done. That needs a judged or a
|
|
294
|
-
+ '
|
|
293
|
+
+ 'nothing deterministic can decide whether it came back done. That needs a judged (soft-green) or a '
|
|
294
|
+
+ 'human close; this authoring flow drafts code-genre closes against a git seed only.',
|
|
295
295
|
}),
|
|
296
296
|
});
|
|
297
297
|
}
|