bareloop 0.37.0 → 0.39.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 +53 -0
- package/bareloop.context.md +48 -26
- package/package.json +3 -3
- package/src/authorflow.js +3 -2
- package/src/authoring.js +62 -3
- package/src/authorjob.js +37 -24
- package/src/authorreadout.js +449 -2
- package/src/authorrun.js +21 -4
- package/src/calibrate.js +84 -10
- package/src/cardauthor.js +4 -1
- package/src/declaredclose.js +10 -4
- package/src/job.js +1 -1
- package/src/judged.js +322 -28
- package/src/kinds.js +13 -1
- package/src/legs.js +15 -0
- package/src/panel/authorroutes.js +9 -6
- package/src/panel/authorsession.js +66 -49
- package/src/panel/index.html +321 -355
- package/src/panel/server.js +33 -13
- package/src/panel/status.js +5 -4
- package/src/planrun.js +4 -3
- package/src/providers.js +27 -0
- package/src/ralph.js +8 -2
- package/src/replay.js +23 -3
- package/src/trend.js +1 -0
- package/types/authoring.d.ts +24 -1
- package/types/authorjob.d.ts +17 -10
- package/types/authorreadout.d.ts +106 -0
- package/types/calibrate.d.ts +4 -1
- package/types/declaredclose.d.ts +2 -1
- package/types/judged.d.ts +21 -2
- package/types/legs.d.ts +12 -0
- package/types/panel/authorsession.d.ts +15 -1
- package/types/panel/server.d.ts +5 -4
- package/types/panel/status.d.ts +1 -1
- package/types/providers.d.ts +7 -0
- package/types/ralph.d.ts +6 -1
- package/types/trend.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ All notable changes to bareloop are documented here. Format:
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning:
|
|
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
|
+
|
|
8
|
+
## [0.39.0] - 2026-10-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Rubric judge rulebook (F192).** `says-what`: the doc description must say more than the function's own name; name words match by prefix (init/initialize; a shorter word of 4+ letters), and with no doc block `has-doc` alone owns the red. `params` also reds an extra `@param` that names no parameter, and a missing field of a destructured parameter once the doc names any field. `has-doc` checks the artifact itself: the judge only points at the declaration, code finds the `/**` block above it. Quote matching uses bareguard `quoteIn` with `wholeLines`, so a quote that dropped a leading `* ` still matches. The injection facts check shares `has-doc`'s acceptance, and every rule carries a plain-words `means` for the case-proposal prompt.
|
|
13
|
+
- **A calibration case may not expect a red the rulebook cannot raise** (a `has-doc` red on a function that has a JSDoc block); it is refused before any locate call.
|
|
14
|
+
- **Calibration record.** A per-case `diag` (locate facts, full reds, per-attempt cause) is recorded beside the graded rows, and each failing case gets one plain why-line. Grading, hashes and verdicts are untouched.
|
|
15
|
+
- **Judged write fence.** A judged stage may only judge files inside the job's write fence; otherwise the red `judged-outside-write-scope` fires at draft time, before signing, on every door (authoring revise loop, signing, runner pre-flight, hand-written or reused specs).
|
|
16
|
+
- **Worker warning.** When a job's close carries the js no-suppressions check, the worker is told up front that it reads comment lines too.
|
|
17
|
+
- **Panel.**
|
|
18
|
+
- A refused rubric proposal and a refused signing each say why in plain words (panel and CLI share one owner); the full reds go to `draft-log.jsonl`.
|
|
19
|
+
- The calibrating line shows the gate's real score (8 of 10 / 3 of 5), not a stale check mark.
|
|
20
|
+
- The confirm plan reads as headed sections (#PLAN / #CHECKS / #NOT CHECKED, and #QUESTIONS when raised).
|
|
21
|
+
- The step map is fit-to-text chips with arrows, a line-start arrow on wrapped lines, and ↻N plus a dashed box for retries.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Strike governor.** A judged stage's trend number is its distinct red count, compared to best-so-far; the gap headline reads "N red(s) across X of Y artifact(s)". Identical, flip-flopping or growing judged reds now strike (they read as not comparable before).
|
|
26
|
+
- **Judge thinking is off** for the rubric judge on deepseek-flash, per call and allow-listed; workers and the drafter are unchanged and `JUDGE_MAX_TOKENS` stays 4000.
|
|
27
|
+
- **Revise reopens the job's boxes**, filled as drafted; Start drafting then drafts fresh. The typed change request is gone: `POST /api/author/:id/revise`, `session.revise`, the "N changes left" hint and `revisesLeft` are removed. The CLI confirm turn is unchanged.
|
|
28
|
+
- Dependencies: `bareguard` ^0.13.0 to ^0.21.0, `bare-agent` ^0.43.0 to ^0.49.0.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **A passed calibration satisfies the seed-red gate for any close with a judged stage**, mixed or pure. The old test read a function's arity (always true), so signing refused a mixed rubric close after calibration passed 10/10 (live session smv0tasvb0u89).
|
|
33
|
+
|
|
34
|
+
### Known limits
|
|
35
|
+
|
|
36
|
+
- The judge's false-red classes seen on live run mv1j01sl are parked (FINDINGS F192 f), not fixed.
|
|
37
|
+
- The step-map chips and the new panel text were not checked at phone width by a person.
|
|
38
|
+
|
|
39
|
+
## [0.38.0] - 2026-10-08
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Edit in chat on every run that did not finish green.** The Chat card opens filled from the run's signed job with every box open; Start drafting works as for any new job. The Ended block's NEXT line points at the button, and a refused Resume says so too.
|
|
44
|
+
- **Resume moves to the left Chat card.** Resume mode locks the signed job and opens only the two caps (money and time); Sign & resume posts the same body as before.
|
|
45
|
+
- **Expanded jobs show their latest 7 other runs;** older runs are reached by search.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- The run page's Job tab is read-only on every run; the Job-tab resume mode is gone.
|
|
50
|
+
- A stopped run wears `[■]` (red), not `[✗]`, on every surface.
|
|
51
|
+
- Reuse workflow is offered only from a run that earned its green. The Reuse list shows only jobs with a real green run; an already-green run offers Edit in chat instead; `POST /api/author/start` refuses a reuse of any local run that did not finish green (the server owns the rule, not just the pickers); and a delivery-refused ending offers Reuse only on a run that earned its green.
|
|
52
|
+
- The Reuse picker reads "Loading…" until its list arrives, and switching the Check type radios no longer blanks the New job boxes.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- A run cut by the money or wall cap reads `capped` on every surface: a leg that ended escalated on a money-halt or wall-halt, and a cap-cut open step on a single-leg run, no longer read as failed. One helper (`capKindOf`) owns the rule.
|
|
57
|
+
- A Stop clicked in the fix loop reads "during the fix phase ... a resume continues the run" instead of the step text "a resume re-enters this step", which was wrong where no step exists (found on run muxwwrgs).
|
|
58
|
+
- Small cleanups with unchanged behaviour: the `npm ci` comment matches the code, a dead pid above `pid_max` is handled, and tests follow the new panel behaviour.
|
|
59
|
+
|
|
7
60
|
## [0.37.0] - 2026-10-07
|
|
8
61
|
|
|
9
62
|
### Added
|
package/bareloop.context.md
CHANGED
|
@@ -67,7 +67,7 @@ inherited rule carries the green that minted it and the contrast that attributed
|
|
|
67
67
|
an unknown provider name THROWS there, it never falls back to a default. Per-model
|
|
68
68
|
request-key gating lives in that table: `deepseek-flash` sets bare-agent's
|
|
69
69
|
`legacyMaxTokens`, because DeepSeek silently ignores `max_completion_tokens` and an
|
|
70
|
-
output cap that does not bind is a money hazard. `provider: 'gemini-api'` (PRD item 31.3)
|
|
70
|
+
output cap that does not bind is a money hazard. The judge's calls additionally carry a per-model, judge-only `thinking:{type:'disabled'}` (allow-list `OPENAI_JUDGE_CALL_OPTIONS`, read through `judgeCallOptions`, applied by `defaultJudgeLoop`; F192) because deepseek-flash's default thinking ate the 4000-token locate budget; workers and the drafter are untouched. `provider: 'gemini-api'` (PRD item 31.3)
|
|
71
71
|
takes the same `Loop` path, reads `GEMINI_API_KEY`, and maps two real tiers
|
|
72
72
|
(`sonnet` → `gemini-2.5-pro`, `haiku` → `gemini-2.5-flash`). It is
|
|
73
73
|
**ADMITTED-PENDING-PROBE**: it has ZERO runs, and the probe rule is not waived for it —
|
|
@@ -677,6 +677,16 @@ attempt PLUS its close. Unpriced is never free: a null cost is a `pricing-red` s
|
|
|
677
677
|
never retried, and the ladder is otherwise ONE retry (`JUDGE_ATTEMPTS`) — after that a broken
|
|
678
678
|
judge is an instrument stop, never a red about the tree.
|
|
679
679
|
|
|
680
|
+
**The rulebook, and what each rule can raise** (`JUDGE_RULES` in `src/judged.js`; every rule carries a plain-words `means` the case-proposal prompt renders, so a proposed practice case never expects a red the rule cannot raise). The judge only POINTS (quotes lines verbatim); code does every check; unsure is red; the judge never says pass/fail. Quotes are matched by bareguard's `quoteIn(..., {wholeLines:true})` (bareguard `^0.21.0`): a contiguous run of whole artifact lines, so a bare fragment cannot pass by hiding in a longer line, while a dropped leading `* ` of a JSDoc tag line still matches. `has-doc` checks only that a JSDoc block sits directly above the function (read off the artifact by `docBlockAbove`; the judge's quote is a pointer that must lie inside that block), so a documented function never breaks it. **`says-what`** (the doc's description must say more than the function's own name) reds when the described prose, minus a fixed list of function words and the name's own words (camel/snake/kebab, light inflections, and a prefix echo such as `init`/`initialize` when the shorter word is at least 4 letters), leaves nothing; no description, or an unlocatable quote, reds. It catches a pure name echo only ("Returns the total." for `getTotal` passes), and with NO doc block at all it stays silent and leaves the red to `has-doc` (the card is not required to carry both). `params` reds a declared parameter with no tag, an `@param` naming no parameter (each destructured slot absorbs one unmatched root tag), and, when the doc names any field of a simple destructured parameter, a missing field; `@param options` alone for `{ x, y }` passes. `returns` reds a returning function with no `@returns`. A practice case that expects a `has-doc` red on a function that has a block is refused as a legality red before any call (`validateCalibrationSet`).
|
|
681
|
+
|
|
682
|
+
**A judged stage may only judge files inside the job's write fence** (`judgedOutsideFence`, `src/authoring.js`, called from `validateDeclaration`, which every door reaches: the authoring revise loop, `validateCloseDecl` at signing and runner pre-flight, and `validateJob` for hand-written or reused specs). A path outside `writeScope` reds `judged-outside-write-scope` at draft time, because a finding on a file the run is fenced out of can never be fixed. With no fence to compare against (the plain-folder deferral) the rule is not run.
|
|
683
|
+
|
|
684
|
+
**One red count.** A judged stage's red COUNT is the number of distinct failing functions it itemizes (`redCount`/`redSet` on the stage detail, `src/kinds.js`; an unsure entry counts as one). The same number is the gap headline ("N red(s) across M of K artifact(s)") and the trend value the close-trend governor compares to best-so-far, direction always down (`trendValueOf`, `src/declaredclose.js`), so a repeated or growing judged failure strikes like any numeric stage instead of running to the wall. Spines written before this change carry the old headline (the card-item count).
|
|
685
|
+
|
|
686
|
+
**A refused draft or signing says WHY in plain words.** `proposalStopText` (a refused rubric proposal: `proposal-invalid` / `rubric-invalid`) and `signingStopText` (refused signing gates) in `src/authorreadout.js` are the one owner of the wording, printed by the panel's failed step and the CLI alike: code-written sentences per red kind naming the case and function, the real spend so far, and what to do; a practice-case/check mismatch is not claimed to be anyone's fault; an unknown red code is quoted, never dropped. The full scrubbed reds go into the session's `draft-log.jsonl` as a `step-reds` record, and the calibrating line shows the gate's real result (`calibrationSummary`: "N of 10 practice cases graded right, M of 5 attack tests resisted").
|
|
687
|
+
|
|
688
|
+
**The worker is warned about the `any` suppression guard.** A job whose actual close stages carry the js `any` suppression pattern gets one extra genre-owned line in the worker system prompt (`ANY_SUPPRESSION_WARNING`, `anySuppressionWarning`, `src/authoring.js`): the guard scans added comment lines too, so prose like `, any` trips it. The guard regex itself is unchanged.
|
|
689
|
+
|
|
680
690
|
**The two SIGNED artifacts of a judged close** (softgreen module 4, design §4.3/§4.4). The
|
|
681
691
|
softgreen interview's Judge Examples answer (PRD item 33 M3 piece 3 — one example to pass, one
|
|
682
692
|
to fail, and why; formerly two questions, Q6+Q7) compiles into the RUBRIC CARD and the FROZEN
|
|
@@ -745,12 +755,13 @@ signing record keeps WHAT was certified — `cardHash`, `casesHash`, `setHash` (
|
|
|
745
755
|
resolved judge identity), the graded rows and the battery — beside `judgeModel` itself.
|
|
746
756
|
`runCalibration` takes `judgeModel` as a REQUIRED argument and throws without one.
|
|
747
757
|
|
|
748
|
-
**D9.3 for a judged
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
758
|
+
**D9.3 for a close with a judged stage** (ruling 3, hamr: *"fix it now, we are delivering softgreen"*;
|
|
759
|
+
widened 2026-10-09: *"a rubric job should check if judge can judge (calibration) ... we can't close
|
|
760
|
+
everything upfront"*). A judged stage skips the seed read (ruling 8), so it can never be red at seed. For
|
|
761
|
+
ANY close carrying a judged stage, pure or mixed with mechanical stages, a PASSED calibration gate plays the
|
|
762
|
+
seed-red role (`seedProofIsCalibration`), and the record says so when no mechanical stage is red itself:
|
|
763
|
+
`gates.seedVerdict.satisfiedBy === 'calibration'`. A failed or missing calibration never satisfies it. A close
|
|
764
|
+
with NO judged stage is UNCHANGED and still needs a work stage red at the seed.
|
|
754
765
|
|
|
755
766
|
**THE JUDGE TIER IS INSIDE THE SIGNATURE, and the stage refuses a mismatch.** *"A judge-model
|
|
756
767
|
bump forces a full recalibration"* used to be an operator-side rule with nothing to fire on,
|
|
@@ -1863,7 +1874,7 @@ hand them to the one `Loop` that drives worker, scout and planner rounds; the ru
|
|
|
1863
1874
|
them once. No price on the row = no `rates` key anywhere = the guesstimate above, byte-identical to
|
|
1864
1875
|
before. bareloop keeps no price list of its own, ever.
|
|
1865
1876
|
|
|
1866
|
-
**The provenance is on the record, per round (`rateSource`).** The pinned bare-agent (`^0.
|
|
1877
|
+
**The provenance is on the record, per round (`rateSource`).** The pinned bare-agent (`^0.49.0`)
|
|
1867
1878
|
carries it on every metering payload, and bareloop forwards it VERBATIM (`rateSourceFields`,
|
|
1868
1879
|
`src/planrun.js`): every API `worker-round` (worker, scout, planner, fix loop) carries it beside
|
|
1869
1880
|
`pricing` today — `'caller'` when a customer price is on the key's row, else `'tier'`/`'default'`
|
|
@@ -3453,13 +3464,13 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3453
3464
|
re-attaches to it from the server's session map (nothing is stored page-side). A panel restart mints a new token and
|
|
3454
3465
|
loses the in-memory sessions: the page then shows one "reload this page" line and stops the Chat poll (no auto-reload;
|
|
3455
3466
|
drafting money already spent stays booked via `draft-spend.json`, below). `GET /api/author/:id` polls the session's state
|
|
3456
|
-
(phase, chat messages, cost, `
|
|
3467
|
+
(phase, chat messages, cost, `specHash` once prepared). `POST /api/author/
|
|
3457
3468
|
:id/send {text}` answers whatever the confirm turn is currently asking (the `language` pick or a plan's
|
|
3458
3469
|
own follow-up question — the old `worseThanBefore` ask is retired, 2026-09-28) — refused outright when the pending ask is the
|
|
3459
|
-
plan MENU itself (`{ok:false}`, no route from chat text to a plan decision, ever).
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3470
|
+
plan MENU itself (`{ok:false}`, no route from chat text to a plan decision, ever). There is no
|
|
3471
|
+
typed-revise route (2026-10-09): the panel's **Revise** button, shown only while a plan waits for the OK,
|
|
3472
|
+
abandons the draft (`/abandon`; spend already booked stays booked) and reopens the New job card with the same
|
|
3473
|
+
values in every box, editable, for a fresh Start drafting; no change request is ever sent to the model. `POST /api/author/:id/sign-prepare` is the menu's
|
|
3463
3474
|
own `confirm` pick — it runs gates 1–4 and reaches `phase:'prepared'` with a `specHash`; it
|
|
3464
3475
|
NEVER signs. `POST /api/author/:id/sign {specHash}` is the ONLY route that spawns a run —
|
|
3465
3476
|
it refuses unless `phase==='prepared'` and the posted hash matches the session's own
|
|
@@ -3471,7 +3482,7 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3471
3482
|
server's environment with `~/.config/bareloop/.env` merged in per request (a key is never read
|
|
3472
3483
|
into or sent to the page — a missing one refuses the session at $0, naming only the env var). `src/panel/authorsession.js` takes
|
|
3473
3484
|
test-only DI seams (`scout`/`generate`/`confirmGenerate`/`authorFn`/`prepareSigningFn`) so
|
|
3474
|
-
a test can drive the real ask()-channel/
|
|
3485
|
+
a test can drive the real ask()-channel/hash wiring without a live provider call;
|
|
3475
3486
|
none of them are reachable from `authorroutes.js`'s real construction path. Sessions live
|
|
3476
3487
|
under `~/.config/bareloop/panel-sessions/<id>/` (each one's own `resolved-spec.json` and
|
|
3477
3488
|
`signing.json`, the two files `bareloop author` itself already writes). Edit/re-sign, the
|
|
@@ -3503,20 +3514,20 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3503
3514
|
result glyphs are unchanged. The reuse button is described under Reuse workflow, below.
|
|
3504
3515
|
**Status words (click-through 2026-10-04)** → every run row, run detail and imported row carries `status`
|
|
3505
3516
|
`{key, sign, word}` from ONE table (`src/panel/status.js`): `[▶] running · [·] waiting · [✓] passed · [✗] failed ·
|
|
3506
|
-
[✗] capped · [
|
|
3517
|
+
[✗] capped · [■] stopped · [?] died`; `glyph` is that sign (the Runs `✗` filter chip also finds a `[■]` run). Mapping: green/already-green/satisfied → passed; no
|
|
3507
3518
|
job-end + runner alive → running, gone → died; cap-halt, wall-halt, and an escalation that is a money-halt or wall-halt
|
|
3508
|
-
→ capped; `stopped` → stopped; every other outcome (plan-red, check-red, step-red, close-red, provider-red,
|
|
3519
|
+
→ capped (ONE rule, `capKindOf` in `src/legs.js`, shared by the run status, a leg's stop reason and the part cards); `stopped` → stopped; every other outcome (plan-red, check-red, step-red, close-red, provider-red,
|
|
3509
3520
|
step-stalled, other escalations, refusals) → failed. The detail also carries `startedAt` and `endedAt` (the last
|
|
3510
3521
|
`job-end` timestamp, null while live or died). `GET /api/imports` rows and `GET /api/imports/:id` carry `runStatus`
|
|
3511
3522
|
(passed, or null when the bundle shows no green) and `runReason` (`goal met`). The Ended block's lines for died and
|
|
3512
3523
|
stopped runs are `no ending recorded` and `you pressed Stop` (each `— resume` when Resume is offered).
|
|
3513
3524
|
Every PART of a run detail (`parts[]`) also carries its own `status` `{key, sign, word}` from the same table
|
|
3514
3525
|
(`partStatusFor`, `src/panel/status.js`): drafting, a green outcome and a part with no verdict and no cut-off → passed; a red
|
|
3515
|
-
outcome → its run-outcome word; a part its leg's halt cut off → capped (money cap / time cap), stopped (you stopped it),
|
|
3526
|
+
outcome → its run-outcome word; a part its leg's halt cut off → capped (money cap / time cap — also a leg that ended `escalated` on a money-halt or wall-halt, and the open step of a single-leg run that ended on a cap), stopped (you stopped it),
|
|
3516
3527
|
died, else failed. A run that DIED (no `job-end`, runner gone) leaves its last part in flight: when that part has no outcome
|
|
3517
3528
|
and no cut-off, the server marks it `died` (`[?] died`), never the "happened, so passed" default — replay alone cannot tell
|
|
3518
3529
|
died from still running, so `getRunDetail` decides it from the same `death` verdict as the run's status. A live run's open
|
|
3519
|
-
part stays running; a part that finished before the death keeps its own status. The page's map
|
|
3530
|
+
part stays running; a part that finished before the death keeps its own status. The page's step map is a row of fit-to-text chips that flow and wrap (no truncation, no horizontal scroll), `[sign title]` each, in plan order, with an arrow between neighbours and a leading arrow on a wrapped line; the status word (from the same table) rides in the chip's tooltip, a retried step has a dashed border and a `↻N` tries mark, a leg boundary a dotted right edge, and a click jumps to that part's Audit row (the live part reads `[▶]`); the step cards and Audit rows
|
|
3520
3531
|
show the word only; no surface says "done".
|
|
3521
3532
|
**Resume (item 2)** → `POST /api/runs/:runid/resume` (`src/panel/runroutes.js`), behind
|
|
3522
3533
|
`checkHumanGuard` (token + own address, like `/api/author/*`); body `{budgetUsd?, maxWallMin?}`
|
|
@@ -3534,7 +3545,9 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3534
3545
|
the log tail is returned as `error` verbatim; otherwise `{ok:true, runid, specHash, capsChanged,
|
|
3535
3546
|
log}`. The run keeps its runid and its one list row: the resumed leg appends to the same spine (P5-R,
|
|
3536
3547
|
`leg-start` in the run list), it never appears as a new run. The page's Resume button opens the
|
|
3537
|
-
|
|
3548
|
+
Chat card in RESUME mode (title `RESUME <job> (run-N)`, filled from `GET /api/author/start-from`, every box locked
|
|
3549
|
+
except the two caps, "spent so far" shown, Clear/Start drafting hidden) and `Sign & resume` on that card is the human
|
|
3550
|
+
click, posting this same route; the run's Job tab is read-only on every run.
|
|
3538
3551
|
**Stop (item 5)** → `POST /api/runs/:runid/stop` (`src/panel/runroutes.js`, `checkHumanGuard`, no body).
|
|
3539
3552
|
It writes the run's STOP REQUEST, a file `<spine>.stop` (`stopFilePath(spine)`, `src/legs.js` — the one
|
|
3540
3553
|
spelling), and signals nothing. `404` unknown run; `409` when the run's LATEST leg is not live
|
|
@@ -3556,11 +3569,18 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3556
3569
|
in the Run tab's action area while live, "stopping after this turn…" after the click, and the same
|
|
3557
3570
|
`[Resume]` there once the engine would accept one. A Stop clicked while the run is in the
|
|
3558
3571
|
scout, plan or close-fix phase is honoured at that phase's next round boundary, the same as in a step (it used to be read in
|
|
3559
|
-
step workers only).
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
green
|
|
3563
|
-
|
|
3572
|
+
step workers only). A stop in the close-fix loop reads "You stopped the run during the fix phase … a resume continues the
|
|
3573
|
+
run" (`ralph`'s `phase` option; the step loop keeps "a resume re-enters this step").
|
|
3574
|
+
**Edit in chat (2026-10-08)** — `ended.actions` carries `{id:'edit', label:'Edit in chat'}` on EVERY ending that is not a
|
|
3575
|
+
real green (failed, capped, stopped, died, every other red, and an already-green or delivery-refused already-green),
|
|
3576
|
+
after `resume` when both show. The button reads `GET /api/author/start-from` and opens the Chat card filled from the
|
|
3577
|
+
signed job with EVERY box open and the normal Start drafting path (no reuse state, no borrowed track record), under a
|
|
3578
|
+
code-owned origin line ("Copied from <job> (run-N) — <status word>. …").
|
|
3579
|
+
**Reuse workflow (replaces P5 item 3's Start from this, 2026-10-03)** — a button on runs that EARNED their green only
|
|
3580
|
+
(the Run tab's action row and the Ended block; `ended.actions` carries `{id:'reuse', label:'Reuse workflow'}` on `green`
|
|
3581
|
+
and green-with-destination-refused `green` rows, and on no other: an `already-green` run (the check passed before any
|
|
3582
|
+
work) offers Edit in chat instead, and a red row's next line is "Change the job: press Edit in chat."; stopped/capped/died
|
|
3583
|
+
rows offer `resume` and `edit`) and on every imported job; the Chat card's Check type is one radio group of three
|
|
3564
3584
|
(Deterministic · Rubric · Reuse workflow), and the third opens a search picker over `GET /api/author/reuse-jobs` (below) that
|
|
3565
3585
|
fills the card through this same path (a pick, then `change` returns to the empty search; Deterministic or Rubric leaves reuse).
|
|
3566
3586
|
It opens the Chat tab's New job card
|
|
@@ -3574,10 +3594,12 @@ key you want first (the panel picks the row by the Model menu).
|
|
|
3574
3594
|
green, notGreen, live, finished, avgSpendUsd, avgWallMs}, line}` (`404` unknown, `409` with an `error` when
|
|
3575
3595
|
there is nothing to copy: no log, the run's signed job is not on disk, or an imported job that changed, lost its
|
|
3576
3596
|
dependency or no longer matches its signed bytes); `POST /api/author/start` accepts `startFrom: <runid>` or
|
|
3577
|
-
`startFrom: {importId}` beside the card and answers `{ok, sessionId, reuse, state}
|
|
3597
|
+
`startFrom: {importId}` beside the card and answers `{ok, sessionId, reuse, state}`; a local `startFrom: <runid>` whose
|
|
3598
|
+
latest outcome is not `green` (`start-from` carries that `outcome`) is refused `409 "only a run that finished green can
|
|
3599
|
+
be reused — this one did not"` — the server owns the rule, not just the pickers. The old
|
|
3578
3600
|
`POST /api/author/start-from-check` and the "Changed — new job" mode are gone. `GET /api/author/reuse-jobs[?model=<Name>]` →
|
|
3579
3601
|
`{ok, jobs:[{job, runid, checkType:'deterministic'|'rubric', line}]}` is the Chat card's Reuse workflow picker list: one entry per
|
|
3580
|
-
job (same `workflowKey`) with at least one
|
|
3602
|
+
job (same `workflowKey`) with at least one real `green` local run (`already-green` is not a reuse source), `runid` = its newest green run (hand it to `start-from`), `line` =
|
|
3581
3603
|
start-from's own estimate line, newest green first; imported jobs are not listed. **Identity, two keys:** the
|
|
3582
3604
|
signature hash `jobSpecHash` is unchanged (it covers the caps and the write fence — what the person signs);
|
|
3583
3605
|
`workflowKey(spec)` (`src/job.js`, not exported from the package root) is a sha256 over the signed spec WITHOUT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"check:lockfile": "node scripts/check-lockfile.mjs"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"bare-agent": "^0.
|
|
31
|
-
"bareguard": "^0.
|
|
30
|
+
"bare-agent": "^0.49.0",
|
|
31
|
+
"bareguard": "^0.21.0",
|
|
32
32
|
"litectx": "^0.32.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
package/src/authorflow.js
CHANGED
|
@@ -384,7 +384,8 @@ export const CLASS_STATEMENTS = Object.freeze({
|
|
|
384
384
|
+ 'the result and deciding whether it is any good — and no command can render it. Compose the mechanical part '
|
|
385
385
|
+ 'first: everything about this repository a command can decide is still a mechanical stage, exactly as it would '
|
|
386
386
|
+ 'be for a green job. Then put ONE judged-floor stage AFTER them, carrying the few things the person said they '
|
|
387
|
-
+ 'actually look for and the real files the work lands in
|
|
387
|
+
+ 'actually look for and the real files the work lands in (files the job may EDIT, inside the write fence, '
|
|
388
|
+
+ 'because a finding on a file the run is fenced out of can never be fixed). A judged stage decides only the lines its card names, '
|
|
388
389
|
+ 'so name each thing they look for as its own line, and leave nothing to be inferred.',
|
|
389
390
|
// N4 slice 1. The mechanical-first composition law is stated as an ORDER
|
|
390
391
|
// because first-red-wins makes the order the mechanism: the cheap stages shield
|
|
@@ -2389,7 +2390,7 @@ export async function authorClose({
|
|
|
2389
2390
|
}
|
|
2390
2391
|
previous = ask.declaration;
|
|
2391
2392
|
|
|
2392
|
-
const v = validateDeclaration(ask.declaration, { catalogue, listing: seedFiles, guards, envOwned: ownedEnvNames, verdictType });
|
|
2393
|
+
const v = validateDeclaration(ask.declaration, { catalogue, listing: seedFiles, guards, envOwned: ownedEnvNames, verdictType, writeScope });
|
|
2393
2394
|
// SCRUBBED HERE, once, where the reds enter this module's records — the same
|
|
2394
2395
|
// boundary rule `renderRejectBlock` already states, applied to the OTHER
|
|
2395
2396
|
// channel they travel down. A validation red quotes what the model declared
|
package/src/authoring.js
CHANGED
|
@@ -516,6 +516,32 @@ export const TYPES_GENRE_TEMPLATE = `1. The STRICT form of the language's type c
|
|
|
516
516
|
|
|
517
517
|
export const GENRE_LANGUAGES = Object.freeze(['js', 'python']);
|
|
518
518
|
|
|
519
|
+
/**
|
|
520
|
+
* The js `any` suppression pattern's regex, ONE spelling: the TYPES js battery
|
|
521
|
+
* below composes it into the signed guard, and `anySuppressionWarning` keys on
|
|
522
|
+
* it, so the warning and the guard cannot drift. The guard scans every ADDED
|
|
523
|
+
* line, comments included, so plain prose like ", any" trips it.
|
|
524
|
+
*/
|
|
525
|
+
const JS_ANY_REGEX = '(?:[{<|(\\[:,]|\\bas)\\s*any\\b|\\bany\\[\\]';
|
|
526
|
+
|
|
527
|
+
/** The worker-facing line that rides with the js `any` guard (run mv13ery3). */
|
|
528
|
+
export const ANY_SUPPRESSION_WARNING = '\nThe no-suppressions check also reads comments: never write the word any right after a comma, colon, bracket, brace, < or | '
|
|
529
|
+
+ "(e.g. ', any', ': any', '(any'), even in plain English — write 'every', start the clause differently, or put it in backticks.";
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* The warning line for a job whose ACTUAL stages carry the js `any` pattern, else
|
|
533
|
+
* ''. Derived from the stages' patterns (never goal prose), so a job without the
|
|
534
|
+
* guard is told nothing about it.
|
|
535
|
+
* @param {any[]|null|undefined} stages
|
|
536
|
+
* @returns {string}
|
|
537
|
+
*/
|
|
538
|
+
export function anySuppressionWarning(stages) {
|
|
539
|
+
const has = Array.isArray(stages) && stages.some((s) => isObj(s) && s.kind === 'pattern-absent-in-diff'
|
|
540
|
+
&& isObj(s.params) && Array.isArray(s.params.patterns)
|
|
541
|
+
&& s.params.patterns.some((/** @type {any} */ q) => isObj(q) && q.regex === JS_ANY_REGEX));
|
|
542
|
+
return has ? ANY_SUPPRESSION_WARNING : '';
|
|
543
|
+
}
|
|
544
|
+
|
|
519
545
|
/**
|
|
520
546
|
* THE GENRE, as data. The suppression batteries are the hand-written closes'
|
|
521
547
|
* own `SUPPRESSIONS` tables (`scripts/u-pulselog-close.mjs:35`,
|
|
@@ -541,7 +567,7 @@ export const TYPES_GENRE = Object.freeze({
|
|
|
541
567
|
Object.freeze({ id: 'ts-expect-error', regex: '@ts-expect-error' }),
|
|
542
568
|
Object.freeze({ id: 'ts-nocheck', regex: '@ts-nocheck' }),
|
|
543
569
|
Object.freeze({ id: 'eslint-disable', regex: 'eslint-disable' }),
|
|
544
|
-
Object.freeze({ id: 'any', regex:
|
|
570
|
+
Object.freeze({ id: 'any', regex: JS_ANY_REGEX }),
|
|
545
571
|
Object.freeze({ id: 'any-star', regex: '@\\w+\\s*\\{\\s*[*?]\\s*\\}' }),
|
|
546
572
|
Object.freeze({ id: 'cast', regex: '@type\\s*\\{.*\\}\\s*\\*\\/\\s*\\(' }),
|
|
547
573
|
]),
|
|
@@ -1057,6 +1083,25 @@ function scopeOfJob(declaration, idx, guards) {
|
|
|
1057
1083
|
return { scoped: false, via: null };
|
|
1058
1084
|
}
|
|
1059
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* Which of a judged stage's paths lie OUTSIDE the job's write fence? One owner of the rule "a check must be
|
|
1088
|
+
* free to edit every file it can report on", called from every door that validates a declaration. It uses
|
|
1089
|
+
* the shipped fence spelling (`globToPrefix`, the same containment reading plan.js's `insideFence` has) and
|
|
1090
|
+
* returns `[]` when there is no fence to compare against or the paths are not a string list (shape is the
|
|
1091
|
+
* schema walk's business).
|
|
1092
|
+
* @param {unknown} paths @param {string[]|null|undefined} writeScope
|
|
1093
|
+
* @returns {string[]}
|
|
1094
|
+
*/
|
|
1095
|
+
export function judgedOutsideFence(paths, writeScope) {
|
|
1096
|
+
if (!Array.isArray(writeScope) || writeScope.length === 0 || !Array.isArray(paths)) return [];
|
|
1097
|
+
const fence = writeScope.filter(isNonEmptyString).map(globToPrefix);
|
|
1098
|
+
if (fence.length === 0) return [];
|
|
1099
|
+
return paths.filter(isNonEmptyString).filter((raw) => {
|
|
1100
|
+
const n = normPrefix(raw);
|
|
1101
|
+
return !fence.some((f) => f === '.' || n === f || n.startsWith(`${f}/`));
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1060
1105
|
/**
|
|
1061
1106
|
* Kinds a declaration may carry AT MOST ONCE, as data beside the catalogue.
|
|
1062
1107
|
*
|
|
@@ -1154,7 +1199,9 @@ function readPath(p, dotted) {
|
|
|
1154
1199
|
* @param {{catalogue?: Record<string, KindSpec>, listing?: string[]|null,
|
|
1155
1200
|
* guards?: {name: string, kind: string, params: Record<string, any>, fill: string[]}[]|null,
|
|
1156
1201
|
* envOwned?: string[]|null, envInjected?: Record<string, string>|null, deferListing?: boolean,
|
|
1157
|
-
* verdictType?: string|null}} [opts]
|
|
1202
|
+
* verdictType?: string|null, writeScope?: string[]|null}} [opts]
|
|
1203
|
+
* `writeScope` — the job's signed write fence. Every path a judged stage judges must sit inside it
|
|
1204
|
+
* ({@link judgedOutsideFence}); absent, the rule is not run (the plain-folder deferral has no fence).
|
|
1158
1205
|
* `listing` — repo-relative paths at the seed (`git ls-tree -r --name-only`).
|
|
1159
1206
|
* `guards` — the picked class's injected guards (`classGuards`).
|
|
1160
1207
|
* `verdictType` — the class the USER picked (`VERDICT_CLASSES`).
|
|
@@ -1171,7 +1218,7 @@ function readPath(p, dotted) {
|
|
|
1171
1218
|
export function validateDeclaration(declaration, opts = {}) {
|
|
1172
1219
|
const {
|
|
1173
1220
|
catalogue = KIND_CATALOGUE, listing = null, guards = null, envOwned = null, envInjected = null,
|
|
1174
|
-
verdictType = null,
|
|
1221
|
+
verdictType = null, writeScope = null,
|
|
1175
1222
|
} = opts;
|
|
1176
1223
|
const deferListing = opts.deferListing === true;
|
|
1177
1224
|
/** @type {Red[]} */
|
|
@@ -1314,6 +1361,18 @@ export function validateDeclaration(declaration, opts = {}) {
|
|
|
1314
1361
|
} else kindOwners.set(s.kind, label);
|
|
1315
1362
|
}
|
|
1316
1363
|
|
|
1364
|
+
// A judged stage may only judge what the run can fix: a path outside the write fence is a
|
|
1365
|
+
// finding the worker can never act on (run mv13ery3: bin/pulselog.js, fence src/**).
|
|
1366
|
+
if (s.kind === JUDGED_FLOOR_KIND && isObj(s.params)) {
|
|
1367
|
+
const outside = judgedOutsideFence(s.params.paths, writeScope);
|
|
1368
|
+
if (outside.length > 0) {
|
|
1369
|
+
red('judged-outside-write-scope', `${at}.params.paths`, `stage "${label}" judges ${outside.join(', ')}, but the `
|
|
1370
|
+
+ `job may only edit ${writeScope?.join(', ')}. A check that reports on a file the run is fenced out of can `
|
|
1371
|
+
+ 'never be satisfied — every finding on it is one the worker cannot fix. Name files inside the write fence',
|
|
1372
|
+
{ stage: label, outside, fence: (writeScope ?? []).map(globToPrefix) });
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1317
1376
|
// ── RULING 5, AS LAW: judged and human stages are `offer: false`, and the
|
|
1318
1377
|
// law is checked rather than applied. `checkMenu` filters `offer !== false`,
|
|
1319
1378
|
// so a declaration that says nothing is silent about a rule the runner
|
package/src/authorjob.js
CHANGED
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
// in the stored declaration (forward-compat point 3), and the suite pins it.
|
|
100
100
|
|
|
101
101
|
import { jobSpecHash, validateJob } from './job.js';
|
|
102
|
-
import { seedAtHead, seedListing, seedRead as runSeedRead, makeSeedTrees,
|
|
102
|
+
import { seedAtHead, seedListing, seedRead as runSeedRead, makeSeedTrees, judgedStages, closeJudges } from './kinds.js';
|
|
103
103
|
// SOFTGREEN modules 4 and 5 — the compile the interview's Q6/Q7 answers land in,
|
|
104
104
|
// and the gate that decides whether the ruler they describe is a ruler.
|
|
105
105
|
import { proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts } from './cardauthor.js';
|
|
@@ -950,6 +950,17 @@ const scrubRed = (r) => /** @type {any} */ (Object.fromEntries(
|
|
|
950
950
|
Object.entries(r).map(([k, v]) => [k, typeof v === 'string' ? redactSecrets(v) : v]),
|
|
951
951
|
));
|
|
952
952
|
|
|
953
|
+
/**
|
|
954
|
+
* THE one spelling of "a passed calibration gate stands in for the seed red": the
|
|
955
|
+
* close carries a judged stage AND the gate ran and passed. A failed or missing
|
|
956
|
+
* calibration never satisfies it, and a close with no judged stage never reaches it.
|
|
957
|
+
* @param {any[]} judged `judgedStages(closeDecl)` @param {any} calibration `gates.calibration`
|
|
958
|
+
* @returns {boolean}
|
|
959
|
+
*/
|
|
960
|
+
export function seedProofIsCalibration(judged, calibration) {
|
|
961
|
+
return judged.length > 0 && calibration?.ok === true;
|
|
962
|
+
}
|
|
963
|
+
|
|
953
964
|
/**
|
|
954
965
|
* GATE 4 — the calibration gate, and the only gate here that spends money.
|
|
955
966
|
*
|
|
@@ -1033,7 +1044,10 @@ async function calibrationGate({ spec, judgedStages, judgeLoop, judgeModel, onJu
|
|
|
1033
1044
|
|
|
1034
1045
|
/** what the signing evidence KEEPS about this gate. The per-case artifacts are
|
|
1035
1046
|
* deliberately not in it — they are already in the signed spec, and a second
|
|
1036
|
-
* copy in the evidence file is a second thing to keep in step.
|
|
1047
|
+
* copy in the evidence file is a second thing to keep in step. Each graded row's
|
|
1048
|
+
* `diag` (F192) keeps the judge's own facts and the decision's reds — scrubbed
|
|
1049
|
+
* and bounded quotes, never an artifact body — so a refusal reads from its own
|
|
1050
|
+
* record. What IS kept is
|
|
1037
1051
|
* WHICH BYTES were certified (`cardHash`/`casesHash`/`setHash`) and BY WHICH
|
|
1038
1052
|
* MODEL (`judgeModel`), which is what makes a judge-model bump detectable at
|
|
1039
1053
|
* all. */
|
|
@@ -1135,16 +1149,15 @@ async function calibrationGate({ spec, judgedStages, judgeLoop, judgeModel, onJu
|
|
|
1135
1149
|
* has already had its chance, and nobody pays to discover a broken
|
|
1136
1150
|
* declaration.
|
|
1137
1151
|
*
|
|
1138
|
-
* D9.3 AND THE JUDGED
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
* its calibration says.
|
|
1152
|
+
* D9.3 AND THE JUDGED CLOSE (2026-08-18 second addendum, ruling 3; hamr: *"fix it now,
|
|
1153
|
+
* we are delivering softgreen, isn't that the whole point?"*; widened 2026-10-09 to mixed
|
|
1154
|
+
* closes, hamr: *"a rubric job should check if judge can judge (calibration) and judge
|
|
1155
|
+
* rules can apply properly to pass ... we can't close everything upfront"*). Gate 3
|
|
1156
|
+
* protects against a close that cannot fail. A judged stage SKIPS the seed read by ruling
|
|
1157
|
+
* 8, so it can never show red there. For ANY close carrying a judged stage (pure or
|
|
1158
|
+
* mixed with mechanical stages), a PASSED calibration gate plays the seed-red role
|
|
1159
|
+
* (`seedProofIsCalibration`). A close with NO judged stage is UNCHANGED: some work stage
|
|
1160
|
+
* must be red at the seed. A failed or missing calibration never satisfies it.
|
|
1148
1161
|
*
|
|
1149
1162
|
* Gates 2 and 3 share ONE execution, and that is stated rather than hidden: at
|
|
1150
1163
|
* job creation the tree IS the seed, so "does every stage run against the real
|
|
@@ -1240,7 +1253,7 @@ export async function prepareSigning({
|
|
|
1240
1253
|
}
|
|
1241
1254
|
// the class rides from the SPEC (PRD v1.57 §2) — gate 1a already refused a spec
|
|
1242
1255
|
// whose class is unknown or locked, so this is the class D5's battery hangs off
|
|
1243
|
-
const dv = validateCloseDecl(spec.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: spec.verdictType });
|
|
1256
|
+
const dv = validateCloseDecl(spec.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: spec.verdictType, writeScope: spec.writeScope });
|
|
1244
1257
|
// the grounded gate's reds quote BOTH untrusted sources — the declaration, and
|
|
1245
1258
|
// the seed listing itself (the listing rule names what really sits beside an
|
|
1246
1259
|
// invented path). The listing half is a channel gate 1a structurally cannot
|
|
@@ -1349,17 +1362,17 @@ export async function prepareSigning({
|
|
|
1349
1362
|
// the close-authoring layer, and this gate is what refuses it BEFORE a
|
|
1350
1363
|
// signature rather than after a paid run.
|
|
1351
1364
|
//
|
|
1352
|
-
// THE JUDGED
|
|
1353
|
-
//
|
|
1354
|
-
//
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
1357
|
-
//
|
|
1358
|
-
//
|
|
1359
|
-
//
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (
|
|
1365
|
+
// THE JUDGED ROUTE (ruling 3, widened 2026-10-09 by hamr: "a rubric job should
|
|
1366
|
+
// check if judge can judge (calibration) and judge rules can apply properly to
|
|
1367
|
+
// pass ... we can't close everything upfront"). A close carrying ANY judged stage
|
|
1368
|
+
// has a work-bearing stage that ruling 8 exempts from the seed read, so the seed
|
|
1369
|
+
// cannot show it red — not because it measures nothing, but because it is not
|
|
1370
|
+
// measured there. Its proof of failability is the calibration gate, which has
|
|
1371
|
+
// just run and passed (a failed one returned above), and which contains signed
|
|
1372
|
+
// cases the pipe must RED as well as ones it must pass. That holds for a PURE
|
|
1373
|
+
// judged close and a MIXED one alike; a close with NO judged stage takes the
|
|
1374
|
+
// original path unchanged (some work stage must be red at the seed).
|
|
1375
|
+
if (workRed.length === 0 && seedProofIsCalibration(judged, base.gates.calibration)) {
|
|
1363
1376
|
// said out loud in the record: this close passed gate 3 on a DIFFERENT proof,
|
|
1364
1377
|
// and a reader must never have to infer which one from an empty workRed list
|
|
1365
1378
|
base.gates.seedVerdict.satisfiedBy = 'calibration';
|