bareloop 0.8.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 +421 -0
- package/README.md +10 -3
- package/bareloop.context.md +198 -17
- 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/index.js +72 -0
- package/src/job.js +48 -4
- package/src/kinds.js +1354 -0
- package/src/ledger.js +13 -0
- package/src/plan.js +26 -1
- package/src/planrun.js +239 -16
- package/src/ralph.js +19 -4
- package/src/reuse.js +68 -7
- package/src/root.js +8 -1
- package/src/run.js +10 -3
- package/src/text.js +120 -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/index.d.ts +9 -1
- package/types/job.d.ts +1 -0
- package/types/kinds.d.ts +343 -0
- package/types/plan.d.ts +18 -0
- package/types/planrun.d.ts +10 -3
- package/types/ralph.d.ts +1 -0
- package/types/run.d.ts +10 -2
- package/types/text.d.ts +84 -0
- package/types/workbranch.d.ts +102 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,427 @@ 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
|
+
|
|
8
429
|
## [0.8.0] — 2026-08-07
|
|
9
430
|
|
|
10
431
|
### Changed
|
package/README.md
CHANGED
|
@@ -43,8 +43,8 @@ it and never disappears.
|
|
|
43
43
|
> under an un-gameable outer close and one budget it can only tighten. On the real-model
|
|
44
44
|
> acceptance battery (F47) the emergent flow converted a job the same worker failed 4/4
|
|
45
45
|
> before — 3/3 runs, clearing the owned bar every time, composing its own check exits. The
|
|
46
|
-
> within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). **
|
|
47
|
-
>
|
|
46
|
+
> within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). **Then N3 —
|
|
47
|
+
> executed inheritance**, where a workflow that greened is kept, offered to the next
|
|
48
48
|
> similar job, and improved as executed instead of drafted from scratch (the ladder's
|
|
49
49
|
> kill-switch question). As of 0.7.0 N3's machinery **ships** — the bridge registry, the
|
|
50
50
|
> operator-signed reuse envelope, the selection call and step-level resume are all in the
|
|
@@ -52,7 +52,14 @@ it and never disappears.
|
|
|
52
52
|
> legal, never *beneficial*. The contrast that was to prove the lift is now retired (F88): a
|
|
53
53
|
> validation rule hands every cold run the plan shape a bridge was carrying, so the gap it
|
|
54
54
|
> would have measured is gone. Treat reuse as available and unproven — what a bridge should
|
|
55
|
-
> carry instead is open.
|
|
55
|
+
> carry instead is open. **The rung in flight is close authoring** — the last layer where a
|
|
56
|
+
> human still wrote code. You answer a short interview about what *done* means; an LLM then
|
|
57
|
+
> composes a close **declaration** over a catalogue of stage kinds bareloop implements — never
|
|
58
|
+
> code, never prose parsed back — which is validated, run once against your untouched repo so
|
|
59
|
+
> every ruler is seen measuring something real, and gated before you sign its hash. It is
|
|
60
|
+
> proven live on both a JavaScript and a Python patient, and its designed failure mode is an
|
|
61
|
+
> honest refusal: a close that would grade nothing, or a stage whose population does not exist
|
|
62
|
+
> in your tree, comes back decision-ready instead of green.
|
|
56
63
|
|
|
57
64
|
## Quick start
|
|
58
65
|
|