bareloop 0.26.0 → 0.27.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 +222 -0
- package/bareloop.context.md +127 -12
- package/package.json +4 -3
- package/src/authorflow.js +255 -33
- package/src/authorjob.js +12 -9
- package/src/authorscout.js +16 -11
- package/src/cli.js +10 -1
- package/src/index.js +1 -1
- package/src/job.js +42 -26
- package/src/planrun.js +81 -2
- package/src/promptregisters.js +8 -0
- package/src/providers.js +36 -0
- package/src/source.js +138 -1
- package/src/text.js +45 -0
- package/src/validate.js +25 -1
- package/types/authorflow.d.ts +49 -7
- package/types/index.d.ts +1 -1
- package/types/job.d.ts +21 -0
- package/types/planrun.d.ts +41 -1
- package/types/providers.d.ts +28 -0
- package/types/source.d.ts +45 -0
- package/types/text.d.ts +35 -0
- package/types/validate.d.ts +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,228 @@ 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.27.0] — 2026-09-21
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`bare-agent` bumped `^0.42.0` → `^0.43.0`.** Ships BA-27 (bareloop's own corroborating
|
|
13
|
+
upstream ask, `docs/product/UPSTREAM-ASKS.md`): `OpenAIProvider`/`OllamaProvider.generate` no
|
|
14
|
+
longer throw a raw `SyntaxError` when a model's tool-call `arguments` string is malformed JSON —
|
|
15
|
+
the shared `parseToolCalls` helper returns the round priced (real `usage`) with `toolCalls: []`
|
|
16
|
+
plus its own `malformedToolCall: {name, error}` marker instead, and `Loop.run` surfaces that
|
|
17
|
+
marker unchanged on its return. No other package changed (`package-lock.json` diff is
|
|
18
|
+
`bare-agent` only).
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- **`withMalformedToolCallShim`, bareloop's local F179 stopgap (`src/authorflow.js`, added
|
|
23
|
+
2026-09-15 commit `96866d5`).** Deleted now that bare-agent 0.43.0 fixes BA-27 upstream —
|
|
24
|
+
`makeLoopGenerate` calls `loop.run()` directly again, no delegate provider. `askStructured`'s
|
|
25
|
+
`r?.malformedToolCall` check (`e9e2839`) is unchanged and now reads bare-agent's own field.
|
|
26
|
+
- **`callCasualty`'s SyntaxError admission (`src/text.js`)**, dead code once no provider bareloop
|
|
27
|
+
constructs (`AnthropicProvider`, `OpenAIProvider`, `GeminiProvider` — `src/providers.js`) can
|
|
28
|
+
still throw a JSON `SyntaxError` out of `generate()` after a billed round: verified against
|
|
29
|
+
0.43.0's installed source that Anthropic and Gemini read tool-call arguments as already-parsed
|
|
30
|
+
objects (nothing to `JSON.parse` in their response paths), and every provider's raw-HTTP-body
|
|
31
|
+
parse is already try/catch-wrapped inside `_request`. The idle-timeout (ETIMEDOUT/TimeoutError)
|
|
32
|
+
admission is unchanged.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **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.
|
|
37
|
+
|
|
38
|
+
- **F191 — a plain-folder source's confirm turn crashed live (run `mu4hc7sp`), leaving a
|
|
39
|
+
two-record spine (`author-start`, `author-phase confirm`) with no ending at all.**
|
|
40
|
+
`confirmProtections` computes protections via `classGuards` (`src/authoring.js`), which is
|
|
41
|
+
keyed by CODE LANGUAGE and throws on a plain folder's `lang: 'none-detected'` — a plain
|
|
42
|
+
folder has no language, so a confirm turn cannot honestly show real, code-derived protections
|
|
43
|
+
for one. `scripts/run-author.mjs` no longer runs a confirm turn over a plain folder at all:
|
|
44
|
+
the honest "no checks yet" stop now fires immediately after `author-start`, at $0 — no scout,
|
|
45
|
+
no confirm turn, no model call. The now-unreachable confirm-turn branch (~70 lines) is
|
|
46
|
+
deleted. Alongside it, the try/catch crash net (which used to start ~300 lines after
|
|
47
|
+
`author-start`, leaving exactly the gap this crash fell into) now opens immediately after the
|
|
48
|
+
spine exists, and its crash message reads the run's own metered-call list rather than
|
|
49
|
+
unconditionally claiming "died inside the paid span". Customer-facing text in
|
|
50
|
+
`scripts/run-interview.mjs`/`bareloop.context.md` that promised a plain-folder confirm turn
|
|
51
|
+
is corrected to match. Fail-first proven in tests (each of the three code changes shown red
|
|
52
|
+
against the prior commit, green after); a new live end-to-end test proves zero provider calls.
|
|
53
|
+
Three follow-up fixes landed in the same pass: the stop's own text claimed "Nothing was spent
|
|
54
|
+
and nothing was written," while the same branch writes `authored.json` into `--out` — both this
|
|
55
|
+
message and `scripts/run-interview.mjs`'s matching install-gap line now read "Nothing was spent.
|
|
56
|
+
Your source was not changed." (true on both counts: no provider call happens on this path, and
|
|
57
|
+
the source tree itself is never touched); an F71 regression (`process.exit(1)` right after a
|
|
58
|
+
`console.log`, which can discard already-queued stdout under a slow reader) was fixed back to
|
|
59
|
+
`process.exitCode = 1`; and a duplicate `spine <path>` print this exitCode fix's fall-through
|
|
60
|
+
briefly introduced was removed (the shared tail print already covers this exit path). Not yet
|
|
61
|
+
re-run against the original crashing patient (`docs/logs/FINDINGS.md` F191).
|
|
62
|
+
- **F190 — fixed properly this pass.** The prior fix (commit 56bbee8) hand-copied
|
|
63
|
+
`scripts/run-u.mjs`'s same-provider `baseUrl` conditional a second time. Both the author and
|
|
64
|
+
judge provider constructions in `scripts/run-author.mjs` now go through `buildRunnerProviders`
|
|
65
|
+
(`src/providers.js`), the one seam `src/cli.js` already used — this file was the one call
|
|
66
|
+
site that had not adopted it. Not yet re-proven live (`docs/logs/FINDINGS.md` F190).
|
|
67
|
+
- **F185 — an interview-authored repo job had no code path INTO `scripts/run-u.mjs`: a person
|
|
68
|
+
finishing the authoring interview was left holding a signed, hash-stable `resolved-spec.json`
|
|
69
|
+
with no way to run it, only a developer hand-step (`cp` the spec into `jobs/` + hand-add a
|
|
70
|
+
`JOBS` row).** hamr's ruling (2026-09-16, option A): `scripts/run-u.mjs` now accepts `--spec
|
|
71
|
+
<path to resolved-spec.json>` as an alternative to `--job <key>` (giving both, or neither, is a
|
|
72
|
+
loud $0 refusal). It `validateJob`'s the spec explicitly and early, then reads the workdir and
|
|
73
|
+
seed off the prepared copy's OWN source manifest (`readSourceManifest`, `src/source.js` — the
|
|
74
|
+
same reader `run-author.mjs` already uses) — the one `source-*/` sibling directory beside the
|
|
75
|
+
spec that carries a `source.json`; never typed, guessed, or defaulted, and a missing/ambiguous
|
|
76
|
+
manifest or a gone tree each refuse by name. `scripts/run-author.mjs`'s "SIGNING PREPARED — NOT
|
|
77
|
+
SIGNED" end screen now prints the exact ready-to-paste run command (real spec path, real
|
|
78
|
+
`--approve <hash>`, the provider's real env-key name per F187). Every existing arbiter gate —
|
|
79
|
+
the `--approve` signature, the budget/wall ceilings, coldReset, the close-first precheck —
|
|
80
|
+
applies unchanged; `--spec` only NAMES the job. Not yet proven live (`docs/logs/FINDINGS.md`
|
|
81
|
+
F185).
|
|
82
|
+
- **F183 — the confirm turn's model-facing prompt never showed the model the real, code-derived
|
|
83
|
+
guards or write fence, so `notChecked` could (and twice live did, runs `mu2bjmed` and
|
|
84
|
+
`mu2qmept`) falsely claim an already-enforced protection was missing** (e.g. "no ts-ignore
|
|
85
|
+
comments" and "do not edit or delete tests", while `no-suppressions` and the write fence
|
|
86
|
+
already covered both). hamr's ruling A (2026-09-16): feed the real protections in as facts,
|
|
87
|
+
never drop the model's list. `confirmProtections`'s existing code-derived list (the same one
|
|
88
|
+
already shown to the person, `src/authorflow.js`) is now computed once, before the model
|
|
89
|
+
drafts, and threaded into `confirmPrompt`'s model-facing prompt as an "ALREADY COVERED" block;
|
|
90
|
+
`CONFIRM_SYSTEM` now orders the model to check that list before naming anything in
|
|
91
|
+
`notChecked`. The model still authors `notChecked` freely — nothing made deterministic, no
|
|
92
|
+
second hand-typed guard list. Not yet proven live (`docs/logs/FINDINGS.md` F183).
|
|
93
|
+
- **Item 6 — `src/authoring.js`'s `TYPES_GENRE_TEMPLATE` (model-facing prompt text,
|
|
94
|
+
interpolated verbatim into the authoring prompt) was missing from `src/
|
|
95
|
+
promptregisters.js`'s `PROMPT_REGISTERS` inventory entirely.** Added; no other prompt content
|
|
96
|
+
found in that file. `docs/product/PRD.md`'s item 33 close-out box ticked.
|
|
97
|
+
- **F182 — the interview never waited for the install, so "Run it now?" was unreachable for a
|
|
98
|
+
repo that needs packages.** `scripts/run-interview.mjs` printed the install-gap message right
|
|
99
|
+
after `prepareSource` and fell straight through to the class questions, only re-checking
|
|
100
|
+
`missingDependencies` once, at hand-off — too late for the offer to ever fire. It now pauses
|
|
101
|
+
right there and loops on the same check (`"Press Enter once it has finished to check again, or
|
|
102
|
+
type skip to carry on without it: "`), printing `"packages found — carrying on."` on a
|
|
103
|
+
resolved recheck; `skip` or end of input carries on unresolved, same as before. Also drops the
|
|
104
|
+
misleading "then rerun this command" wording, which pointed at restarting the whole interview
|
|
105
|
+
when only the install is needed. Not yet proven live.
|
|
106
|
+
- **F181 — a key with an embedded newline (or other control character, or stray whitespace)
|
|
107
|
+
read as "set" by every presence-only key check and then crashed Node's own header-encode
|
|
108
|
+
(`ERR_INVALID_CHAR`) inside the paid span, rather than refusing at $0.** One exported pure
|
|
109
|
+
helper, `apiKeyProblem(value)` (`src/providers.js`, beside the provider table), reports a
|
|
110
|
+
plain-English reason (or `null`) for a value carrying CR, LF, TAB, another C0/DEL control
|
|
111
|
+
character, or leading/trailing whitespace — it never trims or repairs. Wired at every door
|
|
112
|
+
that already presence-checks a key: `scripts/run-author.mjs` (worker and judge keys),
|
|
113
|
+
`scripts/run-u.mjs` (worker and judge keys), `src/cli.js`'s `bareloop run`
|
|
114
|
+
(`ANTHROPIC_API_KEY`), and `scripts/run-interview.mjs`'s `KEYED` offer gate (a malformed key
|
|
115
|
+
no longer counts as keyed). Every refusal names the env var and the reason class, never the
|
|
116
|
+
value. Not yet proven live.
|
|
117
|
+
- **F187 — `scripts/run-u.mjs`'s printed invocation hints (the "approve and run" line and the
|
|
118
|
+
sleep-inhibitor wrapper line, both built through the shared `invoke()` helper) always named
|
|
119
|
+
`ANTHROPIC_API_KEY`, regardless of the job's actual provider.** A job on `openai-api`
|
|
120
|
+
(DeepSeek) printed the wrong variable name to set. `invoke()` now reads `providerEntry.envKey`
|
|
121
|
+
— the same resolved name the real key check at launch already uses. Not yet proven live.
|
|
122
|
+
- **F186 — the printed BEHAVIOUR line (and any replay) counted tool calls from EVERY run that
|
|
123
|
+
ever touched the patient tree, not just the run being read, because the authoring scout's gate
|
|
124
|
+
audit is written directly into the patient tree and the tree's `.gitignore` denies `*.jsonl`,
|
|
125
|
+
so a cold reset never removes it.** Fixed at the source, never by filtering `runBehaviour` on
|
|
126
|
+
`run_id` (a single run legitimately spans several). `scripts/run-author.mjs` now archives its
|
|
127
|
+
own gate audit out of the patient tree the moment authoring ends (`archiveGateAudit()`, called
|
|
128
|
+
from `finally` and once more before the one `process.exit()` path that skips it).
|
|
129
|
+
`scripts/run-u.mjs` now moves any stale audit aside right after `coldReset`, before its own
|
|
130
|
+
Gate ever opens (`moveStaleGateAudit`, new export from `scripts/u-patient.mjs`) — never on the
|
|
131
|
+
resume branch, where the tree's audit is that same run's own prior leg. Not yet proven live.
|
|
132
|
+
- **F184 — a malformed tool-call round on the WORKER path (`src/planrun.js`'s Loop-path `ask()`)
|
|
133
|
+
ended an attempt with nothing on the spine naming it — indistinguishable from the model
|
|
134
|
+
genuinely producing no useful work.** `ask()` now reads bare-agent 0.43.0's own
|
|
135
|
+
`r.malformedToolCall` marker (present since BA-27) and emits a distinct `worker-malformed-tool-
|
|
136
|
+
call` record (`{phase, iteration, name, error}`, scrubbed and length-capped) — visibility only:
|
|
137
|
+
no retry, no change to strikes/ladder/attempt counting/verdict routing, all arbiter-adjacent and
|
|
138
|
+
out of scope. Not yet proven live.
|
|
139
|
+
- **F179 — a malformed tool-call JSON on the FIRST authoring call had no earlier sound
|
|
140
|
+
declaration to fall back to, so 318e066's provider-red stopped the run outright.** hamr's
|
|
141
|
+
2026-09-15 ruling: retry, never repair. `withMalformedToolCallShim` (`src/authorflow.js`,
|
|
142
|
+
wired into `makeLoopGenerate`) is a delegate provider (`Object.create`, never a mutation of
|
|
143
|
+
the shared instance) whose `_request` strips a malformed tool call out of the raw response
|
|
144
|
+
BEFORE `generate()`'s own `JSON.parse` can throw on it, so the round comes back PRICED on
|
|
145
|
+
bare-agent's own real usage instead of crashing. `askStructured` reads the resulting round
|
|
146
|
+
through its EXISTING malformed-emission retry ladder (`MAX_STRUCTURE_RETRIES`, no new cap),
|
|
147
|
+
tagged with a new `malformed-tool-call-arguments` axis — checked BEFORE the accept path and
|
|
148
|
+
regardless of how many calls survived the strip, so a reply carrying a malformed call
|
|
149
|
+
alongside a valid one is never accepted either (follow-up fix, same day, review-caught).
|
|
150
|
+
Scoped to `makeLoopGenerate` only — the authoring declaration calls and the confirm turn; the
|
|
151
|
+
scout and the worker path build their own provider wiring and still only get 318e066's stop
|
|
152
|
+
(an open gap). Not yet proven live.
|
|
153
|
+
- **F179/F180 — bare-agent 0.42.0's OpenAIProvider could throw a raw SyntaxError mid-call (a
|
|
154
|
+
malformed tool-call `arguments` string, `JSON.parse` with no try/catch), crashing past
|
|
155
|
+
`askStructured` and discarding an already-sound, already-measured declaration while the
|
|
156
|
+
billed call went unbooked.** `askStructured` (`src/authorflow.js`) now wraps its `generate`
|
|
157
|
+
call in try/catch, tested against a new shared predicate, `callCasualty` (`src/text.js`,
|
|
158
|
+
beside `priceOf`): a `HaltError` still re-raises (a governance exit, never laundered); an
|
|
159
|
+
ETIMEDOUT/TimeoutError-shaped throw or a JSON-mentioning SyntaxError lands as the same typed
|
|
160
|
+
`providerError` the resolved-`{error}` path already produced, booked at `costUsd: null`
|
|
161
|
+
(honest-unknown, never a complete-looking floor); anything else still crashes.
|
|
162
|
+
`authorscout.js`'s `settled` seam, which already hand-caught its own narrower idle-timeout
|
|
163
|
+
class, now delegates to the same predicate. No JSON repair, no new retry. Not yet proven live
|
|
164
|
+
(run `mu2bjmed` is the live crash this fixes; the corroborating upstream ask is BA-27 in
|
|
165
|
+
`docs/product/UPSTREAM-ASKS.md`).
|
|
166
|
+
- **F178/F177 — a signed fence could name `.git` or `node_modules` directly (or reach a nested
|
|
167
|
+
`node_modules` through an otherwise-legal fence), letting writes there go invisibly to
|
|
168
|
+
`changedSet`.** `scopeContained` (`src/validate.js`, the one containment law `validateJob`
|
|
169
|
+
and `validatePlan` both go through) now refuses any fence/scope whose normalized prefix has a
|
|
170
|
+
whole path segment equal to `.git` or `node_modules` (hamr's ruling, 2026-09-14, option B) —
|
|
171
|
+
a look-alike like `.github`/`my.git`/`node_modules_util` stays admitted. The runtime belt for
|
|
172
|
+
a NESTED `node_modules` a legal fence can still reach uses `tools.denyArgPatterns`
|
|
173
|
+
(`FORBIDDEN_WRITE_SEGMENT_PATTERN`, `src/planrun.js`), scoped to `write`/`edit` only —
|
|
174
|
+
bareguard's `fs.deny` was tried first for `.git` and reverted: it blocks ALL fs actions,
|
|
175
|
+
reads included, which silently over-blocked (caught by the full gate's own drift test,
|
|
176
|
+
`tests/tools.test.js`); `fs.deny` is never used for either book now. Not yet proven live.
|
|
177
|
+
- **F177 — a repo source whose tracked `.gitignore` did not mention `node_modules` let
|
|
178
|
+
installed packages read as the worker's own writes.** `changedSet` (`src/kinds.js`) unions
|
|
179
|
+
the tracked diff with `git ls-files --others --exclude-standard`, so an unignored
|
|
180
|
+
`node_modules` installed into the copy (live-verified: 247 files on a real copy of pulselog)
|
|
181
|
+
read as changes the worker made. `prepareSource` now hides `node_modules` in the copy's own
|
|
182
|
+
PRIVATE `.git/info/exclude` (hamr's ruling, 2026-09-14, option A) — the source repo's tracked
|
|
183
|
+
files, `.gitignore` included, are never touched. Not yet proven live.
|
|
184
|
+
- **PRD item 33 close-out — `prepareSource` freezes tracked files only, so a JS/TS repo's
|
|
185
|
+
prepared copy never carried `node_modules`, and every close stage needing a tool (`tsc`, a
|
|
186
|
+
test runner) instrument-stopped with nothing telling the person why.** bareloop never runs an
|
|
187
|
+
install itself (hamr's ruling, 2026-09-14, option A): `missingDependencies` (`src/source.js`,
|
|
188
|
+
exported) is a pure, $0, JS/TS-only detector that names the exact install command.
|
|
189
|
+
`scripts/run-interview.mjs` prints it right after `prepareSource` succeeds and again at the
|
|
190
|
+
hand-off, where a still-open gap suppresses the "Run it now?" offer entirely.
|
|
191
|
+
`scripts/run-author.mjs` refuses `source-deps-missing` at $0, before the scout and before the
|
|
192
|
+
provider/key are even read. Not yet proven live end to end (the detector and the refusal
|
|
193
|
+
paths are each proven directly; a real `npm ci` against a real prepared copy of a real repo
|
|
194
|
+
confirmed the hidden git's seed stays clean when the source's `.gitignore` ignores
|
|
195
|
+
`node_modules` — see the build report for the gap this does NOT cover, a repo whose
|
|
196
|
+
`.gitignore` does not ignore `node_modules`).
|
|
197
|
+
- **PRD item 33 close-out (L17) — the authoring interview never admitted an endpoint, so a
|
|
198
|
+
DeepSeek draft (`--provider openai-api`) authored against api.openai.com instead.**
|
|
199
|
+
`scripts/run-interview.mjs` now takes an optional `--base-url <url>` flag (no default; absent
|
|
200
|
+
leaves the draft's `baseUrl` field out entirely, never `null`/`''`) and validates it through
|
|
201
|
+
the existing `validateJob` `baseUrl` rule at $0 before writing anything. The interview's
|
|
202
|
+
header, the hand-off command, and `scripts/run-author.mjs`'s own start banner/`author-start`
|
|
203
|
+
event now name the endpoint when one is set; the key hint still points at the provider's own
|
|
204
|
+
env var (`OPENAI_API_KEY` for a DeepSeek endpoint) since there is no separate
|
|
205
|
+
endpoint-specific key. Not yet proven live.
|
|
206
|
+
- **F176 — the revise ladder kept a later revision even when it measured worse than an earlier
|
|
207
|
+
one.** `authorClose` (`src/authorflow.js`) now tracks every MEASURED (validated + seed-read)
|
|
208
|
+
iteration and falls back to the NEWEST SOUND one — a real red/green verdict, never an
|
|
209
|
+
instrument-stop — when the last measured iteration is unsound and an earlier one is not.
|
|
210
|
+
`stop` is never changed; the swap is reported via a new `fellBack` return field, an
|
|
211
|
+
`onPhase('author-fallback', …)` event, and the terminal signing readout. Not yet proven live.
|
|
212
|
+
- **F175 — the confirm turn's own honestly-raised `questions` never reached
|
|
213
|
+
`accepted.openQuestions`.** `runConfirmTurn` now carries the model's own `questions` from the
|
|
214
|
+
plan being accepted into `accepted.openQuestions` on every accepting path (confirm, type-goal,
|
|
215
|
+
the round-2 "fix" terminal), and fixes a related ledger bug where round 1's "fix" text (the
|
|
216
|
+
answer to round 2's redraft) was carried forward even after round 2 was accepted outright. Not
|
|
217
|
+
yet proven live.
|
|
218
|
+
- **F175's open half — "Confirm" and "Type the goal yourself" could still accept a plan whose own
|
|
219
|
+
raised question was never answered, silently confirming a genuinely-missing answer away** (run
|
|
220
|
+
`mu0voeo4`; only the silent-drop half above was covered by the first F175 fix). hamr's
|
|
221
|
+
2026-09-16 ruling (option a): `runConfirmTurn`'s `confirm`/`type-goal` paths now force each
|
|
222
|
+
raised question through a new `kind: 'answer'` step before returning — no extra model call, a
|
|
223
|
+
blank answer re-asks the same question, `null` abandons like every other `ask`. Answers land in
|
|
224
|
+
a new `accepted.answeredQuestions` (Q:/A: pairs), kept separate from `openQuestions` (which
|
|
225
|
+
keeps its "could not resolve" meaning and is honestly empty on these two paths now).
|
|
226
|
+
`confirmedBlock` renders the answers as binding, and the SIGNING PREPARED readout
|
|
227
|
+
(`scripts/author-readout.mjs`'s new `answeredQuestionLines`) prints them when non-empty. The
|
|
228
|
+
`fix` path is unchanged. Not yet proven live.
|
|
229
|
+
|
|
8
230
|
## [0.26.0] — 2026-09-14
|
|
9
231
|
|
|
10
232
|
### Added
|
package/bareloop.context.md
CHANGED
|
@@ -538,6 +538,7 @@ contract); returns the parsed spec on ok, `null` on any red. A spec carrying the
|
|
|
538
538
|
generic unknown-field. Menus exported: `CLOSE_TYPES`, `CLASS_BY_CLOSE`, `GOLD_COMPARE`,
|
|
539
539
|
`CADENCE_UNITS`, `PROVIDERS`, `CONDITION_KEYS`, `TOOL_MENU`, `LOCKED_TOOLS`, `STORE_VERBS`,
|
|
540
540
|
`VERDICT_TYPES`, `LOCKED_VERDICTS` — plus `checkMenu` itself.
|
|
541
|
+
|
|
541
542
|
`globToPrefix(scope)` and `scanSecrets(text)` are exported too — the write-scope prefix
|
|
542
543
|
mapping and the one secret-shape sweep `validateJob` itself is built from (`src/validate.js`).
|
|
543
544
|
|
|
@@ -615,7 +616,7 @@ a step, or test it without a provider.
|
|
|
615
616
|
| interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. The interview ADMITS only what is on the menu (`MENU_CLASSES`): nothing is locked today (`LOCKED_CLASSES` is empty), and any pick off the menu returns a refusal rather than `ok:true` — ask `MENU_CLASSES` which classes can be authored, never this sentence. 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 several slots since it was frozen (D13's genre confirm; the repo question, dropped 2026-08-15 because `repoPath` is mandatory structured input; and, at PRD item 33 M3 piece 3, the old "which files change/read" question, replaced by Source and Destination — MECHANICAL fields, proven against the machine, never a numbered answer), and it renumbers CONTIGUOUSLY from 1 each time rather than leaving a gap. Today the green set is THREE questions (Goal, Success, Guardrails); the `soft-green` set is those three plus one more — Judge Examples (one example to pass, one to fail, and why) — the judged floor's one required input, compiled into the signed rubric card and the frozen calibration set. `verdictType` and `repoPath` are STRUCTURED input, never parsed out of prose. Answers are scrubbed at INGEST |
|
|
616
617
|
| 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 |
|
|
617
618
|
| 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 |
|
|
618
|
-
| 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 |
|
|
619
|
+
| 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. **F176 (fixed):** "kept the last accepted revision" is not the same claim as "kept the best revision" — a later revise round can VALIDATE fine yet MEASURE worse (every stage instrument-stops rather than reading a real red/green verdict). `declaration`/`seedRead`/`finalFrom`/the returned `genreEnv.dropped` are therefore the NEWEST SOUND measured iteration, not simply the last one, when the last one instrument-stopped and an earlier one did not; `stop` itself never changes. The swap is never silent: the return also carries **`fellBack`** — `null` on every run where it never fired, else `{from, to, brokenStages}` naming which iteration was dropped, which now stands, and which of the dropped one's stages instrument-stopped — reported through `onPhase('author-fallback', …)` as it happens and printed at the terminal readout (`fellBackLines`, `scripts/author-readout.mjs`). **F179/F180 (history):** bare-agent 0.42.0's OpenAIProvider used to THROW mid-call on malformed tool-call JSON (a raw SyntaxError, after the billed round), crashing past this function; a 2026-09-15 stopgap caught it as a `provider-red`/`ask.providerError` casualty (booked `costUsd: null`) so a sound prior declaration was kept via the fallback above rather than discarded. **F179 (bare-agent 0.43.0, BA-27 — the real mechanism):** the throw is fixed upstream. `OpenAIProvider`/`OllamaProvider.generate` now return a malformed round PRICED (real `usage`, never `costUsd: null`) with `toolCalls: []` plus their own `malformedToolCall: {name, error}` marker, which `Loop.run` surfaces unchanged on its return. For the authoring declaration and confirm-turn calls, `askStructured` reads that marker and RETRIES through the existing malformed-emission retry ladder (`MAX_STRUCTURE_RETRIES`) instead of stopping the run — nothing is ever repaired, and a malformed FIRST call no longer needs an earlier sound declaration to fall back to. The generic provider-casualty fallback above (idle-timeout, etc.) still applies for every other admitted class. **F184 (fixed, worker path):** the scout and the worker path never joined that retry ladder (still true — retrying is arbiter-adjacent, out of scope), but the worker path's `ask()` (`src/planrun.js`) now at least READS the marker where it already sees it and emits a distinct spine record, `worker-malformed-tool-call` (`{phase, iteration, name, error}`), before returning `r` unexamined exactly as before — no retry, no change to strikes/ladder/attempt counting or verdict routing, purely the same visibility bar this section's authoring-path fix already set |
|
|
619
620
|
| everything above, composed | `authorCloseForJob({ verdictType, answers, repoPath, lang, generate, ceilingUsd?, ... })` → `{ ok, closeDecl, verdictType, refusal, cost, ... }` | refuses at the cheapest gate that can refuse: an interview refusal costs **zero**. THE GENRE REFUSAL LIVES HERE (not in the interview): a language the catalogue owns no data for, and a LOCKED KIND the model reached for, both come back as counted `request-red` demand. **`ceilingUsd` is ONE number handed to BOTH paid seams** (the survey's and the declaration loop's) — the advertised ceiling and the enforced ceiling are the same ceiling. It travels as an EXPLICIT `null` when unset, because unbounded is a stated choice and an absent field read as falsy is that state reached by accident — and anything that is neither a finite number nor `null` throws at the seam rather than becoming that state by typo |
|
|
620
621
|
| assembly | `assembleSpec(specDraft, { closeDecl, verdictType })` | folds the authored half into the OPERATOR's half. Budgets, the fence, cadence, escalation and the provider are never authored by anything here. The GOAL is passed through, not generated. **A draft that ALREADY carries the authored half is REFUSED — it THROWS, never merges and never overwrites.** `AUTHORED_SPEC_FIELDS` (`close`, `closeDecl`, `verdictType`) names the fields this fold WRITES, as data, so the refusal and the fold cannot disagree about which half of the spec is which; the rule is `job.js`'s one step earlier (two closes are two arbiters, and picking one silently is how a signed artefact stops meaning what the signer read). `scripts/run-author.mjs` asks the same question at **$0 BEFORE the scout**, so the answer never arrives after the model has been paid |
|
|
621
622
|
| the three gates | `prepareSigning({ spec, workdir, seedRef? })` → `{ ok, specHash, gates, work, guards, refusal }` | D9, and it NEVER signs |
|
|
@@ -875,7 +876,7 @@ is exactly what F87 forbids.
|
|
|
875
876
|
`scripts/run-interview.mjs` existed the only way in was to hand-write an `answers.json` and a
|
|
876
877
|
spec draft — precisely the SWE tax this product refuses. That script is the half that ASKS
|
|
877
878
|
(`--verdict <class> --provider <anthropic-api|openai-api|gemini-api|clipipe-subscription>
|
|
878
|
-
--out <outdir> [--budget <usd>]`), and it is deliberately GLUE: **it
|
|
879
|
+
--out <outdir> [--budget <usd>] [--base-url <url>]`), and it is deliberately GLUE: **it
|
|
879
880
|
calls no provider at all**, so a whole interview costs $0 and no model ever sees it. `--provider`
|
|
880
881
|
is REQUIRED with NO default — bareloop is LLM-agnostic, and defaulting it would silently re-lock
|
|
881
882
|
every interview onto one vendor; missing or empty dies loud, at the door, naming the same menu
|
|
@@ -883,6 +884,17 @@ every interview onto one vendor; missing or empty dies loud, at the door, naming
|
|
|
883
884
|
`scripts/run-author.mjs` needs no flag of its own — it resolves whatever the interview wrote
|
|
884
885
|
down.
|
|
885
886
|
|
|
887
|
+
`--base-url` is OPTIONAL with NO default (PRD item 33 close-out — L17 named the provider but
|
|
888
|
+
never admitted the endpoint): a provider name's table entry (`src/providers.js`) can be reached
|
|
889
|
+
through an OpenAI-compatible gateway other than its own default host — DeepSeek, today's one
|
|
890
|
+
secondary, is reached as `--provider openai-api --base-url https://api.deepseek.com/v1`. Absent,
|
|
891
|
+
the draft carries NO `baseUrl` field at all (never `null`, never `''`) and every provider
|
|
892
|
+
constructor defaults its own endpoint. When given, it lands in the draft's `baseUrl` field
|
|
893
|
+
beside `provider` and passes through the SAME `validateJob` pass every other field takes below,
|
|
894
|
+
at $0: `http://` to a public host, an embedded `user:pass@`, or an unparseable string reds there,
|
|
895
|
+
before anything is written. The key still follows the PROVIDER, not the endpoint — a DeepSeek
|
|
896
|
+
draft's key still loads from `OPENAI_API_KEY`, and the hand-off says so.
|
|
897
|
+
|
|
886
898
|
**Source and Destination are the interview's own FIRST TWO QUESTIONS, and they REPLACE
|
|
887
899
|
`--patient`** (PRD item 33 M3, ruling 2, `docs/product/ITEM33-BUILD.md`). Source — "a local
|
|
888
900
|
folder, subfolder or file, or one URL the machine can reach" — is asked first and checked
|
|
@@ -910,8 +922,10 @@ piece 4 step S6 (D5 = A), the FORM CONTINUES — the class's own questions, job
|
|
|
910
922
|
wall are all still asked, and the draft is still written, with no `writeScope` field
|
|
911
923
|
(`PLAIN_FOLDER_DEFERRED_FIELDS`, `src/authorjob.js` — Destination there is an output directory,
|
|
912
924
|
never a fence). The honest named stop (bareloop's check catalogue is code-genre only today,
|
|
913
|
-
ruling 7 → M4) no longer lands HERE — it moves to `run-author.mjs`,
|
|
914
|
-
|
|
925
|
+
ruling 7 → M4) no longer lands HERE — it moves to `run-author.mjs`, which now gives it
|
|
926
|
+
IMMEDIATELY, at $0 (D5 amended 2026-09-21, F191: no scout and no confirm turn either — a plain
|
|
927
|
+
folder has no code language, so the confirm turn's own code-derived protections cannot be
|
|
928
|
+
computed for one; see the run-author.mjs section below). Everything load-bearing in it is
|
|
915
929
|
borrowed rather than respelled —
|
|
916
930
|
the QUESTIONS are the library's frozen sets (`questionsFor` / `requiredAnswersFor`), printed as
|
|
917
931
|
handed over and never re-worded, re-ordered or re-numbered by the script; the REFUSALS are
|
|
@@ -946,7 +960,9 @@ spends it.
|
|
|
946
960
|
DIFFERENT PROCESS under its own ceiling; the interview prints its exact command line (with
|
|
947
961
|
`--budget` propagated only when one was given, and a note when the CHOSEN provider's own key —
|
|
948
962
|
`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, resolved through the same provider table
|
|
949
|
-
`run-author.mjs` and `run-u.mjs` share — is unset in this shell
|
|
963
|
+
`run-author.mjs` and `run-u.mjs` share — is unset in this shell, OR set but MALFORMED: F181 — a
|
|
964
|
+
value carrying a line break/control character/stray whitespace (a two-line secret-store entry,
|
|
965
|
+
e.g.) reads a distinct reason via `apiKeyProblem` (`src/providers.js`), never the value)
|
|
950
966
|
before asking `Run it now? [y/N]`, so declining still leaves a command to paste and two files
|
|
951
967
|
already on disk. Only an explicit yes spends — the answer that costs nothing is the one you get
|
|
952
968
|
by saying nothing, the same lean the pause's doors take. On a yes it releases stdin before
|
|
@@ -976,7 +992,10 @@ into a success one process up.
|
|
|
976
992
|
- `run-author.mjs` — **1** a refusal or a failed gate (this now includes the confirm turn's own
|
|
977
993
|
`confirm-abandoned`/`confirm-restart` stops — input ended, or the person chose to start over,
|
|
978
994
|
both counted through the same generic "not-authored" branch every other authoring refusal
|
|
979
|
-
uses
|
|
995
|
+
uses, and `source-deps-missing` — the prepared copy's `package.json` lists dependencies with
|
|
996
|
+
no `node_modules`, refused at $0 before the scout and before the provider/key are even
|
|
997
|
+
checked, naming the exact install command); **2** operator/config; **3** a leak; **4** a
|
|
998
|
+
CRASH inside the paid span. **3
|
|
980
999
|
deliberately OVERRIDES a 4**: a secret sitting in a written file is the harder line of the
|
|
981
1000
|
two, and the crash keeps both of its own louder channels — the whole error on stderr and its
|
|
982
1001
|
own `author-crash` spine record. **This script is now interactive** (step S4): the confirm
|
|
@@ -984,12 +1003,15 @@ into a success one process up.
|
|
|
984
1003
|
who wrote what and lets the person confirm the plan, fix it (up to 2 rounds), type the goal
|
|
985
1004
|
themselves, or start over — everything else (the survey, the declaration loop, D9's gates)
|
|
986
1005
|
stays unattended. `rl.close()` runs in a `finally` around the whole paid span. **A plain-folder
|
|
987
|
-
Source (step S6, D5
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1006
|
+
Source (step S6, D5 amended 2026-09-21, F191) gets NO scout AND NO confirm turn.** D5
|
|
1007
|
+
originally ran the confirm turn over a $0, no-git directory listing of the frozen tree first,
|
|
1008
|
+
THEN gave the honest "no checks yet" stop (`request-red`/`non-code-source`) once a plan was
|
|
1009
|
+
confirmed. Live run `mu4hc7sp` crashed inside that confirm turn instead: `confirmProtections`
|
|
1010
|
+
calls `classGuards` (`src/authoring.js`), which is keyed by CODE LANGUAGE and throws on a
|
|
1011
|
+
plain folder's `lang: 'none-detected'` — a plain folder has no language, so the confirm turn
|
|
1012
|
+
cannot honestly show real, code-derived protections for one, and now never runs one. The stop
|
|
1013
|
+
fires immediately after `author-start`, at $0, no scout, no confirm turn, no model call — the
|
|
1014
|
+
spine is exactly `author-start` → `author-end{outcome:'not-authored', stop:'non-code-source'}`.
|
|
993
1015
|
|
|
994
1016
|
**The protections a person is shown in the confirm turn come from CODE, never the model**
|
|
995
1017
|
(fix #1, live run mu0voeo4, 2026-09-14 — a run once showed a model-invented "behavior-
|
|
@@ -1005,6 +1027,26 @@ silently dropped. It is shown under its own heading in the menu (`scripts/run-au
|
|
|
1005
1027
|
and threaded through to `accepted.notChecked` and `confirmedBlock`'s own "THE PERSON ASKED
|
|
1006
1028
|
FOR THESE, BUT NOTHING CHECKS THEM" section for the composer.
|
|
1007
1029
|
|
|
1030
|
+
**The model drafting `notChecked` is now shown the SAME code-derived protections list before
|
|
1031
|
+
it drafts** (F183, ruling A, 2026-09-16 — live runs `mu2bjmed` and `mu2qmept` both had the
|
|
1032
|
+
model falsely claim an already-enforced guard or the write fence was missing, because it was
|
|
1033
|
+
never told what was actually enforced). `confirmProtections`'s output is computed once, before
|
|
1034
|
+
the paid round loop, and threaded into `confirmPrompt`'s model-facing prompt as an "ALREADY
|
|
1035
|
+
COVERED" block; `CONFIRM_SYSTEM` orders the model to check that list before naming anything in
|
|
1036
|
+
`notChecked`. The model still authors `notChecked` freely — this never makes the list
|
|
1037
|
+
deterministic, and it is still never a hand-typed second spelling of the guards. Not yet
|
|
1038
|
+
proven live.
|
|
1039
|
+
|
|
1040
|
+
**`accepted.openQuestions` is the model's OWN honest `questions`, from the plan being
|
|
1041
|
+
ACCEPTED** (F175, fixed 2026-09-14) — never a prior, superseded round's, and never limited to
|
|
1042
|
+
a person's typed "fix" text as it used to be. On the round-2 "fix" terminal path (D3: there is
|
|
1043
|
+
no 3rd call) the model's own questions come first, then the person's still-pending fix text.
|
|
1044
|
+
Shown to the composer by `confirmedBlock` ("state these in your notes, never decide them
|
|
1045
|
+
silently") and at the terminal signing readout (`openQuestionLines`, `scripts/author-readout.mjs`).
|
|
1046
|
+
**Not fully closed:** the confirm menu still lets a person pick "Confirm" without being forced
|
|
1047
|
+
to resolve a raised question first — only the silent-drop-from-the-signed-record half of F175
|
|
1048
|
+
is fixed; the menu-bypass half stays open.
|
|
1049
|
+
|
|
1008
1050
|
**A crash inside the paid span leaves a BODY.** The fallible span — from just after
|
|
1009
1051
|
`author-start` to the end of the main flow — sits in ONE try/catch that RETRIES NOTHING and
|
|
1010
1052
|
SWALLOWS NOTHING. The operator's copy goes first and verbatim (it must not depend on the two
|
|
@@ -2438,6 +2480,20 @@ objects will not be caught by this. `scripts/behaviour-readout.mjs <gate-audit.j
|
|
|
2438
2480
|
prints the block for one run or every run_id found in the file, skipping malformed lines with
|
|
2439
2481
|
a count rather than throwing.
|
|
2440
2482
|
|
|
2483
|
+
**F186 (2026-09-15) — the "run's own gate-audit JSONL" claim above was FALSE for a while, at the
|
|
2484
|
+
file-hygiene layer, never inside `runBehaviour` itself.** The authoring scout's gate audit
|
|
2485
|
+
(`src/authorscout.js`'s `defaultSurveyor`) writes directly into the patient tree at its root
|
|
2486
|
+
(`<workdir>/gate-audit.jsonl`), and the tree's own `.gitignore` denies `*.jsonl`, so
|
|
2487
|
+
`scripts/run-u.mjs`'s cold reset (`git clean -fd`, no `-x`) never removed it — the file
|
|
2488
|
+
accreted rows from every authoring scout AND every worker run that had ever touched the tree,
|
|
2489
|
+
and `run-u.mjs`'s end-of-run rename claimed the WHOLE accreted file as "this run's own audit"
|
|
2490
|
+
with no filtering. The fix is at the source, never a `run_id` filter bolted onto `runBehaviour`
|
|
2491
|
+
(a single run legitimately spans several run_ids): `scripts/run-author.mjs` archives its own
|
|
2492
|
+
audit out of the tree the moment authoring ends; `scripts/run-u.mjs` moves any stale audit
|
|
2493
|
+
aside right after `coldReset`, before its own Gate ever opens (never on a resume, where the
|
|
2494
|
+
tree's audit is that same run's own prior leg). With both halves in place, the file this
|
|
2495
|
+
section describes really is scoped to the one run reading it, restoring the claim above.
|
|
2496
|
+
|
|
2441
2497
|
```js
|
|
2442
2498
|
import { runBehaviour, formatBehaviour } from 'bareloop';
|
|
2443
2499
|
const events = fs.readFileSync(auditFile, 'utf8').trim().split('\n').map(JSON.parse);
|
|
@@ -3202,6 +3258,37 @@ freeze loop never trusts a buffer read a moment earlier), `into-exists`,
|
|
|
3202
3258
|
refusal never throws, never overwrites a person's file, and never lands a destination inside
|
|
3203
3259
|
the run's own scratch tree.
|
|
3204
3260
|
|
|
3261
|
+
**`prepareSource` copies ONLY git-tracked files** (hamr's ruling, unchanged) — a JS/TS repo's
|
|
3262
|
+
prepared copy therefore never carries `node_modules`, and every close stage that needs a tool
|
|
3263
|
+
(`tsc`, a test runner) would instrument-stop with nothing telling the person why. bareloop
|
|
3264
|
+
NEVER runs an install itself — it only ever touches gated primitives, never a shell-out to
|
|
3265
|
+
`npm`/`pnpm`/`yarn`/`bun` on the person's behalf. `missingDependencies` is exported ($0,
|
|
3266
|
+
JS/TS only for now — other languages return `null`, M3b's job): it takes `(treeDir,
|
|
3267
|
+
sourceSubdir?)` and walks from `sourceSubdir` up to `treeDir` (the same nearest-manifest rule
|
|
3268
|
+
`detectLanguage` uses, done independently to avoid a `source.js` ⇄ `detectlang.js` import
|
|
3269
|
+
cycle), and when the nearest `package.json` names a non-empty `dependencies`/`devDependencies`
|
|
3270
|
+
with no sibling `node_modules`, it returns `{manager, command, reason}` — the exact install
|
|
3271
|
+
line (`npm ci`/`pnpm install --frozen-lockfile`/`yarn install --frozen-lockfile`/`bun install
|
|
3272
|
+
--frozen-lockfile` by lockfile, else `npm install`), `cd`-prefixed for a subfolder job. An
|
|
3273
|
+
invalid `package.json`, no manifest found, or `node_modules` already present all return `null`
|
|
3274
|
+
— "cannot tell" is never a fabricated gap. `run-interview.mjs` prints the gap and the exact
|
|
3275
|
+
command right after `prepareSource` succeeds, and now PAUSES there (F182 fix, 2026-09-15):
|
|
3276
|
+
"Press Enter once it has finished to check again, or type skip to carry on without it" —
|
|
3277
|
+
Enter re-runs `missingDependencies` on the spot ("packages found — carrying on." or "still
|
|
3278
|
+
missing (…) — try again, or type skip to carry on without it.", looping), `skip` (or end of
|
|
3279
|
+
input, treated the same) carries on unresolved. It re-checks once more at the hand-off: a
|
|
3280
|
+
still-open gap suppresses the "Run it now?" offer entirely (the same shape the unkeyed-shell
|
|
3281
|
+
gate already uses) rather than spending on a run that would only instrument-stop — this is
|
|
3282
|
+
what the pause makes reachable: before the fix the interview fell straight through to the
|
|
3283
|
+
class questions and only ever re-checked once, too late for the offer to ever fire.
|
|
3284
|
+
`run-author.mjs` refuses
|
|
3285
|
+
`source-deps-missing` (a `request-red`, routed through the same `refusalEvents()` channel
|
|
3286
|
+
`language-unsupported` uses) at $0, before the scout and before the provider/key are even
|
|
3287
|
+
read. Once the person installs packages into the copy themselves, `prepareSource` has already
|
|
3288
|
+
hidden `node_modules` from `changedSet` via the copy's own PRIVATE `.git/info/exclude` (F177),
|
|
3289
|
+
so those installed files never read as the worker's own writes — the source repo's tracked
|
|
3290
|
+
`.gitignore` is never touched.
|
|
3291
|
+
|
|
3205
3292
|
**Destination is a DIRECTORY, never a filename** (D3 rework, hamr's ruling, 2026-09-12,
|
|
3206
3293
|
condensed in `docs/product/ITEM33-BUILD.md`). It may already exist and need not be empty; it
|
|
3207
3294
|
may sit inside the source itself (the same-dir case: "i can limit your actions to a certain
|
|
@@ -3269,6 +3356,22 @@ Not wired yet, named rather than silently absent: `bareloop run` (the exported b
|
|
|
3269
3356
|
33/M5. The intake form that lets a person fill in Source/Destination through the authoring
|
|
3270
3357
|
interview (rather than the CLI above) is item 33/M3, not yet built.
|
|
3271
3358
|
|
|
3359
|
+
**Running an authored spec needs no developer hand-step any more (F185, fixed 2026-09-16).**
|
|
3360
|
+
`scripts/run-u.mjs --spec <path to resolved-spec.json>` is an alternative to `--job <key>`
|
|
3361
|
+
(both, or neither, is a loud $0 refusal): it `validateJob`'s the spec on disk explicitly and
|
|
3362
|
+
early, then finds the ONE `source-*/` sibling directory beside it that carries a
|
|
3363
|
+
`source.json` (`run-interview.mjs` prepares the source door and invokes `run-author.mjs`
|
|
3364
|
+
against the same `--out`, so `resolved-spec.json` and `source-<runid>/{source.json,tree/}`
|
|
3365
|
+
are always siblings) and reads the workdir (`<that dir>/tree`) and seed off that SAME
|
|
3366
|
+
manifest `readSourceManifest` already serves elsewhere — never typed, guessed, or defaulted.
|
|
3367
|
+
No sibling, more than one, a missing seed, or a gone tree each refuse by name. Everything
|
|
3368
|
+
past that resolution — the `--approve <jobSpecHash>` signature, budget/wall ceilings,
|
|
3369
|
+
coldReset, the close-first precheck — is untouched: `--spec` only NAMES the job, exactly as
|
|
3370
|
+
a `--job` row does. `scripts/run-author.mjs`'s "SIGNING PREPARED — NOT SIGNED" screen now
|
|
3371
|
+
prints the ready-to-paste `--spec` command (real path, real `--approve <hash>`, the
|
|
3372
|
+
provider's real env-key per F187) — a person finishing the interview alone can reach a
|
|
3373
|
+
running job with no `jobs/` edit and no JOBS-table row.
|
|
3374
|
+
|
|
3272
3375
|
## Architecture
|
|
3273
3376
|
|
|
3274
3377
|
Three layers. An **outer shell** (dumb, permanent): per-run budget cap via bareguard,
|
|
@@ -3337,6 +3440,18 @@ under it — this is a passthrough, not a jail.
|
|
|
3337
3440
|
denies the gate audit path and every `ARBITER_BOOK_STORES` path under the workdir
|
|
3338
3441
|
(`src/authorscout.js:295`); a plan step's read scope is the workdir alone
|
|
3339
3442
|
(`src/planrun.js:1894`).
|
|
3443
|
+
- **A signed fence can never name `.git` or `node_modules` (F178/F177).** `scopeContained`
|
|
3444
|
+
(`src/validate.js`, design law #1's own function — `validateJob`'s `writeScope` and
|
|
3445
|
+
`validatePlan`'s step `scope`/exit `target`/`path` all go through it) refuses any fence/scope
|
|
3446
|
+
whose normalized prefix has a whole path segment equal to `.git` or `node_modules` — not a
|
|
3447
|
+
look-alike like `.github/**`, `my.git/**`, or `node_modules_util/**`. That alone cannot catch
|
|
3448
|
+
a NESTED `node_modules` reachable through an otherwise-legal fence (a monorepo package scope,
|
|
3449
|
+
or one a worker creates fresh during the run) — bareguard's `fs.deny` is exact
|
|
3450
|
+
prefix-containment only, no glob, so it cannot express "at any depth" even if it were used
|
|
3451
|
+
here. **`fs.deny` is deliberately NOT used for either book**: it blocks ALL fs actions,
|
|
3452
|
+
reads included, and hamr's ruling blocks WRITING only — reading stays allowed. The runtime
|
|
3453
|
+
belt is `tools.denyArgPatterns` (`FORBIDDEN_WRITE_SEGMENT_PATTERN`, `src/planrun.js`): keyed
|
|
3454
|
+
to `write`/`edit` ONLY, matched against the serialized action's `path` field, whole-segment.
|
|
3340
3455
|
|
|
3341
3456
|
**What it does not guarantee:** no network egress control and no container/VM isolation —
|
|
3342
3457
|
this repo has neither built nor documented either. `CLOSE_ENV_DENY`'s deny rule matches
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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",
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
"test": "node scripts/hermetic.mjs && node scripts/prompt-commit-check.mjs --range origin/main..HEAD",
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
25
|
"build:types": "tsc",
|
|
26
|
-
"prepublishOnly": "npm run build:types"
|
|
26
|
+
"prepublishOnly": "npm run build:types",
|
|
27
|
+
"check:lockfile": "node scripts/check-lockfile.mjs"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"bare-agent": "^0.
|
|
30
|
+
"bare-agent": "^0.43.0",
|
|
30
31
|
"bareguard": "^0.13.0",
|
|
31
32
|
"litectx": "^0.32.0"
|
|
32
33
|
},
|