bareloop 0.26.0 → 0.28.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
@@ -5,6 +5,277 @@ All notable changes to bareloop are documented here. Format:
5
5
  [SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
6
6
  feature lands, **patch** = docs, fixes, scaffolding.
7
7
 
8
+ ## [0.28.0] — 2026-09-24
9
+
10
+ ### Added
11
+
12
+ - **`bareloop run-u`, `bareloop interview`, `bareloop author`, and `bareloop replay` (+
13
+ `--all`)** — four new CLI commands giving the panel's (N6) planned History/Run/Audit
14
+ surfaces, and any other adopter, a library-backed door onto machinery that previously
15
+ existed only as standalone scripts (`scripts/run-u.mjs`, `scripts/run-interview.mjs`,
16
+ `scripts/run-author.mjs`, `scripts/run-replay.mjs`). Each new command dispatches argv
17
+ straight to a library module's own `main(argv, deps)` — one owner per flag grammar — so
18
+ the CLI and the standalone script converge on the exact same code path. `bareloop replay
19
+ --all <dir>` lists every spine under a directory.
20
+
21
+ ### Changed
22
+
23
+ - **`scripts/run-u.mjs`, `scripts/run-interview.mjs`, `scripts/run-author.mjs`, and
24
+ `scripts/run-replay.mjs` lifted into `src/`** (`src/userrun.js`, `src/interviewrun.js`,
25
+ `src/authorrun.js`, `src/replayio.js`, plus `src/authorreadout.js`, `src/u-patient.js`,
26
+ `src/u-readout.js` moved outright to sit under `tsc`'s `rootDir`). Each script is now a
27
+ thin (~15–20 line) adapter over its library module, the same shape `bin/bareloop.mjs`
28
+ already is over `src/cli.js`. Behaviour-preserving: every `console.log`/`console.error`
29
+ became an injectable `out`/`err`, every `process.exit(n)` became a thrown `ExitSignal(n)`
30
+ caught at the function's own boundary (a library function never calls `process.exit`
31
+ itself), and `process.env`/`process.argv` reads became injectable parameters. Moving this
32
+ code under `rootDir` exposed it to `tsc` for the first time, surfacing and fixing ~26 real
33
+ `strictNullChecks` errors (no `!`, `as any`, or `@ts-ignore`), including one genuine
34
+ type-annotation bug (`humanRuling.text` declared `string?` when the real value flows
35
+ `string|null`).
36
+ - **`src/cli.js`'s bare interactive menu** now lists `run-u`, `interview`, `author`, and
37
+ `replay` alongside the existing commands (previously unlisted even though they already
38
+ existed on the command line), and usage/error text in `src/interviewrun.js`/
39
+ `src/authorrun.js` now names the command actually typed (`bareloop interview`/`bareloop
40
+ author`) instead of a hardcoded script path, when invoked that way.
41
+
42
+ ### Fixed
43
+
44
+ - **`doRun`'s job-end spine read could crash an otherwise-successful run's tail.** A
45
+ hand-rolled `readFileSync(...).split('\n').map(JSON.parse)` outside any try/catch threw
46
+ uncaught on a spine with a malformed/truncated final line (e.g. a process killed
47
+ mid-append). Retargeted onto the existing tolerant `parseJsonl` (now in
48
+ `src/replayio.js`), which skips a bad line instead of throwing.
49
+ - **F193/F194 — the prompt-commit-check's prose-only exemption.** A commit touching a
50
+ registered prompt-register file normally needs Failure/Addresses/Corrects labels plus a
51
+ run citation; a narrow, fail-closed exemption now applies when every changed line in
52
+ every touched prompt-register file is provably prose-only (whitespace or a comment),
53
+ decided by scanning real old/new diff text rather than the commit message. F194 closed a
54
+ follow-up hole where a blank line inserted or deleted inside a still-open template
55
+ literal was misclassified prose-only and wrongly exempted.
56
+
57
+ ## [0.27.0] — 2026-09-21
58
+
59
+ ### Changed
60
+
61
+ - **`bare-agent` bumped `^0.42.0` → `^0.43.0`.** Ships BA-27 (bareloop's own corroborating
62
+ upstream ask, `docs/product/UPSTREAM-ASKS.md`): `OpenAIProvider`/`OllamaProvider.generate` no
63
+ longer throw a raw `SyntaxError` when a model's tool-call `arguments` string is malformed JSON —
64
+ the shared `parseToolCalls` helper returns the round priced (real `usage`) with `toolCalls: []`
65
+ plus its own `malformedToolCall: {name, error}` marker instead, and `Loop.run` surfaces that
66
+ marker unchanged on its return. No other package changed (`package-lock.json` diff is
67
+ `bare-agent` only).
68
+
69
+ ### Removed
70
+
71
+ - **`withMalformedToolCallShim`, bareloop's local F179 stopgap (`src/authorflow.js`, added
72
+ 2026-09-15 commit `96866d5`).** Deleted now that bare-agent 0.43.0 fixes BA-27 upstream —
73
+ `makeLoopGenerate` calls `loop.run()` directly again, no delegate provider. `askStructured`'s
74
+ `r?.malformedToolCall` check (`e9e2839`) is unchanged and now reads bare-agent's own field.
75
+ - **`callCasualty`'s SyntaxError admission (`src/text.js`)**, dead code once no provider bareloop
76
+ constructs (`AnthropicProvider`, `OpenAIProvider`, `GeminiProvider` — `src/providers.js`) can
77
+ still throw a JSON `SyntaxError` out of `generate()` after a billed round: verified against
78
+ 0.43.0's installed source that Anthropic and Gemini read tool-call arguments as already-parsed
79
+ objects (nothing to `JSON.parse` in their response paths), and every provider's raw-HTTP-body
80
+ parse is already try/catch-wrapped inside `_request`. The idle-timeout (ETIMEDOUT/TimeoutError)
81
+ admission is unchanged.
82
+
83
+ ### Fixed
84
+
85
+ - **The publish workflow now fails when `package-lock.json`'s version drifts from `package.json`.** npm writes that field on install, so a release that bumps `package.json` without running one leaves it behind — and nothing caught it: `npm ci` fails when the lockfile's *dependency* entries disagree, but never checks the lockfile's copy of the project's own version. `scripts/check-lockfile.mjs` (`npm run check:lockfile`) compares both places npm writes it and runs in the publish workflow. No lockfile is not a failure.
86
+
87
+ - **F191 — a plain-folder source's confirm turn crashed live (run `mu4hc7sp`), leaving a
88
+ two-record spine (`author-start`, `author-phase confirm`) with no ending at all.**
89
+ `confirmProtections` computes protections via `classGuards` (`src/authoring.js`), which is
90
+ keyed by CODE LANGUAGE and throws on a plain folder's `lang: 'none-detected'` — a plain
91
+ folder has no language, so a confirm turn cannot honestly show real, code-derived protections
92
+ for one. `scripts/run-author.mjs` no longer runs a confirm turn over a plain folder at all:
93
+ the honest "no checks yet" stop now fires immediately after `author-start`, at $0 — no scout,
94
+ no confirm turn, no model call. The now-unreachable confirm-turn branch (~70 lines) is
95
+ deleted. Alongside it, the try/catch crash net (which used to start ~300 lines after
96
+ `author-start`, leaving exactly the gap this crash fell into) now opens immediately after the
97
+ spine exists, and its crash message reads the run's own metered-call list rather than
98
+ unconditionally claiming "died inside the paid span". Customer-facing text in
99
+ `scripts/run-interview.mjs`/`bareloop.context.md` that promised a plain-folder confirm turn
100
+ is corrected to match. Fail-first proven in tests (each of the three code changes shown red
101
+ against the prior commit, green after); a new live end-to-end test proves zero provider calls.
102
+ Three follow-up fixes landed in the same pass: the stop's own text claimed "Nothing was spent
103
+ and nothing was written," while the same branch writes `authored.json` into `--out` — both this
104
+ message and `scripts/run-interview.mjs`'s matching install-gap line now read "Nothing was spent.
105
+ Your source was not changed." (true on both counts: no provider call happens on this path, and
106
+ the source tree itself is never touched); an F71 regression (`process.exit(1)` right after a
107
+ `console.log`, which can discard already-queued stdout under a slow reader) was fixed back to
108
+ `process.exitCode = 1`; and a duplicate `spine <path>` print this exitCode fix's fall-through
109
+ briefly introduced was removed (the shared tail print already covers this exit path). Not yet
110
+ re-run against the original crashing patient (`docs/logs/FINDINGS.md` F191).
111
+ - **F190 — fixed properly this pass.** The prior fix (commit 56bbee8) hand-copied
112
+ `scripts/run-u.mjs`'s same-provider `baseUrl` conditional a second time. Both the author and
113
+ judge provider constructions in `scripts/run-author.mjs` now go through `buildRunnerProviders`
114
+ (`src/providers.js`), the one seam `src/cli.js` already used — this file was the one call
115
+ site that had not adopted it. Not yet re-proven live (`docs/logs/FINDINGS.md` F190).
116
+ - **F185 — an interview-authored repo job had no code path INTO `scripts/run-u.mjs`: a person
117
+ finishing the authoring interview was left holding a signed, hash-stable `resolved-spec.json`
118
+ with no way to run it, only a developer hand-step (`cp` the spec into `jobs/` + hand-add a
119
+ `JOBS` row).** hamr's ruling (2026-09-16, option A): `scripts/run-u.mjs` now accepts `--spec
120
+ <path to resolved-spec.json>` as an alternative to `--job <key>` (giving both, or neither, is a
121
+ loud $0 refusal). It `validateJob`'s the spec explicitly and early, then reads the workdir and
122
+ seed off the prepared copy's OWN source manifest (`readSourceManifest`, `src/source.js` — the
123
+ same reader `run-author.mjs` already uses) — the one `source-*/` sibling directory beside the
124
+ spec that carries a `source.json`; never typed, guessed, or defaulted, and a missing/ambiguous
125
+ manifest or a gone tree each refuse by name. `scripts/run-author.mjs`'s "SIGNING PREPARED — NOT
126
+ SIGNED" end screen now prints the exact ready-to-paste run command (real spec path, real
127
+ `--approve <hash>`, the provider's real env-key name per F187). Every existing arbiter gate —
128
+ the `--approve` signature, the budget/wall ceilings, coldReset, the close-first precheck —
129
+ applies unchanged; `--spec` only NAMES the job. Not yet proven live (`docs/logs/FINDINGS.md`
130
+ F185).
131
+ - **F183 — the confirm turn's model-facing prompt never showed the model the real, code-derived
132
+ guards or write fence, so `notChecked` could (and twice live did, runs `mu2bjmed` and
133
+ `mu2qmept`) falsely claim an already-enforced protection was missing** (e.g. "no ts-ignore
134
+ comments" and "do not edit or delete tests", while `no-suppressions` and the write fence
135
+ already covered both). hamr's ruling A (2026-09-16): feed the real protections in as facts,
136
+ never drop the model's list. `confirmProtections`'s existing code-derived list (the same one
137
+ already shown to the person, `src/authorflow.js`) is now computed once, before the model
138
+ drafts, and threaded into `confirmPrompt`'s model-facing prompt as an "ALREADY COVERED" block;
139
+ `CONFIRM_SYSTEM` now orders the model to check that list before naming anything in
140
+ `notChecked`. The model still authors `notChecked` freely — nothing made deterministic, no
141
+ second hand-typed guard list. Not yet proven live (`docs/logs/FINDINGS.md` F183).
142
+ - **Item 6 — `src/authoring.js`'s `TYPES_GENRE_TEMPLATE` (model-facing prompt text,
143
+ interpolated verbatim into the authoring prompt) was missing from `src/
144
+ promptregisters.js`'s `PROMPT_REGISTERS` inventory entirely.** Added; no other prompt content
145
+ found in that file. `docs/product/PRD.md`'s item 33 close-out box ticked.
146
+ - **F182 — the interview never waited for the install, so "Run it now?" was unreachable for a
147
+ repo that needs packages.** `scripts/run-interview.mjs` printed the install-gap message right
148
+ after `prepareSource` and fell straight through to the class questions, only re-checking
149
+ `missingDependencies` once, at hand-off — too late for the offer to ever fire. It now pauses
150
+ right there and loops on the same check (`"Press Enter once it has finished to check again, or
151
+ type skip to carry on without it: "`), printing `"packages found — carrying on."` on a
152
+ resolved recheck; `skip` or end of input carries on unresolved, same as before. Also drops the
153
+ misleading "then rerun this command" wording, which pointed at restarting the whole interview
154
+ when only the install is needed. Not yet proven live.
155
+ - **F181 — a key with an embedded newline (or other control character, or stray whitespace)
156
+ read as "set" by every presence-only key check and then crashed Node's own header-encode
157
+ (`ERR_INVALID_CHAR`) inside the paid span, rather than refusing at $0.** One exported pure
158
+ helper, `apiKeyProblem(value)` (`src/providers.js`, beside the provider table), reports a
159
+ plain-English reason (or `null`) for a value carrying CR, LF, TAB, another C0/DEL control
160
+ character, or leading/trailing whitespace — it never trims or repairs. Wired at every door
161
+ that already presence-checks a key: `scripts/run-author.mjs` (worker and judge keys),
162
+ `scripts/run-u.mjs` (worker and judge keys), `src/cli.js`'s `bareloop run`
163
+ (`ANTHROPIC_API_KEY`), and `scripts/run-interview.mjs`'s `KEYED` offer gate (a malformed key
164
+ no longer counts as keyed). Every refusal names the env var and the reason class, never the
165
+ value. Not yet proven live.
166
+ - **F187 — `scripts/run-u.mjs`'s printed invocation hints (the "approve and run" line and the
167
+ sleep-inhibitor wrapper line, both built through the shared `invoke()` helper) always named
168
+ `ANTHROPIC_API_KEY`, regardless of the job's actual provider.** A job on `openai-api`
169
+ (DeepSeek) printed the wrong variable name to set. `invoke()` now reads `providerEntry.envKey`
170
+ — the same resolved name the real key check at launch already uses. Not yet proven live.
171
+ - **F186 — the printed BEHAVIOUR line (and any replay) counted tool calls from EVERY run that
172
+ ever touched the patient tree, not just the run being read, because the authoring scout's gate
173
+ audit is written directly into the patient tree and the tree's `.gitignore` denies `*.jsonl`,
174
+ so a cold reset never removes it.** Fixed at the source, never by filtering `runBehaviour` on
175
+ `run_id` (a single run legitimately spans several). `scripts/run-author.mjs` now archives its
176
+ own gate audit out of the patient tree the moment authoring ends (`archiveGateAudit()`, called
177
+ from `finally` and once more before the one `process.exit()` path that skips it).
178
+ `scripts/run-u.mjs` now moves any stale audit aside right after `coldReset`, before its own
179
+ Gate ever opens (`moveStaleGateAudit`, new export from `scripts/u-patient.mjs`) — never on the
180
+ resume branch, where the tree's audit is that same run's own prior leg. Not yet proven live.
181
+ - **F184 — a malformed tool-call round on the WORKER path (`src/planrun.js`'s Loop-path `ask()`)
182
+ ended an attempt with nothing on the spine naming it — indistinguishable from the model
183
+ genuinely producing no useful work.** `ask()` now reads bare-agent 0.43.0's own
184
+ `r.malformedToolCall` marker (present since BA-27) and emits a distinct `worker-malformed-tool-
185
+ call` record (`{phase, iteration, name, error}`, scrubbed and length-capped) — visibility only:
186
+ no retry, no change to strikes/ladder/attempt counting/verdict routing, all arbiter-adjacent and
187
+ out of scope. Not yet proven live.
188
+ - **F179 — a malformed tool-call JSON on the FIRST authoring call had no earlier sound
189
+ declaration to fall back to, so 318e066's provider-red stopped the run outright.** hamr's
190
+ 2026-09-15 ruling: retry, never repair. `withMalformedToolCallShim` (`src/authorflow.js`,
191
+ wired into `makeLoopGenerate`) is a delegate provider (`Object.create`, never a mutation of
192
+ the shared instance) whose `_request` strips a malformed tool call out of the raw response
193
+ BEFORE `generate()`'s own `JSON.parse` can throw on it, so the round comes back PRICED on
194
+ bare-agent's own real usage instead of crashing. `askStructured` reads the resulting round
195
+ through its EXISTING malformed-emission retry ladder (`MAX_STRUCTURE_RETRIES`, no new cap),
196
+ tagged with a new `malformed-tool-call-arguments` axis — checked BEFORE the accept path and
197
+ regardless of how many calls survived the strip, so a reply carrying a malformed call
198
+ alongside a valid one is never accepted either (follow-up fix, same day, review-caught).
199
+ Scoped to `makeLoopGenerate` only — the authoring declaration calls and the confirm turn; the
200
+ scout and the worker path build their own provider wiring and still only get 318e066's stop
201
+ (an open gap). Not yet proven live.
202
+ - **F179/F180 — bare-agent 0.42.0's OpenAIProvider could throw a raw SyntaxError mid-call (a
203
+ malformed tool-call `arguments` string, `JSON.parse` with no try/catch), crashing past
204
+ `askStructured` and discarding an already-sound, already-measured declaration while the
205
+ billed call went unbooked.** `askStructured` (`src/authorflow.js`) now wraps its `generate`
206
+ call in try/catch, tested against a new shared predicate, `callCasualty` (`src/text.js`,
207
+ beside `priceOf`): a `HaltError` still re-raises (a governance exit, never laundered); an
208
+ ETIMEDOUT/TimeoutError-shaped throw or a JSON-mentioning SyntaxError lands as the same typed
209
+ `providerError` the resolved-`{error}` path already produced, booked at `costUsd: null`
210
+ (honest-unknown, never a complete-looking floor); anything else still crashes.
211
+ `authorscout.js`'s `settled` seam, which already hand-caught its own narrower idle-timeout
212
+ class, now delegates to the same predicate. No JSON repair, no new retry. Not yet proven live
213
+ (run `mu2bjmed` is the live crash this fixes; the corroborating upstream ask is BA-27 in
214
+ `docs/product/UPSTREAM-ASKS.md`).
215
+ - **F178/F177 — a signed fence could name `.git` or `node_modules` directly (or reach a nested
216
+ `node_modules` through an otherwise-legal fence), letting writes there go invisibly to
217
+ `changedSet`.** `scopeContained` (`src/validate.js`, the one containment law `validateJob`
218
+ and `validatePlan` both go through) now refuses any fence/scope whose normalized prefix has a
219
+ whole path segment equal to `.git` or `node_modules` (hamr's ruling, 2026-09-14, option B) —
220
+ a look-alike like `.github`/`my.git`/`node_modules_util` stays admitted. The runtime belt for
221
+ a NESTED `node_modules` a legal fence can still reach uses `tools.denyArgPatterns`
222
+ (`FORBIDDEN_WRITE_SEGMENT_PATTERN`, `src/planrun.js`), scoped to `write`/`edit` only —
223
+ bareguard's `fs.deny` was tried first for `.git` and reverted: it blocks ALL fs actions,
224
+ reads included, which silently over-blocked (caught by the full gate's own drift test,
225
+ `tests/tools.test.js`); `fs.deny` is never used for either book now. Not yet proven live.
226
+ - **F177 — a repo source whose tracked `.gitignore` did not mention `node_modules` let
227
+ installed packages read as the worker's own writes.** `changedSet` (`src/kinds.js`) unions
228
+ the tracked diff with `git ls-files --others --exclude-standard`, so an unignored
229
+ `node_modules` installed into the copy (live-verified: 247 files on a real copy of pulselog)
230
+ read as changes the worker made. `prepareSource` now hides `node_modules` in the copy's own
231
+ PRIVATE `.git/info/exclude` (hamr's ruling, 2026-09-14, option A) — the source repo's tracked
232
+ files, `.gitignore` included, are never touched. Not yet proven live.
233
+ - **PRD item 33 close-out — `prepareSource` freezes tracked files only, so a JS/TS repo's
234
+ prepared copy never carried `node_modules`, and every close stage needing a tool (`tsc`, a
235
+ test runner) instrument-stopped with nothing telling the person why.** bareloop never runs an
236
+ install itself (hamr's ruling, 2026-09-14, option A): `missingDependencies` (`src/source.js`,
237
+ exported) is a pure, $0, JS/TS-only detector that names the exact install command.
238
+ `scripts/run-interview.mjs` prints it right after `prepareSource` succeeds and again at the
239
+ hand-off, where a still-open gap suppresses the "Run it now?" offer entirely.
240
+ `scripts/run-author.mjs` refuses `source-deps-missing` at $0, before the scout and before the
241
+ provider/key are even read. Not yet proven live end to end (the detector and the refusal
242
+ paths are each proven directly; a real `npm ci` against a real prepared copy of a real repo
243
+ confirmed the hidden git's seed stays clean when the source's `.gitignore` ignores
244
+ `node_modules` — see the build report for the gap this does NOT cover, a repo whose
245
+ `.gitignore` does not ignore `node_modules`).
246
+ - **PRD item 33 close-out (L17) — the authoring interview never admitted an endpoint, so a
247
+ DeepSeek draft (`--provider openai-api`) authored against api.openai.com instead.**
248
+ `scripts/run-interview.mjs` now takes an optional `--base-url <url>` flag (no default; absent
249
+ leaves the draft's `baseUrl` field out entirely, never `null`/`''`) and validates it through
250
+ the existing `validateJob` `baseUrl` rule at $0 before writing anything. The interview's
251
+ header, the hand-off command, and `scripts/run-author.mjs`'s own start banner/`author-start`
252
+ event now name the endpoint when one is set; the key hint still points at the provider's own
253
+ env var (`OPENAI_API_KEY` for a DeepSeek endpoint) since there is no separate
254
+ endpoint-specific key. Not yet proven live.
255
+ - **F176 — the revise ladder kept a later revision even when it measured worse than an earlier
256
+ one.** `authorClose` (`src/authorflow.js`) now tracks every MEASURED (validated + seed-read)
257
+ iteration and falls back to the NEWEST SOUND one — a real red/green verdict, never an
258
+ instrument-stop — when the last measured iteration is unsound and an earlier one is not.
259
+ `stop` is never changed; the swap is reported via a new `fellBack` return field, an
260
+ `onPhase('author-fallback', …)` event, and the terminal signing readout. Not yet proven live.
261
+ - **F175 — the confirm turn's own honestly-raised `questions` never reached
262
+ `accepted.openQuestions`.** `runConfirmTurn` now carries the model's own `questions` from the
263
+ plan being accepted into `accepted.openQuestions` on every accepting path (confirm, type-goal,
264
+ the round-2 "fix" terminal), and fixes a related ledger bug where round 1's "fix" text (the
265
+ answer to round 2's redraft) was carried forward even after round 2 was accepted outright. Not
266
+ yet proven live.
267
+ - **F175's open half — "Confirm" and "Type the goal yourself" could still accept a plan whose own
268
+ raised question was never answered, silently confirming a genuinely-missing answer away** (run
269
+ `mu0voeo4`; only the silent-drop half above was covered by the first F175 fix). hamr's
270
+ 2026-09-16 ruling (option a): `runConfirmTurn`'s `confirm`/`type-goal` paths now force each
271
+ raised question through a new `kind: 'answer'` step before returning — no extra model call, a
272
+ blank answer re-asks the same question, `null` abandons like every other `ask`. Answers land in
273
+ a new `accepted.answeredQuestions` (Q:/A: pairs), kept separate from `openQuestions` (which
274
+ keeps its "could not resolve" meaning and is honestly empty on these two paths now).
275
+ `confirmedBlock` renders the answers as binding, and the SIGNING PREPARED readout
276
+ (`scripts/author-readout.mjs`'s new `answeredQuestionLines`) prints them when non-empty. The
277
+ `fix` path is unchanged. Not yet proven live.
278
+
8
279
  ## [0.26.0] — 2026-09-14
9
280
 
10
281
  ### Added