bareloop 0.7.0 → 0.9.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 +704 -0
- package/README.md +13 -4
- package/bareloop.context.md +351 -32
- package/package.json +1 -1
- package/src/authorflow.js +1280 -0
- package/src/authoring.js +1296 -0
- package/src/authorjob.js +793 -0
- package/src/authorscout.js +706 -0
- package/src/bridges.js +2 -2
- package/src/declaredclose.js +519 -0
- package/src/exits.js +8 -2
- package/src/index.js +72 -0
- package/src/job.js +91 -10
- package/src/kinds.js +1354 -0
- package/src/ledger.js +70 -9
- package/src/plan.js +57 -101
- package/src/planrun.js +554 -75
- package/src/ralph.js +27 -5
- package/src/reuse.js +135 -11
- package/src/root.js +8 -1
- package/src/run.js +29 -3
- package/src/text.js +120 -0
- package/src/validate.js +121 -0
- package/src/workbranch.js +261 -0
- package/types/authorflow.d.ts +720 -0
- package/types/authoring.d.ts +447 -0
- package/types/authorjob.d.ts +241 -0
- package/types/authorscout.d.ts +307 -0
- package/types/declaredclose.d.ts +174 -0
- package/types/exits.d.ts +14 -0
- package/types/index.d.ts +9 -1
- package/types/job.d.ts +14 -2
- package/types/kinds.d.ts +343 -0
- package/types/plan.d.ts +41 -38
- package/types/planrun.d.ts +125 -18
- package/types/ralph.d.ts +17 -0
- package/types/reuse.d.ts +5 -1
- package/types/run.d.ts +23 -2
- package/types/text.d.ts +84 -0
- package/types/validate.d.ts +50 -0
- package/types/workbranch.d.ts +102 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,710 @@ 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.9.0] — 2026-08-09
|
|
9
|
+
|
|
10
|
+
The **close-authoring rung**. The close stops being a hand-written script and becomes a
|
|
11
|
+
DECLARATION over kinds bareloop owns: a user answers a six-question interview, an LLM
|
|
12
|
+
composes the declaration by calling a schema-derived tool, three mechanical gates run it
|
|
13
|
+
against the real repository, and a human signs the hash. Nothing here judges a close —
|
|
14
|
+
no LLM validates another LLM's close, at any point (D9). Design record (D1–D13, FROZEN):
|
|
15
|
+
`docs/plans/2026-08-07-close-authoring-design.md`; PRD v1.51–v1.58.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **M1 — `src/kinds.js`, the kind executor: four kinds under three inherited runtime
|
|
20
|
+
contracts.** The four kinds (`command-exit`, `count-not-worse`, `pattern-absent-in-diff`,
|
|
21
|
+
`files-changed`) are the small part; the CONTRACTS every kind inherits are the
|
|
22
|
+
load-bearing part, and they live in the executor where no declaration can weaken them.
|
|
23
|
+
**Instrument-stop** (exit 97, `judged` withheld — a broken instrument is a CASUALTY, never
|
|
24
|
+
a red; and `first`-over-nothing is *unknown* where `sum`-over-nothing is a counted 0).
|
|
25
|
+
**The gap** (`gapKeep` on every line, trims announced and counted). **The changed set**
|
|
26
|
+
(diff plus untracked, minus `.litectx/` by PREFIX and `gate-audit.jsonl` by EXACT path —
|
|
27
|
+
so a worker-authored `src/gate-audit.jsonl` still counts). Graduated from the gate-2 POC
|
|
28
|
+
executor as a REWRITE: async spawn (`spawnSync` freezes the host loop the stall fuse lives
|
|
29
|
+
in, F68), caller-scoped seed worktrees under the OS temp dir rather than a module-global
|
|
30
|
+
cache inside the package, and the arbiter's own `CLOSE_ENV_DENY` imported rather than
|
|
31
|
+
re-spelled. **D12 rides on top: the close stores the COUNTING RULE and never a number** —
|
|
32
|
+
every run measures its own seed, with the route that measured it recorded. 49 integration
|
|
33
|
+
tests over real temp git repos and real child processes.
|
|
34
|
+
- **M2 — the catalogue, the TYPES genre, and the declaration validator: a path is SELECTED
|
|
35
|
+
from the tree, never derived from prose.** One module, because the catalogue, the genre
|
|
36
|
+
and the validator are one contract read from three sides, and a second spelling of any of
|
|
37
|
+
them is the drift class the parser normaliser already exists to prevent.
|
|
38
|
+
- **`KIND_CATALOGUE` IS the whole vocabulary.** The four live kinds are keyed to the
|
|
39
|
+
executor's own `LIVE_KINDS` and asserted equal in BOTH directions. `judged-floor` and
|
|
40
|
+
`human-confirms` are LOCKED menu entries — a distinct `locked-kind` red at validation,
|
|
41
|
+
before any tokens, carrying `verb` + `lib: 'bareloop'` so the demand is counted on
|
|
42
|
+
structured fields and never filed against a bare-suite package (the BA-2 class).
|
|
43
|
+
`harness-loop` (TESTGEN) is **ABSENT** — no entry at all, so declaring it is an
|
|
44
|
+
unknown-kind typo, which is the honest reading of a kind that is out of v1.
|
|
45
|
+
- **The genre template is FROZEN TEXT.** The suite reads addendum 3 out of the prereg and
|
|
46
|
+
compares BYTES, so a paraphrase in source fails the build rather than quietly changing
|
|
47
|
+
what every future close is authored against. The JS 7-pattern and Python 5-pattern
|
|
48
|
+
suppression batteries come from the hand-written closes' own `SUPPRESSIONS` tables —
|
|
49
|
+
operator-owned genre knowledge now. Two residues land as genre DATA, ours to inject and
|
|
50
|
+
never model-filled: guard **SCOPE** (`no-suppressions` ships with NO scope and nothing
|
|
51
|
+
left to fill — the POC's arm A narrowed it to the two target files, which reads stricter
|
|
52
|
+
and is not, because a suppression in a new helper walks straight through; narrowing an
|
|
53
|
+
injected guard is a red, not a taste) and genre **ENV** (`MYPYPATH` is expressed here and
|
|
54
|
+
applied by the flow — it moves no seed number, so no seed-verdict read can ever catch its
|
|
55
|
+
absence, and a declaration that authors it itself reds `genre-owned-env`).
|
|
56
|
+
- **The validator**: schema, kinds, params, F49's static nested-quantifier reject over
|
|
57
|
+
every model-authored regex (the detector itself untouched — monotonic-only), the parser
|
|
58
|
+
through M1's normaliser verbatim, and the **F84 one-population law in both halves** (a
|
|
59
|
+
count over a scoped job must NAME its population; the same cmd+parser+scope declared
|
|
60
|
+
twice is one number wearing two stage names — with the in-scope/outside split, the thing
|
|
61
|
+
the law exists to protect, asserted to stay legal). Plus **hamr's listing rule**: every
|
|
62
|
+
path-like param must SELECT from the seed listing. The POC's arm A derived
|
|
63
|
+
`src/alertEmail.js` from the user's prose and silently reclassified 15 real errors into
|
|
64
|
+
the wrong population; no READING of a declaration catches that, and a prefix match
|
|
65
|
+
against `git ls-tree` does. The red hands back what DOES exist beside the invented path —
|
|
66
|
+
named items, counted, capped (F38/F39), never a paragraph.
|
|
67
|
+
- Three preconditions fail CLOSED, all guarding one failure — *a validator handed nothing
|
|
68
|
+
to check against reports a declaration it never examined*: `listing` and `guards` absent
|
|
69
|
+
are reds, and `envOwned` splits ABSENT from EMPTY (`[]` is a genre that owns no
|
|
70
|
+
variables; `undefined` is a caller that never asked — F59's distinction in the shape it
|
|
71
|
+
bites here). One disagreement the tests caught and the code won: a bare `**` is **NOT a
|
|
72
|
+
root** — `globToPrefix` collapses only the `/**` SUFFIX, so admitting it would put the
|
|
73
|
+
validator and the grader on two spellings of one scope (the F9 red class): green at the
|
|
74
|
+
gate, and a containment guard that reds every file at runtime. It reds. 46 tests TDD,
|
|
75
|
+
watched failing first; 54-mutant battery, ZERO survivors (six holes closed on the first
|
|
76
|
+
pass).
|
|
77
|
+
- **M3 — the declaration is CALLED, not written** (`src/authorscout.js` LOOKS,
|
|
78
|
+
`src/authorflow.js` WRITES; neither ACTS — D11's split). The load-bearing change against
|
|
79
|
+
the gate-2 POC is hamr's schema-forcing ruling: the model answers by calling
|
|
80
|
+
**`declare_close`**, whose input schema IS the declaration shape and is DERIVED from the
|
|
81
|
+
catalogue — kind list, locked-ness, parameter names and every enum read from M1/M2, with
|
|
82
|
+
`schemaCoverage` failing the build loudly if the catalogue ever grows a parameter the
|
|
83
|
+
schema cannot express. A malformed emission is an artifact-red on its own bounded axis; the
|
|
84
|
+
tolerant text parse survives only as a marked fallback for a provider with no tool mode.
|
|
85
|
+
- Two consequences named rather than smoothed: **a locked kind now has no schema branch, so
|
|
86
|
+
declaring one is INEXPRESSIBLE** — which means M2's `locked-kind` demand counter is
|
|
87
|
+
reachable only from the interview layer or the text fallback, and the header says so
|
|
88
|
+
instead of pretending the channel is intact. And the tool is an OUTPUT CHANNEL that takes
|
|
89
|
+
no action, so D3's "no tools at all" is untouched: **hamr's law is about actions, not
|
|
90
|
+
syntax.**
|
|
91
|
+
- **Measured, both arms against the real `Loop`:** stopping the loop inside the tool costs
|
|
92
|
+
1 provider round and $0.001; unwired costs 2 and $0.002 — tool mode does not cost double
|
|
93
|
+
what text mode did.
|
|
94
|
+
- Three defects found by the tests before a run found them: a FLAT directory beat every
|
|
95
|
+
listing tier (200 names render as 200 names at "counts-only"), so the prompt ingredient
|
|
96
|
+
was unbounded — names now give way, counts never do, with a backstop bounding the block
|
|
97
|
+
that beats every tier; an unreadable listing returned no `files` key at all, which
|
|
98
|
+
destructures to `undefined` and reads as falsy-empty (the exact absent-renders-as-empty
|
|
99
|
+
shape this module exists to refuse); and the genre-env injection rewrote stages it had no
|
|
100
|
+
reason to touch, making the signed form differ from what the model wrote for no stated
|
|
101
|
+
reason. **F59 is enforced five ways and refuses at $0** — an ABSENT survey, an EMPTY facts
|
|
102
|
+
object, an unreadable listing, an empty tree and an unbuildable genre variable each stop
|
|
103
|
+
the flow before a token is spent. 1222/1222; 40 of 41 mutants killed.
|
|
104
|
+
- **M4 — the close is a SIGNED FIELD, not a compiled script** (`closeDecl`,
|
|
105
|
+
`src/declaredclose.js`, `src/authorjob.js`). **The integration decision is made in the open
|
|
106
|
+
and named in the source:** a declared close is carried by a new spec field and executed by
|
|
107
|
+
M1's own kind executor. The cheaper alternative — render each declared stage as a shell
|
|
108
|
+
one-liner and let the existing `close[]` path run it — is REJECTED, and the rejection is the
|
|
109
|
+
point: it would turn owned kinds back into authored shell strings, which is exactly what D3
|
|
110
|
+
makes inexpressible. A declaration whose meaning is a generated `sh -c` is a script with
|
|
111
|
+
extra steps, and the arbiter would be judging text nobody validated.
|
|
112
|
+
- **Two executors, ONE contract.** Every behaviour the command path already had is pinned
|
|
113
|
+
by a test on the declared path: first-red-wins with the deciding stage naming itself; the
|
|
114
|
+
forbidden zone routed by a TYPED fault (`STOP_FAULTS`, stamped in M1 at the site that
|
|
115
|
+
OBSERVED it) so a timeout still offers *"raise the close timeout"* and a spawn failure
|
|
116
|
+
still offers *"fix the close"*; the gap header from ONE template (`stageGap`, now exported
|
|
117
|
+
— `src/trend.js` parses that exact line); the scrub at the emission boundary; and the
|
|
118
|
+
check menu still derived from stage names, one hop, one direction.
|
|
119
|
+
- **`close` and `closeDecl` are ALTERNATIVES, refused rather than merged**
|
|
120
|
+
(`close-duplicated`): a spec with two closes has two arbiters, and *picking one silently
|
|
121
|
+
is how a signed artefact stops meaning what the signer read*. The declared close is
|
|
122
|
+
HARD-class in the SAME table the close types use (`CLASS_BY_CLOSE.declared`), so a locked
|
|
123
|
+
verdict on one reds twice — as counted demand AND as `close-hierarchy`.
|
|
124
|
+
- **The gate is SPLIT honestly, not skipped.** A job spec is validated with no repository
|
|
125
|
+
in hand, so the listing rule and the scoped-job derivation that arms the F84 law are
|
|
126
|
+
DEFERRED there — `deferListing: true`, spelled literally, with `grounded: false` on the
|
|
127
|
+
result — and the RUNNER re-runs them against the real seed before any stage and before any
|
|
128
|
+
token. Proven both ways: the invented path `src/alertEmail.js` passes the spec gate and
|
|
129
|
+
dies at the runner, with **zero provider calls**.
|
|
130
|
+
- **D8/D12 ride together.** The seed is HEAD at run start, READ and recorded on a
|
|
131
|
+
`close-decl` spine event, never typed and structurally absent from the signed spec —
|
|
132
|
+
`seedRef` is an unknown-field red on `closeDecl`, because a baseline frozen at signing
|
|
133
|
+
judges run 5 against run 1's tree. `baseline: "seed"` is measured at each run's own start,
|
|
134
|
+
every run.
|
|
135
|
+
- **Two things the bridge adds**, because the declared executor knows more than an exit code
|
|
136
|
+
does: a `gapKeep` derived from one prefix constant, so Layer R's red-set has an instrument
|
|
137
|
+
on this path too; and a TREND VALUE donated **only** for `lower-is-better` — the omission
|
|
138
|
+
is the load-bearing half, since `trend.js` reads improvement as `value < best`, so donating
|
|
139
|
+
a higher-is-better test-count floor would read a DROPPING count as convergence and
|
|
140
|
+
recommend a top-up on a dying run. Null is the honest answer (F6); the stage position still
|
|
141
|
+
travels.
|
|
142
|
+
- **F90, two findings, neither found by a failure.** The design record's one named defect
|
|
143
|
+
(D13's ledger request-red misattribution) was ALREADY FIXED on `main` four days before the
|
|
144
|
+
record was written, so what M4 owed was the PROOF the new emit site routes through it — a
|
|
145
|
+
regression test walking refusal → `refusalEvents` → `classifyIncidents` → `suggestedAsk`,
|
|
146
|
+
asserting the ask reads "bareloop:" and names no bare-suite package, with both halves
|
|
147
|
+
killed as separate mutants (*a frozen document is frozen against rewriting, not against the
|
|
148
|
+
world moving underneath it* — F63 in its cheapest form). And a SECOND gap renderer silently
|
|
149
|
+
blinded Layer R's trim detector: it refuses to compare a red-set from a trimmed window by
|
|
150
|
+
looking for ralph's own marker, and the kind executor announces its trims in different
|
|
151
|
+
words — the direction that MANUFACTURES a fixation reading out of a blind instrument. Fixed
|
|
152
|
+
by exporting the executor's marker and reading both. *The shared-marker guard held
|
|
153
|
+
perfectly and was still defeated, not by drift but by a producer the reader had never heard
|
|
154
|
+
of.*
|
|
155
|
+
- 46 new tests over real temp git patients, real child processes and real `runPlan` (the
|
|
156
|
+
runner seam is the highest-risk part of the module and nothing there is mocked). 1268/1268;
|
|
157
|
+
mutation battery 22/22 after one honest survivor — a test asserting `reds.every(…)` over an
|
|
158
|
+
array the mutant left EMPTY, the conditional-over-a-never-true-fixture trap in its
|
|
159
|
+
`.every([])` form.
|
|
160
|
+
- **The interview re-keys from GENRE to VERDICT CLASS, and the pick is a PROMISE** (PRD v1.57
|
|
161
|
+
§1–§2 — **this SUPERSEDES D4**). `verdictType` is a USER-facing RADIO (`green` | `soft-green`
|
|
162
|
+
| `hitl` — the spelling already shipped in `job.js`; the design brief's `softgreen` would have
|
|
163
|
+
flipped every signed hash). **v1 admits only `green`**; a locked pick returns the counted
|
|
164
|
+
`request-red` refusal BEFORE its questions run.
|
|
165
|
+
- `QUESTION_SETS` replaces `TYPES_QUESTIONS`. The green set is TYPES' **six** questions
|
|
166
|
+
byte-for-byte — they were already genre-neutral, so generalising meant DELETING the D13
|
|
167
|
+
genre-confirm slot, not rewording. Locked sets carry `questions: null`, never `{}` (absent
|
|
168
|
+
is never empty, F59), and `questionsFor` THROWS on them: reaching questions means admission
|
|
169
|
+
failed to refuse. Genre understanding moves into `CLASS_STATEMENTS` fed to the composer, and
|
|
170
|
+
the genre refusal moves to the composer on the same counted path.
|
|
171
|
+
- **The guard battery re-homes to the class** (hamr: *"battery ties with job type"*).
|
|
172
|
+
`CLASS_BATTERIES` keys attachment — which guards, and their un-removability — off the class;
|
|
173
|
+
the tool-specific contents fill at COMPOSITION from the language the declaration names. **An
|
|
174
|
+
empty battery is IMPOSSIBLE, enforced not asserted**: unknown class, locked class, unknown
|
|
175
|
+
language, empty fill and zero guards all THROW.
|
|
176
|
+
- **Class-vs-ceiling, ruled *"yes to both, go"*:** every catalogue kind carries its
|
|
177
|
+
`verdictClass`, and a declaration whose ceiling EXCEEDS the pick is an honest
|
|
178
|
+
`class-ceiling` red naming the kind — never a silent upgrade or downgrade. Inert in v1 by
|
|
179
|
+
construction, and proven FIRING against an injected soft-green kind.
|
|
180
|
+
- Driver: **`--verdict` is REQUIRED** — a defaulted radio would answer the user's question for
|
|
181
|
+
them; `answers.json` drops key 7. Live smoke: `--verdict hitl` refuses at $0
|
|
182
|
+
decision-ready, `--verdict green` runs to the provider boundary. 14/14 mutants killed; the
|
|
183
|
+
saved PREPARED python spec still validates post-rework. 1354/1354.
|
|
184
|
+
- **The scout gets 3 typed attempts, and every raw lands in the books** (PRD v1.58). hamr's
|
|
185
|
+
rulings in the register, verbatim: *"retry on scout too, tighten-only same as revisions"*;
|
|
186
|
+
*"depending on causes, we can hardcode 3 attempts, but if reasons are beyond malformed then we
|
|
187
|
+
may verge into self-healing … i don't think we should"*; *"for audit, it should come on top of
|
|
188
|
+
existing audit trails … scrub for common things in general"*.
|
|
189
|
+
- `SCOUT_ATTEMPTS = 3`, hardcoded; the `attempts` parameter is TIGHTEN-ONLY and CLAMPS rather
|
|
190
|
+
than throws (floor **1** — a scout that never ran is an ABSENT nobody can act on). The gate
|
|
191
|
+
reads TYPED causes stamped at the detection site and **only the malformed class retries**
|
|
192
|
+
(`empty` | `unparseable`). What is excluded is excluded for a reason: `call-failed` covers
|
|
193
|
+
transport and `truncated:max_tokens` (provider-red, no redraft), `short` is F59's cut-off
|
|
194
|
+
population and already has its own instrument INSIDE the attempt, and `non-object` /
|
|
195
|
+
`empty-object` are valid JSON with vacuous content — a SEMANTIC failure, which F38/F39
|
|
196
|
+
measured as the same distribution sampled twice. **That is the self-healing line, and it is
|
|
197
|
+
not crossed.** The re-ask is TOOLLESS over the survey's own conversation, naming the
|
|
198
|
+
mechanical parse error and position and nothing else — the repository was already read, only
|
|
199
|
+
the emission was unreadable. **There is no JSON repair behind it and there never will be**: a
|
|
200
|
+
repairer decides what the model MEANT and writes it down as though the model had said it, in
|
|
201
|
+
the one artefact whose whole job is to be honest about what a repository contains.
|
|
202
|
+
- **`raws` — what every model call actually SAID**, the scout's attempts absorbed alongside the
|
|
203
|
+
declaration's, in the cost book's own order and under the same labels it meters. `scrubRaw` /
|
|
204
|
+
`RAW_PERSIST_MAX` (8000) / `RAW_TRIM_MARKER` join `src/text.js` as the third one-spelling
|
|
205
|
+
model-output helper — a NEW trim spelling deliberately, because `GAP_TRIM_MARKER` has a
|
|
206
|
+
reader with a different meaning (F90). One recorder writes the cost entry and the raw
|
|
207
|
+
together (labels pinned pairwise by test), the cut is walked back off a continuation byte so
|
|
208
|
+
a multi-byte character is never split, and **raws are present on the `$0` preflight refusal
|
|
209
|
+
paths too** — the path that spends nothing more is exactly the one whose evidence used to die
|
|
210
|
+
with the process. `iterations` records what a turn MEANT; `raws` records what it SAID, and a
|
|
211
|
+
malformation is only ever visible in the second. Attribution fix found mid-build: the
|
|
212
|
+
classified blob is tracked BY INDEX, not `raws.at(-1)` — an F59 recovery discard had
|
|
213
|
+
mis-stamped the verdict. E2E replay of `mslhn707`'s real failure shape: 3 labelled calls,
|
|
214
|
+
raws carrying causes and attempt indexes, reason *"— after 3 attempts"*, and a planted
|
|
215
|
+
key-shaped secret scrubbed to 0 across the serialized artifact. 1405/1405.
|
|
216
|
+
- **The WORK BRANCH is a hard rule, enforced STRUCTURALLY** (PRD v1.57 §3; `src/workbranch.js`).
|
|
217
|
+
hamr's ruling, built as ruled: *"The agent creates a NEW BRANCH before it touches any code — a
|
|
218
|
+
HARD RULE, no exceptions… Named with a meaningful slug."*
|
|
219
|
+
- **Structure over ordering:** `mkWorker` — the single seam that grants write-class verbs —
|
|
220
|
+
THROWS a categorised interpreter-red if asked for a writable worker with no branch prepared.
|
|
221
|
+
That covers both write sites and any future third, which an ordering rule cannot. The seam
|
|
222
|
+
sits at planrun step 0c: after the $0 precheck/preflight (an already-green tree leaves no
|
|
223
|
+
branch behind), before the scout (every branch fault is an instrument stop costing zero
|
|
224
|
+
tokens).
|
|
225
|
+
- The slug is `bareloop-` + the SIGNED spec's `job` field, kebab-validated and INSIDE the spec
|
|
226
|
+
hash, so the name cannot drift from what was signed (goal prose was rejected as a source, and
|
|
227
|
+
the rejection is documented). `WORK_BRANCH_RE`'s alphabet makes `..`, `@{`, `.lock`, a leading
|
|
228
|
+
`-`, and **`main`/`master` inexpressible**; `prepareWorkBranch` re-validates even a
|
|
229
|
+
hand-passed name. Collisions take `-2`/`-3` suffixes (one blast radius per try). **Resume
|
|
230
|
+
returns to ITS OWN recorded branch** (a `work-branch` spine record; `readResume`'s
|
|
231
|
+
`restart.branch`), and a recorded branch that is GONE is a STOP, never a fresh start — the
|
|
232
|
+
surviving mutant taught the assertion to demand the arbiter's own wording. **`branch-red` is
|
|
233
|
+
a distinct terminal, excluded from ledger classification** (an interpreter-red would aim an
|
|
234
|
+
upstream ask at a library for a patient nobody prepared). Adjacent fix: `relay` now passes
|
|
235
|
+
the category through instead of filing wiring faults as transport casualties.
|
|
236
|
+
- **Consequences recorded in `bareloop.context.md`, hamr-agreed:** the patient must be a git
|
|
237
|
+
repository with at least one commit, AND is always a separate COPY of the treated repo, never
|
|
238
|
+
the original — *"keep patients copies separate from original, always"*; the copy is the blast
|
|
239
|
+
radius. A detached HEAD attaches (`from: null`); a subdirectory workdir branches in the
|
|
240
|
+
enclosing repo (pre-existing semantics, now on the record). 18 new tests against real tmp git
|
|
241
|
+
repos plus guard/resume coverage across `planrun`/`run`/`reuse`, watched failing first; 2
|
|
242
|
+
sabotages and a mutation pass. 1387/1387.
|
|
243
|
+
- **`scripts/run-author.mjs` — the authoring driver, now tracked**, and the paid proofs it
|
|
244
|
+
bought. **pulselog (JS) reached SIGNING PREPARED twice** ($0.25 / $0.22), with the trial gate at
|
|
245
|
+
32s and a **byte-identical spec hash across cold runs**. **aurora (python) refused honestly
|
|
246
|
+
twice**, and both refusals were the product working: run 1 a real uncrafted `mypy` fatal
|
|
247
|
+
(symlink `MYPYPATH`), run 2 the scope filter dropping error lines reported through a symlink
|
|
248
|
+
spelling — where the crash-stop correctly refused a 0-count instead of minting a fake green on a
|
|
249
|
+
tree carrying 16 strict errors. The fix then converted that refusal **for $0 on the same saved
|
|
250
|
+
declaration**: SIGNING PREPARED, all three gates green, `typecheck-spawner` honest RED at seed
|
|
251
|
+
(16 vs 0), `specHash` byte-identical to the failed run's own hash. Second sitting, $0.59 (F93):
|
|
252
|
+
`mslsnnzk` $0.345 diagnosing, `mslwbkz7` $0.245 validating the fix live — same composed shape,
|
|
253
|
+
GREEN `value=0` with the dropped-count note, three gates PASS, SIGNING PREPARED on the reworked
|
|
254
|
+
code. F91 / F92 / F93.
|
|
255
|
+
|
|
256
|
+
### Changed
|
|
257
|
+
|
|
258
|
+
- **`maxRevisions` joins every other cap as TIGHTEN-ONLY.** The bound predated the law:
|
|
259
|
+
`maxRevisions: 50` bought fifty revise rounds, and unlimited revising is exactly the
|
|
260
|
+
thrash-as-adaptation the prereg forbids. The clamp lives at the ONE seam inside `authorClose`,
|
|
261
|
+
so it is not a rule any caller can be trusted to keep — the loop reads a DERIVED cap and the
|
|
262
|
+
parameter never reaches it. The floor is **0** (author once, no revise round — already a legal
|
|
263
|
+
loop shape) rather than the scout's floor of 1, and a non-finite or negative value lands on the
|
|
264
|
+
floor instead of a bound the loop never enters.
|
|
265
|
+
- **`structureRetries` becomes tighten-only** under the constant that already claimed to be its
|
|
266
|
+
ceiling, and **`effectiveTimeoutMs` with it** — the same direction every cap in this system runs
|
|
267
|
+
in.
|
|
268
|
+
- **Absolute paths join the command deny-floor, rejection-only.** `checkKind` gained a command
|
|
269
|
+
deny-floor plus an absolute-path/traversal refusal during the review rounds; drive-absolute
|
|
270
|
+
spellings (`C:\…`) were the gap left in it. Monotonic: the floor only ever adds rejections.
|
|
271
|
+
- **The ten hand-written close scripts import `JUDGED_MARKER` from `src/kinds.js`** instead of
|
|
272
|
+
respelling `judged=1` as a literal (hamr-approved wiring). Output proven byte-identical by
|
|
273
|
+
EXECUTION on all ten; no signed spec hash flips (`jobSpecHash` covers the resolved spec object,
|
|
274
|
+
not script bytes). **Known new coupling, recorded not hidden:** close scripts now import the
|
|
275
|
+
`src/` graph, which matters if they ever ship standalone.
|
|
276
|
+
- **A GREEN declared close now reports what its stages announced** (`notes`). `translate()`
|
|
277
|
+
computed a stage's gap text and threw it away on GREEN and on INSTRUMENT-STOP — but M1's kinds
|
|
278
|
+
announce some things whichever way a stage lands (a declared env var that was dropped, the scope
|
|
279
|
+
filter's own arithmetic), so the bridge was dropping the **check-both-greens** audit trail on
|
|
280
|
+
the floor. Surfaced as `notes`, absent when empty, per stage as well as on the summary — and
|
|
281
|
+
deliberately NOT `.gap`, because every consumer downstream guards with `if (gap)`, so a green
|
|
282
|
+
carrying one would read as revision-worthy and a stop carrying one would look like a verdict it
|
|
283
|
+
never rendered. Verified inert: every spine reader keys on `verdict`/`gap`/`stage`, nothing
|
|
284
|
+
routes or bounds on this field, and **no verdict moves.**
|
|
285
|
+
- **`assembleSpec` REFUSES a draft that already carries the authored half** instead of clobbering
|
|
286
|
+
it. It used to drop a draft's `close` on the floor and stamp its own `verdictType` over the
|
|
287
|
+
operator's, silently — the case `src/job.js` already rules on in its own words (*"picking one
|
|
288
|
+
silently is how the signed artefact stops meaning what the signer read"*), one step earlier. Now
|
|
289
|
+
a named refusal over `AUTHORED_SPEC_FIELDS` (`close`, `closeDecl`, `verdictType` — the fields
|
|
290
|
+
the fold WRITES, named as data so the refusal and the fold can never disagree about which half
|
|
291
|
+
of the spec is which), and `run-author` asks the same question at **$0 BEFORE the scout**, so
|
|
292
|
+
the answer never arrives after the model has been paid.
|
|
293
|
+
- **`closeStagesOf(job)` is the ONE staging every close consumer reads**, widened to both fields
|
|
294
|
+
— a consumer reading `close` alone sees a declared job as CLOSELESS and offers the drafter an
|
|
295
|
+
empty check menu. `scripts/consolidate-bridges.mjs` was still calling `stageClose(spec.close)`
|
|
296
|
+
and refusing declared-close specs for the one reason untrue of them; migrated (a spec with
|
|
297
|
+
neither field still dies honestly).
|
|
298
|
+
|
|
299
|
+
### Fixed
|
|
300
|
+
|
|
301
|
+
- **F93 — the broken-ruler guard read match counts AFTER the scope filter, so a LIVE tool over an
|
|
302
|
+
empty population read as a DEAD one** (`src/kinds.js`). `count-not-worse`'s crash guard summed
|
|
303
|
+
`breakdown[].matches`, which `parseValue` tallies after the scope filter has already dropped the
|
|
304
|
+
out-of-population lines. **Two entirely different worlds collapsed into that one number**: a
|
|
305
|
+
tool that crashed before it looked at the tree, and a tool that RAN and printed parseable
|
|
306
|
+
findings which all fell outside this stage's scope. Run `mslsnnzk` paid for it: `npm run
|
|
307
|
+
typecheck -- --strict` exited 2 and printed **67 real `error TS\d+:` lines**, every one under
|
|
308
|
+
`src/`, against a stage scoped `excludePrefixes: ["src/"]` — post-scope the count was 0, the
|
|
309
|
+
stage stopped as a crashed instrument, and **a good close was refused at gate 2**.
|
|
310
|
+
- The same two-faults-read-as-one class the `first` aggregate's own stop had already SPLIT in
|
|
311
|
+
its own comment; the `sum` path carried the unfixed twin. `parseValue` now reports
|
|
312
|
+
`preScopeMatches`, tallied where LIVENESS lives — before the filter has an opinion, kept +
|
|
313
|
+
unattributable + dropped alike — and the guard consults that. A genuinely silent non-zero exit
|
|
314
|
+
still matched nothing pre-scope and still stops: **the fail-safe is untouched in the direction
|
|
315
|
+
it was chosen for**, and the accepted limit for `grep -c` / pytest-5 stands unchanged. One
|
|
316
|
+
`parseValue` call site (`measure`) serves both the current tree and the seed worktree, so the
|
|
317
|
+
baseline-side measurement gets the same reading by CONSTRUCTION; `command-exit` and
|
|
318
|
+
`pattern-absent-in-diff` never touch it.
|
|
319
|
+
- The stop lane also stops throwing away its own diagnostics: `stopped()` takes the notes a
|
|
320
|
+
measurement had already computed and renders them under the stop, so a `first` term whose every
|
|
321
|
+
match was scope-excluded sends the reader to the SCOPE instead of hunting a parser bug.
|
|
322
|
+
- **Validated live, not asserted:** `mslwbkz7` composed the same shape on the post-fix code and
|
|
323
|
+
walked to SIGNING PREPARED — GREEN `value=0` with the dropped-count note, three gates PASS.
|
|
324
|
+
F92's shape-lottery framing is **refuted in part**: the model composed the same correct
|
|
325
|
+
inside/outside split 3/3 (systematic, not a roll); what varied was the AGGREGATE, and only one
|
|
326
|
+
of the two resulting stops was a defect. The attribution was corrected the same night off the
|
|
327
|
+
artifacts — the crash guard fired **once**, on `mslsnnzk`; `mslhpw2v`'s stop was the
|
|
328
|
+
`first`-path fail-safe working as designed. **The parked re-compose-on-refusal lever survives
|
|
329
|
+
as a build and loses this run as its justification:** a retry loop cannot heal a mislabeled
|
|
330
|
+
instrument.
|
|
331
|
+
- **The scope filter and the FENCE compare FILES, not spellings.** The live python rerun refused at
|
|
332
|
+
the seed gates because `mypy` under `MYPYPATH=src` reports every error through aurora's tracked
|
|
333
|
+
symlink spelling (`src/aurora_spawner/…`) while the declared scope held the physical spelling
|
|
334
|
+
(`packages/spawner/src/…`) — the lexical filter dropped all 16 real error lines, and 0 matches +
|
|
335
|
+
exit 1 tripped the crash-stop. **The fail-safe did its job on first live contact:** without it, a
|
|
336
|
+
COUNTED ZERO against baseline 0 would have minted a fake green on a tree carrying 16 strict
|
|
337
|
+
errors. Fixed by resolving BOTH sides to physical identity via `realpathSync` **on a lexical miss
|
|
338
|
+
only**, per-call cache; unresolvable paths fall back to lexical (fail-open killed by sabotage AND
|
|
339
|
+
by a pre-existing shipped test), and a symlink resolving OUTSIDE every prefix stays excluded.
|
|
340
|
+
- Then hamr: *"fence fix approved, wire both."* The two remaining lexical sites get the same
|
|
341
|
+
mechanism through one shared `underPrefixEither` (lexical first, physical only on a miss; a
|
|
342
|
+
null physical resolution is the old fence byte for byte): `runFilesChanged`'s `allowPrefixes`
|
|
343
|
+
containment and `runPatternAbsentInDiff`'s scope. **The gap was REAL, not hygiene** — a
|
|
344
|
+
watched-failing test proved the `no-suppressions` guard (the F87 antidote) scanned NOTHING and
|
|
345
|
+
read GREEN over a real `@ts-ignore` when the scope was declared through a symlink spelling. A
|
|
346
|
+
fake-green lane, now shut; the widening direction (spelled inside, physically OUTSIDE) stays
|
|
347
|
+
shut, killed by sabotage plus three shipped tests. `globToPrefix`, the `..`-prohibition and the
|
|
348
|
+
crash-stop are untouched. **Accepted limit documented in-code:** a DELETED file inside a
|
|
349
|
+
symlinked package has no realpath and falls back to lexical — a possible false red on
|
|
350
|
+
aurora-style deletions, which is the fail-safe direction, left until a live run trips it.
|
|
351
|
+
- **On a RESUME the work branch is prepared BEFORE the $0 instruments** (`src/planrun.js`). Steps
|
|
352
|
+
0a (close precheck) and 0b (check preflight) are readings OF A TREE, and on a resume the tree
|
|
353
|
+
that matters is the recorded work branch — it holds the work already paid for and IS the run being
|
|
354
|
+
continued — but the seam that puts the run on it sat at 0c, below both. Standing on the handed
|
|
355
|
+
ref, the precheck could read `already-green` off a tree that is not this run's, and every
|
|
356
|
+
`baseline: "seed"` stage would baseline against it: **an instrument reading the wrong subject,
|
|
357
|
+
reading honestly and saying nothing true.** One ordering split, one seam — `prepareBranch` is
|
|
358
|
+
defined ONCE (the branch-red refusal is written once, so two call sites cannot drift into two
|
|
359
|
+
messages) and called before 0* on a resume, at 0c otherwise. The cold ruling is untouched: an
|
|
360
|
+
already-green COLD tree still returns above 0c and leaves no branch behind, because that clause
|
|
361
|
+
is about MINTING and the resume arm creates nothing (`created: false`). A resume whose branch is
|
|
362
|
+
gone or foreign now stops `branch-red` before the arbiter grades anything at all — earlier, free,
|
|
363
|
+
deterministic. (Reported by `/code-review` and deliberately NOT fixed there: the ordering is ruled
|
|
364
|
+
territory, parked for hamr's word and landed on it.)
|
|
365
|
+
- **The persisted-copy scrub class closes its 3rd, 4th and 5th instances** — a string reaching a
|
|
366
|
+
record that OUTLIVES the run by a path the single redaction boundary does not cover.
|
|
367
|
+
- **3rd, both declaration gates:** validator reds were persisted VERBATIM — and gate 1b quotes
|
|
368
|
+
the SEED LISTING via the did-you-mean helper, so a secret-shaped FILENAME in the patient tree
|
|
369
|
+
reaches the persisted red through a channel the spec sweep structurally cannot see (measured:
|
|
370
|
+
spec clean, red leaks). Fixed at both gates with one shared `scrubRed` over every string field
|
|
371
|
+
— a detail-only fix was sabotage-proven INSUFFICIENT, because the structured `cmd` field ships
|
|
372
|
+
verbatim — plus symmetry wraps on the provider-derived channels.
|
|
373
|
+
- **4th, `src/authorscout.js`:** `record()` scrubs a raw at CREATION, when the verdict is not yet
|
|
374
|
+
known, and the verdict was ASSIGNED onto the stored object afterwards. `reason` is the worst
|
|
375
|
+
field to do that to — `classifySurvey`'s `CALL_FAILED` route quotes the transport's own error
|
|
376
|
+
prose verbatim, a string nobody here wrote and nobody bounds — and the raws land in
|
|
377
|
+
`authored.json` and on the spine. **The scrub had become a property of WHEN you knew the
|
|
378
|
+
verdict.** Fixed by making the stamp a FUNCTION rather than an assignment (`stampRaw` in
|
|
379
|
+
`src/text.js`, which `scrubRaw` renders its own two fields through): one place decides how a
|
|
380
|
+
diagnosis becomes persistable, exactly as one place already decided it for the text. `cause` is
|
|
381
|
+
enumerated and passes through untouched — masking a closed set would only hide a bug in the set.
|
|
382
|
+
- **5th, `src/authorjob.js`:** `notes` is the one FREE-TEXT field a model writes straight into
|
|
383
|
+
the SIGNED `closeDecl`, and it was the only string on that boundary not riding `redactSecrets`
|
|
384
|
+
while every sibling does. No live leak today (inputs are pre-scrubbed upstream and
|
|
385
|
+
`sweepSecretLiterals` reds a secret-shaped note on the way to signing), so this is the
|
|
386
|
+
defense-in-depth half of the class. Hashes are untouched: `redactSecrets` returns a
|
|
387
|
+
non-matching string byte-identical, and no fixture or signed spec carries a secret-shaped note.
|
|
388
|
+
- Every regression watched failing first with the RAW TOKEN in the actual, then watched passing,
|
|
389
|
+
then each fix sabotaged in turn and its test watched red again; assertions demand
|
|
390
|
+
`scanSecrets(JSON.stringify(…)) === []` **plus the mask and the surviving prose around it** —
|
|
391
|
+
masked, never deleted.
|
|
392
|
+
- **The whole-branch review's fifteen, in three shrinking rounds** (F91). Round 1: the genre-env
|
|
393
|
+
envelope survives re-validation; the command deny-floor lands in `checkKind`; `cleanup()` never
|
|
394
|
+
throws; the crashed counter (nonzero exit + 0 matched lines) routes to an instrument stop with
|
|
395
|
+
seed baselines included; `effectiveTimeoutMs` becomes tighten-only; plus a `renderSeedReadBlock`
|
|
396
|
+
scrub (a two-headed leak) and a tautological Layer-R trim test rewritten to be ABLE to fail.
|
|
397
|
+
Round 2, a fix-diff review OF round 1: `renderRejectBlock`'s scrub (a second leak channel of the
|
|
398
|
+
same class, found by reviewing the first fix's diff), genre-env checked at the GROUNDED gates,
|
|
399
|
+
`envCapableKind` consolidated so injector and validator cannot drift, and the crash-stop's
|
|
400
|
+
`grep -c` / pytest exit-5 blind spot documented as an accepted fail-safe limit. Round 3, a
|
|
401
|
+
validation sweep: guard-name-keyed `scopeOfJob` + `AT_MOST_ONCE_KINDS`, `resolveSourcePrefixes`
|
|
402
|
+
realpath-dedupe (measured live on aurora), the notes divergence closed, `parseValue`'s first
|
|
403
|
+
aggregate keeping its notes, scout recovery preferring `s2.error`, and `addedLines`' stat-throw
|
|
404
|
+
announcing a note. 19 new tests sabotage-proven; mutation micro-batch 5/5 killed.
|
|
405
|
+
- **`/code-review medium`'s five.** Validation reds are scrubbed WHERE THEY ENTER THE RECORDS — the
|
|
406
|
+
model-facing copy was masked and the persisted copy was not. The diff scanner's header skip is
|
|
407
|
+
scoped to the PREAMBLE, so an added `++` line can no longer hide a suppression from the guard.
|
|
408
|
+
Worktree cleanup READS git's result instead of assuming it. Plus the two caps above. Each fix
|
|
409
|
+
carries a regression test proven failing against the pre-fix code; the suite was independently
|
|
410
|
+
re-run rather than taken from the reviewer's own gate.
|
|
411
|
+
- **`/diff-review`'s ten land as nine fixed and one DROPPED with its reason.** Beyond the four in
|
|
412
|
+
*Changed* above: the dead `d.code !== 0` clause (git's `ok` IS `code === 0`; a spawn fault carries
|
|
413
|
+
`code: null`); one test running the same declaration through BOTH first-red-wins loops, since the
|
|
414
|
+
deliberate duplication had nothing pinning it; the collision walk's check-then-act NAMED rather
|
|
415
|
+
than closed, because `git checkout -b` already refuses an existing branch (measured, exit 128) so
|
|
416
|
+
the race ends in the stop below it; `scoutReaskTurn`'s rendered text; and `buildSeedListing`
|
|
417
|
+
rendering the `full` tier TWICE on every run that never needed to degrade. One finding **REFUTED
|
|
418
|
+
as a bug and kept as a fail-safe**: the retry ladder's re-survey arm is right — a toolless re-ask
|
|
419
|
+
rests entirely on *"the model already read the tree"*, and an empty transcript is that premise
|
|
420
|
+
gone — the HEADER was the thing that lied, so the header tells the truth and the branch gets the
|
|
421
|
+
test its always-non-empty helper had prevented. **DROPPED (S3):** every `process.exit(2)` in
|
|
422
|
+
`run-author` fires in the argument-parsing preamble, before the script's first stdout write, so
|
|
423
|
+
there is no queued output for F71 to discard — and `die` is used in expression position
|
|
424
|
+
(`return die(…)`), where `exitCode`+return would let a fatal unreadable-JSON CONTINUE into the
|
|
425
|
+
run; the post-output paths already use `process.exitCode`.
|
|
426
|
+
|
|
427
|
+
Suite **1422/1422** on a single clean run; `typecheck` and `build:types` exit 0.
|
|
428
|
+
|
|
429
|
+
## [0.8.0] — 2026-08-07
|
|
430
|
+
|
|
431
|
+
### Changed
|
|
432
|
+
- **The variance meter REPORTS progress — and still decides nothing on it** (F85, A+B;
|
|
433
|
+
`src/planrun.js`). The meter stopped a step that was CONVERGING and then told the redrafting
|
|
434
|
+
planner that step's exits were *"unmoved"*. Two instruments over one run, and only one of them
|
|
435
|
+
right. Live on `u-msh70zla`: the close gaps went 30 → 24 → 15 → 14 with the ladder recording
|
|
436
|
+
`distinctGaps 3` / `strikes 0`, while the replan brief said nothing had moved and the materials
|
|
437
|
+
said *"0 step(s) completed"* — so the replanner, told it had achieved nothing, rebuilt a file
|
|
438
|
+
that was already at zero errors, wrote nothing twice, and the run died with $1.09 and 7 minutes
|
|
439
|
+
unspent. hamr's ruling, verbatim: *"meter is right but missing a piece … it should give heads up
|
|
440
|
+
on money/time + progress for llm to judge."*
|
|
441
|
+
- **The firing condition is byte-identical** — `moneyShare`/`timeShare >= 0.5`, both axes, the
|
|
442
|
+
same threshold, the same throw. A progress term in the TRIGGER would make a governance
|
|
443
|
+
instrument over an operator-owned allowance judge capability, so a converging step that is
|
|
444
|
+
eating the run is still stopped. What was missing was never a term in the condition; it was
|
|
445
|
+
the READING.
|
|
446
|
+
- **The reading is read off `src/trend.js` — the SAME instance the money halt reads.** Nothing
|
|
447
|
+
new is computed at the meter: no second reader, because two instruments answering one question
|
|
448
|
+
is the exact defect being fixed. Per-stage, stage names and numbers only, and `unknown` stays
|
|
449
|
+
`unknown` — a close that reports no number donates nothing (F6).
|
|
450
|
+
- **The hardcoded `with its exits unmoved` string is DELETED.** It printed on every step-variance
|
|
451
|
+
stop whatever had happened, and on `u-msh70zla` it was simply false. The escalation detail —
|
|
452
|
+
which a human reads and which the replan brief quotes — now states the meter's fact (*a share of
|
|
453
|
+
the run was consumed*) and then the trend instrument's measured reading, as two separate claims
|
|
454
|
+
rather than one fused verdict on the work. The materials `progress` line keeps its structural
|
|
455
|
+
sentence (*"step N of M did not finish; K step(s) completed before it"* — a plan's shape is what
|
|
456
|
+
the planner re-allocates) and gains the close trend beside it.
|
|
457
|
+
- **Spine change:** the `variance` record gains `trend` / `motion` / `reading` / `series` — the
|
|
458
|
+
same four names and the same shapes the money halt already emits, so one reader parses a
|
|
459
|
+
progress reading wherever it appears. ADDED fields, never repurposed ones (the spine is
|
|
460
|
+
append-only forever). Measured post-fix on `u-mshcpdg4`, iteration 3 on `finish-strict-typecheck`:
|
|
461
|
+
`moneyShare 0.497`, `timeShare 0.769`, `axis time`, `trend "converging"`,
|
|
462
|
+
`reading "still progressing — typecheck 30 → 15 → 15 → 1"`,
|
|
463
|
+
`series [{stage:"typecheck",values:[30,15,15,1]}]` — against `u-msh70zla`'s pre-fix record, which
|
|
464
|
+
carried the shares and nothing else.
|
|
465
|
+
- **`runTrend` was blind to everything a STEP achieves** (F85, the prerequisite found en route). The
|
|
466
|
+
run's trend reader was fed only the close PRECHECK and the outer fix loop, so a meter firing
|
|
467
|
+
mid-step had nothing to report about a step that had just gone 24 → 15 → 14. Two folds corrected:
|
|
468
|
+
a step's failing check is an arbiter-rendered close grade (same `runStages`, same chain, same stage
|
|
469
|
+
names) and is now recorded like one; and it is recorded on the RAW gap, because `evalExits` wraps
|
|
470
|
+
stage output as `check "<name>" red: …` and that wrapper line carries the word "red" with no number
|
|
471
|
+
on it — so every step grade used to donate a null. Measured on `u-msh70zla`'s own archived gaps:
|
|
472
|
+
raw reads `typecheck 24 → 15 → 14`, wrapped reads nothing at all. Plus a **preflight seed** so the
|
|
473
|
+
counter has a baseline: the precheck reds at the close's FIRST failing stage (first-red-wins) and
|
|
474
|
+
every shipped close opens with `changed-from-seed`, so the numeric stages had no baseline at all and
|
|
475
|
+
a run would read `unknown` on exactly the stop the readout exists for. Seeded ONCE PER STAGE, off the
|
|
476
|
+
reader's own books — the precheck and the preflight menu grade the same unchanged tree back to back,
|
|
477
|
+
and a repeated baseline in a series reads as an attempt that achieved nothing, which is the phantom
|
|
478
|
+
flat step this change exists to remove.
|
|
479
|
+
- **`tests/resume-u.test.js` is decoupled from the live job spec.** It read
|
|
480
|
+
`jobs/bareagent-u-types.json` while hardcoding $8 / 45min, so an operator budget edit on a live spec
|
|
481
|
+
turned the suite red. Every number now derives from the spec it reads, proven by passing at a third
|
|
482
|
+
budget value. Suite: **1023 tests** (1027 minus the 4 deleted with the `never wrote` feature below);
|
|
483
|
+
typecheck and `build:types` exit 0.
|
|
484
|
+
- **The per-step model tier menu narrows to `['sonnet']`** (`STEP_MODELS`, `src/plan.js` — a PUBLIC
|
|
485
|
+
surface: a plan declaring `model: "haiku"` is now REDDED by `validatePlan` with `invalid-value` at
|
|
486
|
+
`steps.N.model`, carrying the menu). hamr's order, verbatim: *"haiku should be dropped to see if it
|
|
487
|
+
passes, if it's agent poor work or harness, like always."* **This is a reversible ATTRIBUTION PROBE,
|
|
488
|
+
not a finding that haiku is incapable** — what the $0 archive read behind it shows is a CONFOUND, in
|
|
489
|
+
both directions. Over 186 spines / 71 accepted plans / 255 steps, only **20 steps ever declared a tier
|
|
490
|
+
at all**: haiku **12** — 9 of them on a REPLANNED plan, 6 the LAST step of their plan, round bounds
|
|
491
|
+
3–12, **2 ever green** — against explicitly-declared sonnet **8**, 2 on a replan, 3 last, bounds 14–32,
|
|
492
|
+
**5 green**. The planner reaches for haiku on steps it judges mechanical, and a replanned step sits on
|
|
493
|
+
a run already in trouble, so neither column is a controlled read; and all three bareagent-u runs of
|
|
494
|
+
2026-08-06 tiered their replanned step down (haiku/6, haiku/8, haiku/12) and all three failed. That
|
|
495
|
+
confound sat directly on top of the programme's most recent capability read (F86's tail). Removing the
|
|
496
|
+
tier is how it gets attributed: with one tier expressible, a still-failing replan is agent or harness,
|
|
497
|
+
never the tier.
|
|
498
|
+
- **The `model` field and its validator branch STAY.** A one-entry menu means a plan — or a stored
|
|
499
|
+
bridge — declaring `model: "sonnet"` still validates, and restoring haiku is a one-token edit. No
|
|
500
|
+
stored bridge declares haiku (verified).
|
|
501
|
+
- **The drafter prompt's `model` line is DELETED** rather than reduced to a one-item menu: it
|
|
502
|
+
advertised *"a cheaper tier for mechanical steps"* and there is no cheaper tier now, and reciting a
|
|
503
|
+
single legal value spends tokens to invite a no-op field (`attempts` is the existing
|
|
504
|
+
legal-but-not-offered precedent). Verified by RENDERING the real prompt — 0 lines match
|
|
505
|
+
`/model|tier|haiku|sonnet|cheaper/i` in either the fresh or the replan form.
|
|
506
|
+
- **Only what the AGENT may express narrows**, which is the safe direction. `--model haiku` remains
|
|
507
|
+
the OPERATOR's probe knob (PRD v1.36), untouched in `scripts/run-u.mjs`; two comments there called
|
|
508
|
+
the runner's tier map *"the same closed menu the planner uses"* and now say plainly that it is
|
|
509
|
+
deliberately wider.
|
|
510
|
+
- **No spec hash moved.** `jobSpecHash` covers the resolved spec, not `STEP_MODELS`; all 10 job specs
|
|
511
|
+
were re-hashed and none moved — bareagent-u and bareguard-u are still the hashes hamr signed.
|
|
512
|
+
- Measured on the first sonnet-only run against the last haiku-era one, same patient: **10.9 → 17.5
|
|
513
|
+
seconds per round** (1.6× slower), 120 → 87 rounds, and the failure CLASS changed — struck out at 2
|
|
514
|
+
remaining errors with money and clock left, versus still converging at 1 error when the wall
|
|
515
|
+
expired. Better rounds are slower rounds, and the binding constraint moved from the strike ladder to
|
|
516
|
+
the clock. That sonnet-only run was also the first to reach the outer close, where it was redded for
|
|
517
|
+
**silencing the type checker instead of typing the code** (F87's suppression genre) — the close
|
|
518
|
+
refused it, and the eventual green removed the suppressions.
|
|
519
|
+
- **`bareloop.context.md` named `sonnet | haiku` in two places and is corrected** — the shipped adopter
|
|
520
|
+
contract carried the old menu in the `steps[].model` schema row and in the `providerFor` (P) section,
|
|
521
|
+
so an integrating agent reading it would have drafted a plan that reds. Both now name the one-entry
|
|
522
|
+
menu and say why it is one entry.
|
|
523
|
+
|
|
524
|
+
### Fixed
|
|
525
|
+
- **A locked-VERDICT `request-red` no longer files as a bare-agent upstream ask** (`src/job.js`,
|
|
526
|
+
`src/ledger.js`). `classifyIncidents` hardcoded `lib: 'bare-agent'` for every `request-red`, but the
|
|
527
|
+
code covers two territories: a locked TOOL verb (bare-agent, the original case) and a locked VERDICT
|
|
528
|
+
type (`soft-green`/`hitl`) — demand against bareloop's OWN `VERDICT_TYPES` menu, live-misfiled for
|
|
529
|
+
every `LOCKED_VERDICTS` declaration (the BA-2 misattribution class). Per the typed-lib rule the
|
|
530
|
+
territory is now stamped at the EMIT site (`Red` gains `lib?`; `verdictType` → `bareloop`, `tools` →
|
|
531
|
+
`bare-agent`) and the ledger reads it, keeping the hardcode only as the pre-field legacy-spine
|
|
532
|
+
fallback (zero archived rows carry the code, verified). The `suggestedAsk` template renders the
|
|
533
|
+
stamped target, so a bareloop-catalogue refusal can never seed a bare-agent ask; D13's genre refusals
|
|
534
|
+
(close-authoring design record) route by stamping their own lib with no ledger change. 5 new
|
|
535
|
+
failing-first tests; `capability-gap` still hardcodes bare-agent — flagged, structurally unreachable
|
|
536
|
+
today (a validation red returns before any provider call; the module header says so).
|
|
537
|
+
- **…and `capability-gap` no longer hardcodes it either — the flag above is CLOSED** (`src/ledger.js`).
|
|
538
|
+
`capability-gap` is `request-red`'s cap-halted form and inherits both of its territories, but the fix
|
|
539
|
+
landed at one of the two sites and not the other: the synthesis loop still wrote `'bare-agent'`
|
|
540
|
+
literally, and so did its `suggestedAsk` template — the one field a human actually files from, where
|
|
541
|
+
fixing only the occurrence's `lib` would have left the misattribution fully intact. The territory now
|
|
542
|
+
comes from the OCCURRENCE (the `request-red` rows already carry the lib stamped at their emit site),
|
|
543
|
+
and the **dedup key becomes `(verb, lib)` rather than the verb alone**: the ledger's own occurrence
|
|
544
|
+
key carries `lib`, so a `Set` over verbs would merge two territories into one row and attribute it to
|
|
545
|
+
whichever the set happened to hold first — an arbitrary target, which is the defect itself. One gap
|
|
546
|
+
per (territory, verb) costs nothing and each carries an honest one. **Reachability is unchanged and
|
|
547
|
+
still DORMANT** — `validateJob` returns not-ok on any red and `run.js` returns on a validation red, so
|
|
548
|
+
a `request-red` and a `cap-halt` cannot share one spine today; this is a latent-consistency fix at a
|
|
549
|
+
seam the module header documents as dormant, and it claims no live bug. 3 new failing-first tests,
|
|
550
|
+
each labelled in-file as a FUNCTION-CONTRACT fixture (`classifyIncidents` is pure) so a later reader
|
|
551
|
+
cannot mistake the synthetic stream for something a run can produce. Closed now rather than left
|
|
552
|
+
flagged because a fix that lands at one of two identical sites is the class this repo has already
|
|
553
|
+
paid for once — the ripgrep install that went into `ci.yml` and not `publish.yml`, which then failed
|
|
554
|
+
on the identical cause.
|
|
555
|
+
- **The replan ceiling reset on EVERY resume leg — a kill refilled a bound the operator signed**
|
|
556
|
+
(`src/planrun.js`, `src/run.js`, `src/reuse.js`, `scripts/run-u.mjs`). `replanned`/`varianceGrantUsed`
|
|
557
|
+
were locals in `runPlan`, and a resume is another `runPlan` call. Confirmed by execution before any
|
|
558
|
+
remedy: leg 1 replanned and stopped; leg 2, driven with a `resumeSeed`, replanned again — a run total
|
|
559
|
+
of 2 with every record showing 1. Two became four by being killed once, which is precisely the creep
|
|
560
|
+
the latch exists to make impossible (*"unlimited replanning launders thrash as adaptation"*, PRD
|
|
561
|
+
v1.12).
|
|
562
|
+
- **Fixed on the MONEY-FOLD precedent, not the grade-seed one, and the choice is the fix.**
|
|
563
|
+
`readGradeSeed`'s documented known limit is that it reads ONE spine, so a resume of a resume
|
|
564
|
+
inherits leg 2's grades and not leg 1's — the chain shortens by a leg per kill. That is fail-safe
|
|
565
|
+
for a READOUT (a short chain can only under-claim a direction) and it is the DANGEROUS direction
|
|
566
|
+
for a CEILING, where an under-claimed ledger is a refilled allowance. So each leg DECLARES the
|
|
567
|
+
ledger it inherited on its own spine (`priorSpentUsd`'s mechanism) and the next reader adds only
|
|
568
|
+
its own window's `replan` records. Leg 3 inherits the whole chain, not leg 2's slice.
|
|
569
|
+
- **The counter-argument was answered, not stepped over.** `src/trend.js` deliberately refuses to
|
|
570
|
+
seed the fix loop's ITERATIONS from history, and that stays right: an attempt allowance is a LEG
|
|
571
|
+
bound, and a restarted leg buys its own attempts with its own money. A replan ceiling bounds how
|
|
572
|
+
many times the WORKFLOW may be redrawn, which is the run's span — the same span the halt readout
|
|
573
|
+
already covers. The two seeds disagree on purpose and both now say so in-source.
|
|
574
|
+
- **SPINE FIELDS — `job-start` gains `priorReplans` / `priorReplanGrantUsed`.** Emitted only when
|
|
575
|
+
there is a fold (a decorative `0` is indistinguishable from a run nobody folded), on the same
|
|
576
|
+
declaration `priorSpentUsd`/`priorWallMs` already ride. The grant latch travels BESIDE the count
|
|
577
|
+
rather than being derived from it: a leg can inherit a spent ordinary ceiling with the arbiter's
|
|
578
|
+
extra still unearned (`1`, `false`), and deriving one from the other would collapse two real states
|
|
579
|
+
into one. `readResume`'s `direct` mode reads the fold off these very fields, so a U-path run
|
|
580
|
+
resumed twice inherits leg 1.
|
|
581
|
+
- **SPINE MEANING CHANGE — `plan-executed.replans` counts the CHAIN, not the leg** (and `replanned`
|
|
582
|
+
with it). **An adopter parsing spines must re-read this field.** The change was made rather than
|
|
583
|
+
shadowed by a second field precisely because `replans` shipped only on this unreleased branch, so
|
|
584
|
+
no archived spine carries the leg reading for a reader to be confused by. The leg's own number is
|
|
585
|
+
not lost: a leg emits one `replan` record per replan it drafts, so `chain − records-in-this-window`
|
|
586
|
+
is the fold it inherited — the same arithmetic `readResume` runs — and a second field stating a
|
|
587
|
+
derivable number would be a second reader of one question.
|
|
588
|
+
- **New option: `runJob(spec, { resumeReplans })`** (and `runPlan`'s identically-named option),
|
|
589
|
+
`{count, grantUsed} | null`, defaulting to `null` = the cold path, byte-identical to a run nobody
|
|
590
|
+
resumed. Belted like every declared fold entering the arbiter's arithmetic: a garbage, negative or
|
|
591
|
+
non-finite seed reads as `0` rather than poisoning later comparisons as `NaN`, and a negative one
|
|
592
|
+
can never cancel a real replan and widen the ceiling. `scripts/run-u.mjs --resume` and `runReuse`
|
|
593
|
+
both pass it. **The disarm control holds** — a resumed leg with an UNSPENT ceiling still replans, so
|
|
594
|
+
the bound is bounded and not switched off.
|
|
595
|
+
- **The replan brief's `closeGapBlock` re-bounded an ALREADY-bounded gap and deleted the red names the
|
|
596
|
+
first envelope had rescued** (`src/planrun.js`, `src/exits.js`). `src/exits.js` states the rule for
|
|
597
|
+
this same artifact one seam earlier: the check gap arrives already enveloped by `runClose`'s
|
|
598
|
+
`boundGap` under the stage's own `gapKeep`, which deliberately rescues the `not ok`/`FAILED` names
|
|
599
|
+
out of the elided middle — the mechanical gap F46's conversion mechanism feeds the worker. A second
|
|
600
|
+
400/1500 envelope deleted exactly those a second time, which is F28 reintroduced; measured on a
|
|
601
|
+
120-red-line close output, the first bound rescued **78** names and the second dropped **12** of
|
|
602
|
+
them. `CHECK_GAP_MAX` (12,000) is now **exported** from `src/exits.js` and imported here rather than
|
|
603
|
+
respelled, so the two seams cannot drift on how big a check gap may be, and it is applied as a
|
|
604
|
+
**backstop, not an envelope**: under it the gap passes through VERBATIM. The headroom is measured,
|
|
605
|
+
which is why the ceiling is not smaller — `runClose`'s own envelope tops out at ~10.1KB (400 head +
|
|
606
|
+
8192 keep + 1500 tail + labels), and joined with a `tree-changed` detail that is ~10.2KB, leaving
|
|
607
|
+
~1.8KB to spare, so the shipped path is untouched and the backstop fires only where `src/exits.js`
|
|
608
|
+
says it should: a seam that returned something never bounded at all. Length is measured AFTER the
|
|
609
|
+
secret scrub, because a mask is wider than most literals it replaces and bounding the raw string
|
|
610
|
+
would let a gap cross the ceiling on its way through the one transformation that always runs.
|
|
611
|
+
- **The replan brief's block was LABELLED as the close's own output but could lead with ours**
|
|
612
|
+
(`src/planrun.js`). `res.gap` is `lastGap` — the join of EVERY failing exit's detail in exit order —
|
|
613
|
+
and with `MAX_EXITS_PER_STEP` at 2 plus the mandatory `tree-changed` pairing on a write step, a step
|
|
614
|
+
that wrote nothing opens the block with the EVALUATOR's own prose (`0 files changed under src/** …`)
|
|
615
|
+
under a label reading *"the verification's own output"*. Captured verbatim from a real replan prompt.
|
|
616
|
+
The **label** is corrected to *"What this step's exits reported on its last attempt (their own
|
|
617
|
+
output, verbatim)"*; the **payload is deliberately kept** — "the step wrote nothing" is exactly what
|
|
618
|
+
a replanner should be told, and stripping it to make an old label true would trade a real fact for a
|
|
619
|
+
tidy sentence. F86's thesis (the artifact travels as TEXT, never parsed) is restated, not weakened.
|
|
620
|
+
- **F49's ReDoS reject now covers the OPERATOR's two regex fields, not just the agent's**
|
|
621
|
+
(`src/job.js`, `src/validate.js`, `src/plan.js`). `hasNestedQuantifier` shipped wired at exactly ONE
|
|
622
|
+
site — the agent-authored `artifact-written.pattern`. Two operator-authored patterns reach an
|
|
623
|
+
equally untimed evaluator and were uncovered: `judged.pattern`, exec'd by `runClose` against the
|
|
624
|
+
close's whole stdout+stderr, and `gapKeep`, compiled by `boundGap` and run **once per line** of
|
|
625
|
+
close output. Both now red `invalid-value` at the validation gate, before any tokens burn.
|
|
626
|
+
- **Measured, not argued:** `boundGap(stream, '(a+)+$')` against a 40-character line does not finish
|
|
627
|
+
in 20 seconds (`timeout` exit 124).
|
|
628
|
+
- **Why this is worse than an operator self-DoS, and F67 is the reason.** On the agent side F49's
|
|
629
|
+
own scope note holds — the agent authors both the pattern and the artifact, so a hang burns only
|
|
630
|
+
its own wall-clock and compromises no arbiter. These two run in **bareloop's own process**, so the
|
|
631
|
+
hang blocks the MAIN EVENT LOOP — and the in-process stall fuse is a timer in that same loop, so
|
|
632
|
+
it cannot fire (*a guard living inside the process it guards shares that process's fate*). The run
|
|
633
|
+
then dies to the OUTSIDE watchdog and reads as a model stall: **a bad regex in a SIGNED spec
|
|
634
|
+
presents as a provider problem.** The cost is a misdiagnosis, not just a stop.
|
|
635
|
+
- **The detector MOVED to `src/validate.js` — one inventory, never a second copy.** `plan.js`
|
|
636
|
+
imports `job.js`, so `job.js` cannot import `plan.js`; housing the scan with the other shared
|
|
637
|
+
primitives is what lets both documents reject through the same function. `plan.js` re-exports the
|
|
638
|
+
name (the `WRITE_VERBS` precedent), so the F49 site is byte-identical in behaviour. A second copy
|
|
639
|
+
would be precisely the drift class `SECRET_PATTERNS` exists to prevent — a shape one validator
|
|
640
|
+
rejects and the other admits. `hasNestedQuantifier` is not exported from `src/index.js`, so the
|
|
641
|
+
move is internal and breaks no adopter.
|
|
642
|
+
- **Admissibility swept before the change and re-run after:** all **92** operator regexes across all
|
|
643
|
+
**10** signed specs in `jobs/` — **zero** newly red. No spec breaks, no spec-hash churn (no spec
|
|
644
|
+
file is touched). The shipped shapes are pinned in the suite as the control that can produce the
|
|
645
|
+
negative: if the reject ever reds one of them, it has broken the live fleet.
|
|
646
|
+
- **Known limit, PARKED not chased:** the scan misses the alternation-overlap class (`(a|aa)+$`,
|
|
647
|
+
`(x|xy)*$` — no inner quantifier exists to find; the blowup comes from overlapping branches under
|
|
648
|
+
a repeat). Widening it changes which SIGNED specs are admissible, which is arbiter-adjacent.
|
|
649
|
+
Docked to the close-authoring rung, where hand-authored operator regexes may cease to be a surface
|
|
650
|
+
at all — see **PRD v1.55**. Recorded there too, so it is not re-chased: `([^])*$` is NOT caught and
|
|
651
|
+
that is CORRECT (`[^]` is the any-char idiom, so the pattern is linear); the genuinely nested
|
|
652
|
+
`([^]*)*$` IS caught.
|
|
653
|
+
|
|
654
|
+
### Added
|
|
655
|
+
- **A SECOND variance stop can earn ONE more replan — granted by the ARBITER, on a mechanical
|
|
656
|
+
reading** (F85, C). A second `step-variance` after the one-replan ceiling was spent used to be a
|
|
657
|
+
hard stop, and on a run that was still converging that stop threw away work already paid for. Every
|
|
658
|
+
clause of the grant is a constraint: **ONE**, bounded by a LATCH rather than a comparison so the
|
|
659
|
+
ceiling cannot creep by arithmetic (unlimited replanning launders thrash as adaptation, v1.12) — a
|
|
660
|
+
third variance stop is the stop, however well it is going; **the arbiter's**, read mechanically off
|
|
661
|
+
the same `runTrend` instance the meter and the money halt read, with the agent given no channel to
|
|
662
|
+
it — it never asks, is never offered it, and cannot influence it (no self-adjusted budgets, ever);
|
|
663
|
+
**`converging` only**, the trend instrument's own category with no fresh number invented here
|
|
664
|
+
(threshold-setting is the operator's), so `flat` and `unknown` stop exactly as before and an unknown
|
|
665
|
+
is never rounded up into a reason to spend (F6); and **`step-variance` only** — an exhaustion or a
|
|
666
|
+
stall after the ceiling is unchanged. `remainingUsd() > MONEY_MIN` still gates it, as it gates the
|
|
667
|
+
ordinary replan. **Spine change:** `replan` gains `replan` (which replan this is) and — only when the
|
|
668
|
+
arbiter granted one past the ordinary ceiling — `granted` with the reading it was granted on, so a
|
|
669
|
+
reader can never mistake the ceiling for a grant; `plan-executed` gains a `replans` count riding
|
|
670
|
+
BESIDE the shipped `replanned` boolean, never repurposing it. Live on `u-mshcpdg4`: three
|
|
671
|
+
`plan-accepted` records — the initial draft and two replans, the second granted on `converging`.
|
|
672
|
+
- **`closeGapBlock(gap)` — the replan brief carries the close's OWN output** (F86; `src/planrun.js`).
|
|
673
|
+
`u-mshcpdg4` reached ONE remaining strict error and still died. The close gap named the work exactly
|
|
674
|
+
— `src/recurse.js(978,115)` — and the worker read it every attempt; the REPLANNER, which is the
|
|
675
|
+
component that chooses which files the next plan targets, saw only
|
|
676
|
+
*"still progressing — typecheck 30 → 15 → 15 → 1"*: a number with no address. It aimed the last plan
|
|
677
|
+
at `src/loop.js`, already at zero errors, wrote nothing twice, and took two strikes — with $1.10 and
|
|
678
|
+
82 seconds still on the clock at the moment that replan was granted (the run itself ended $0.8174 and
|
|
679
|
+
~6 seconds short of its caps). F85 gave the brief the trajectory; this gives it the artifact the trajectory
|
|
680
|
+
is a summary OF. Handed over as **TEXT**, with both boundaries reused rather than respelled: scrubbed
|
|
681
|
+
through the one secret inventory (`redactSecrets`/`SECRET_PATTERNS`) because a prompt is an egress
|
|
682
|
+
point and the next caller will not remember the upstream scrub, and bounded by ralph's own `boundGap`
|
|
683
|
+
— now **exported** from `src/ralph.js` for it, since a private slice would be a second truncation
|
|
684
|
+
scheme that drifts on exactly the property that matters (whether the red lines survive, and whether
|
|
685
|
+
the trim announces itself, F28); the close path still calls it with the stage's own `gapKeep`,
|
|
686
|
+
byte-identically. The red-set is `REPLAN_GAP_KEEP = '\S'` — every non-blank line — because a shipped
|
|
687
|
+
`gapKeep` is `^`-anchored (`^BAREAGENT `, `^red`, `^FAILED`) and the exit evaluator wraps stage output
|
|
688
|
+
in `check "x" red: …`, so the anchor no longer sits where the pattern expects it; a gap that reaches
|
|
689
|
+
here is ALREADY a red-set (`judge` builds it out of the failing exits only), so keeping every line is
|
|
690
|
+
the correct instrument, not a widened one. **No spine field** — `src/trend.js`'s rule is that no
|
|
691
|
+
record carries a close byte. An empty gap yields the empty string, so the brief renders
|
|
692
|
+
byte-identically to the pre-F86 one (a labelled empty section would invite the planner to explain an
|
|
693
|
+
absence the run never observed). Verified by execution, not by reading: sabotaging the wiring
|
|
694
|
+
(`gapBlock = ''`) turns the `u-mshcpdg4` regression test red (`not ok 121`); restored byte-clean.
|
|
695
|
+
|
|
696
|
+
### Removed
|
|
697
|
+
- **`gapFilesNeverWritten()` and the `never wrote` replan advisory** (F86) — an EXPORT of
|
|
698
|
+
`src/planrun.js` is gone (it was never re-exported through the package entry, whose `exports` map
|
|
699
|
+
admits only `.`, so no adopter import breaks). hamr's order, verbatim: *"delete it and rerun
|
|
700
|
+
bareagent"*. The measured reason, on `u-mshcpdg4`'s own gap: the advisory resolved to
|
|
701
|
+
`src/loop.js` — the file already at zero errors — and said so as a DIRECTIVE, beside the artifact
|
|
702
|
+
that said the opposite; this worker follows directive prose (positive-scope confinement). It also had
|
|
703
|
+
no evidence of ever converting anything: it fired by construction on `u-msdsmkid`, and that run still
|
|
704
|
+
step-redded. The general rule it broke is why `closeGapBlock` hands over text instead: **a parsed
|
|
705
|
+
file list cannot tell a summary line from a detail line**. Line 1 of that very gap reads
|
|
706
|
+
`reports 1 error(s) in src/recurse.js, src/loop.js` — every file in SCOPE — and only line 2 names the
|
|
707
|
+
culprit; and the shapes differ per close anyway (tsc `file:line`, pytest test ids, a count close that
|
|
708
|
+
names no file at all). A model reading the artifact can make that distinction; a regex reproduces the
|
|
709
|
+
bug. Two readers of one question with the parsed one wrong is the same failure the variance meter just
|
|
710
|
+
had. Its 4 tests went with it, and its sole caller was the brief — no speculative code left behind.
|
|
711
|
+
|
|
8
712
|
## [0.7.0] — 2026-08-05
|
|
9
713
|
|
|
10
714
|
### Changed
|