bareloop 0.4.0 → 0.5.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 +111 -0
- package/README.md +10 -10
- package/bareloop.context.md +133 -15
- package/package.json +2 -2
- package/src/exits.js +163 -0
- package/src/index.js +7 -1
- package/src/interpret.js +25 -22
- package/src/job.js +231 -88
- package/src/plan.js +237 -0
- package/src/planrun.js +677 -0
- package/src/ralph.js +18 -6
- package/src/run.js +41 -4
- package/types/exits.d.ts +51 -0
- package/types/index.d.ts +4 -1
- package/types/interpret.d.ts +98 -0
- package/types/job.d.ts +12 -0
- package/types/plan.d.ts +41 -0
- package/types/planrun.d.ts +49 -0
- package/types/ralph.d.ts +20 -3
- package/types/run.d.ts +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,117 @@ feature lands, **patch** = docs, fixes, scaffolding.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] — 2026-07-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Layer 2 core — the plan-v1 flow (design record
|
|
15
|
+
`docs/plans/2026-07-21-layer-2-plan-v1-design.md`; premise validated in F46).**
|
|
16
|
+
The semantic converter, built:
|
|
17
|
+
- **job-v1 four-field plan shape** (`src/job.js`): `goal` / `verdictType`
|
|
18
|
+
(`green|soft-green|hitl` frozen radio — v1 admits `green` only; a locked type is a
|
|
19
|
+
`request-red` with the type as a structured `verb` field) / `close` / `checks[]`
|
|
20
|
+
(operator-SIGNED named checks: the predicate-close body + a slug name, same
|
|
21
|
+
validation, same runClose machinery; checks decide nothing and mint nothing) /
|
|
22
|
+
`tools` (the plan ceiling). Exclusive with legacy `steps[]` (`shape-conflict` red);
|
|
23
|
+
`steps[]` is co-existing scaffolding with a staged sunset — archives alongside
|
|
24
|
+
config-v1 when the Layer 2 path proves itself in its battery.
|
|
25
|
+
- **plan-v1 validator** (`src/plan.js`, `validatePlan`): gates the AGENT-authored
|
|
26
|
+
plan against the SIGNED spec — verbs ⊆ ceiling (`verb-escape`, verb as structured
|
|
27
|
+
data), rounds ≤ shell cap, targets/scopes inside the fence, exits from the closed
|
|
28
|
+
menu only (`exit-illegal`), `check-passes` resolving against the signed menu
|
|
29
|
+
(`check-unknown` names the menu), no `dependsOn` (strictly sequential — an inert
|
|
30
|
+
knob is a fake contrast lever), AND-only exit composition max 2, and the F17
|
|
31
|
+
pairing law (check-passes on a write step demands tree-changed — the seed tree is
|
|
32
|
+
green). Fails CLOSED on a missing/non-plan-shape job (`job-invalid`).
|
|
33
|
+
- **exit evaluator** (`src/exits.js`, `snapshotScope`/`evalExits`): the shell's own
|
|
34
|
+
fixed code for `artifact-written` / `tree-changed` / `json-valid` /
|
|
35
|
+
`check-passes`. Outcome, never intent: sha256 snapshots, identical re-writes are
|
|
36
|
+
not changes (F43), git status never consulted (F45). Instrument faults ride out as
|
|
37
|
+
`fault` by runClose verdict name — escalated, never fed to the worker as a gap.
|
|
38
|
+
- **the judge seam** (`src/ralph.js`): `ralph({ judge })` — the PRD v1.12 §4
|
|
39
|
+
generalization; shell-injected, inexpressible in any config or plan. Same verdict
|
|
40
|
+
vocabulary, so the forbidden zone, F32 worker-crash routing (a check crashed by the
|
|
41
|
+
worker's own test feeds back — the F46 mechanism), and cap taxonomy are unchanged.
|
|
42
|
+
- **the plan executor** (`src/planrun.js`, `runPlan`): close precheck
|
|
43
|
+
(`already-green` distinct, F17) → checks preflight ($0, before tokens) → read-only
|
|
44
|
+
SCOUT → PLAN (one redraft with reds fed back) → sequential micro-loops with
|
|
45
|
+
exit-gap feedback → ONE replan (exhaustion only) → the operator's close with one
|
|
46
|
+
bounded fix loop. Every round metered `worker-round` with a phase label (F12);
|
|
47
|
+
prompt contract v1.12 §5 mutation-proven; `plan-executed` (plan-as-executed,
|
|
48
|
+
design law #2) on the spine.
|
|
49
|
+
- **runJob dispatch** (`src/run.js`): a plan-shape spec routes through the ONE
|
|
50
|
+
runJob entry — same approval gate, smoke, ledger, and job-end money contract.
|
|
51
|
+
New outcomes: `already-green | plan-red | check-red | close-red`.
|
|
52
|
+
- 139 new tests (503 total), TDD throughout, 3 targeted mutations fired and killed.
|
|
53
|
+
Built and integration-tested against scripted providers.
|
|
54
|
+
- **Layer 2 rung ACCEPTED — the real-model acceptance battery (F47; prereg
|
|
55
|
+
`docs/02-experiments/TESTGEN-PREREG.md` §2026-07-22a/b).** Job #4 (TESTGEN) run
|
|
56
|
+
through the REAL plan flow (`runJob → runPlan`: scout → the agent DRAFTS the plan →
|
|
57
|
+
validator gates → per-step check-loops → outer grader), on `anthropic-api`,
|
|
58
|
+
claude-sonnet-5, vs F39's baseline (0 conversion) and the F46 POC (hardwired).
|
|
59
|
+
**3/3 valid acting rows converted (≥2/3 bar) → accepted; 3/3 cleared the 45% bar
|
|
60
|
+
(67.5/55/55, vs the POC's 27.5/40/37.5 with 0 at 45); 3/3 the agent composed the
|
|
61
|
+
`check-passes(clean-run)` exit ITSELF** (the one thing the POC could not test). Every
|
|
62
|
+
green driven by the step check-loop alone; all writes fenced, source frozen, secrets
|
|
63
|
+
clean; the F45 spend guard stopped an unpriced casualty. 7 provider-red casualties
|
|
64
|
+
across an Overloaded window (excluded as evidence), $27.36 of a $30 cap. This trips
|
|
65
|
+
the "path closes green end-to-end" milestone: **`steps[]` and config-v1 sunset on
|
|
66
|
+
landing.** Driver `scripts/run-battery-l2accept.mjs` (gained `--need`/`--priorUsd`
|
|
67
|
+
for a multi-run continuation under one governed cap).
|
|
68
|
+
- **Module 4d — native clipipe worker surface (BA-16; `bare-agent` → `^0.33.0`).** The
|
|
69
|
+
plan flow now runs on TWO surfaces: the `Loop` (API, unchanged) and, when
|
|
70
|
+
`job.provider === 'clipipe-subscription'`, the `claude` CLI's NATIVE tool channel — the
|
|
71
|
+
subscription path (no metered API). Since native governance is constructor-time and
|
|
72
|
+
per-worker, the runner takes a `nativeProvider` FACTORY (`{policy, onTurn?, maxTurns,
|
|
73
|
+
hasTools}) => provider`) it calls fresh per worker: `hasTools:true` → native tool mode
|
|
74
|
+
(the SAME `wireGate` fence clips onto the provider — a live POC proved an out-of-scope
|
|
75
|
+
write is DENIED); `hasTools:false` → the toolless drafter runs metered claude-json TEXT
|
|
76
|
+
mode (a native session reports no cost — this path keeps the drafter's spend visible).
|
|
77
|
+
Money reconciles per session (accounted `worker-round` = session total; `worker-turn` =
|
|
78
|
+
attribution); `max_turns` is a bounded attempt, not an escalation; a missing factory is
|
|
79
|
+
`interpreter-red`, never a silent fall-back to the API. 5 native tests + a live
|
|
80
|
+
end-to-end smoke on the real CLI (green, all workers metered, `spendComplete` honest).
|
|
81
|
+
|
|
82
|
+
- **Native read-cap — the CLI truncation fix (F48; `bare-agent` → `^0.33.1`, BA-17
|
|
83
|
+
ranged read).** On the `clipipe-subscription` surface the `claude` CLI truncates a large
|
|
84
|
+
tool result (~40–50KB, measured) BEFORE the model sees it — spilling the remainder to a
|
|
85
|
+
fenced-off `tool-results/` file and wrapping it in a "read in chunks" notice the model
|
|
86
|
+
distrusts as injection — so a whole-file `shell_read` of a large file blinded the native
|
|
87
|
+
worker (0-write stall). The runner now bounds the native `shell_read` result below the CLI
|
|
88
|
+
cap (`NATIVE_READ_CAP`) and returns a TRUSTED notice steering to `ctx_get` ranged retrieval,
|
|
89
|
+
plus a native-only strategy line; the API path is untouched (full result rides into context).
|
|
90
|
+
Measured: **0 → 7 writes** on the real job. Cross-surface verdict (F48): the native surface
|
|
91
|
+
is capable at the STEP but did not carry job #4 to a grade — 0/2 acting rows vs the API's
|
|
92
|
+
3/3, and a 3.5× budget raise ($8→$28) was refuted (escalated on the F39 semantic-stall at
|
|
93
|
+
$7). IN only as a babysat, $0-marginal-billing fallback; **only the `anthropic-api` surface
|
|
94
|
+
is guaranteed.** Local LLMs remain deferred and unmeasured.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- **Layer 2 pre-release review (F48) — 4 fixes, 2 correctness** (TDD, failing-then-passing
|
|
99
|
+
test each; full suite 519/519): (1) a provider-red/gate-red raised DURING a step's micro-loop
|
|
100
|
+
was collapsed to `step-red:<id>` — a transport CASUALTY recorded as a capability failure and
|
|
101
|
+
missing the F44 `spendComplete:false` floor; each terminal category now rides out under its own
|
|
102
|
+
name so the returned outcome and the emitted escalation agree (F11). (2) `tree-changed` counted
|
|
103
|
+
a sibling scope's files as deletions when a step had ≥2 tree-changed exits (merged snapshot),
|
|
104
|
+
falsely passing an unchanged scope — deletions are now scoped to the exit's own prefix. (3)
|
|
105
|
+
abandoned-plan artifacts no longer ride forward as the new plan's "prior steps' results" after a
|
|
106
|
+
replan. (4) dead `isUnpriced()` sub-conditions removed from the replan/cap-halt terminal (the
|
|
107
|
+
step-end guard already returns `pricing-red` first).
|
|
108
|
+
- **Layer 2 whole-branch review — 8 doctrine-restoring fixes to the plan flow** (all in the
|
|
109
|
+
graduated Layer 2 code, none in pre-existing modules; validated against source with 0
|
|
110
|
+
refuted, a failing-then-passing test each): a `gold` close validated under `verdictType:
|
|
111
|
+
green` crashed `runPlan` with no `job-end` (now `close-unsupported` before tokens); the
|
|
112
|
+
`check-passes` gap was re-truncated to 400 chars, deleting the gapKeep failing-test names
|
|
113
|
+
(F28 reintroduced — now carried whole); no in-flight `pricing-red` (F6 — now bails at
|
|
114
|
+
scout/plan/step); the plan drafter's Gate budget was frozen pre-execute and reused for the
|
|
115
|
+
replan (now a fresh drafter per `obtainPlan`); a money-gate halt triggered a replan instead
|
|
116
|
+
of stopping (F45 — now gated on funds, drained → honest `cap-halt`); the step-setup catch
|
|
117
|
+
recorded a category the escalation contradicted (F11 — now agreed); the plan branch dropped
|
|
118
|
+
F44's `spendComplete:false` on a transport-throw `provider-red`; a write-only tool ceiling
|
|
119
|
+
validated, blinding the scout (now requires ≥1 read-capable verb).
|
|
120
|
+
|
|
10
121
|
## [0.4.0] — 2026-07-21
|
|
11
122
|
|
|
12
123
|
### Added
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<img src="https://img.shields.io/github/package-json/v/hamr0/bareloop?label=version&color=2a4f8c" alt="version (auto from package.json)">
|
|
15
15
|
<img src="https://img.shields.io/badge/license-Apache%202.0-2a4f8c" alt="license: Apache 2.0">
|
|
16
|
-
<img src="https://img.shields.io/badge/status-Layer%
|
|
16
|
+
<img src="https://img.shields.io/badge/status-Layer%202%20%C2%B7%20accepted-2a6d3a" alt="status: Layer 2, accepted">
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
**"Automate this job — I don't know the best workflow."** For tasks that are **repeated,
|
|
@@ -25,15 +25,15 @@ inheritance with ledger-counted attribution.
|
|
|
25
25
|
The pitch in one line: **workflows that earn their own design, with receipts** — every
|
|
26
26
|
inherited rule carries the green that minted it and the contrast that attributed it.
|
|
27
27
|
|
|
28
|
-
> **Status: Layer
|
|
29
|
-
> signed job spec
|
|
30
|
-
>
|
|
31
|
-
>
|
|
32
|
-
>
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
> kill-switch question.
|
|
28
|
+
> **Status: Layer 2 — accepted.** The agent now authors its own workflow: `runJob()`
|
|
29
|
+
> takes a signed job spec, surveys the repo, **drafts a validated plan of bounded steps**
|
|
30
|
+
> (each with an operator-signed self-check it may reference but never author), and runs it
|
|
31
|
+
> under an un-gameable outer close and one budget it can only tighten. On the real-model
|
|
32
|
+
> acceptance battery (F47) the emergent flow converted a job the same worker failed 4/4
|
|
33
|
+
> before — 3/3 runs, clearing the owned bar every time, composing its own check exits. The
|
|
34
|
+
> within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). Next: **N3 —
|
|
35
|
+
> executed inheritance**, where the workflow persists and improves across runs (the
|
|
36
|
+
> ladder's kill-switch question).
|
|
37
37
|
|
|
38
38
|
## Quick start
|
|
39
39
|
|
package/bareloop.context.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# bareloop — Integration Guide
|
|
2
2
|
|
|
3
|
-
> **Current through
|
|
4
|
-
> draft-PR hitl step
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
>
|
|
3
|
+
> **Current through Layer 2 (ACCEPTED, F47)** (headless single-job loop: `runJob`, text +
|
|
4
|
+
> tool middles, the draft-PR hitl step; the plan-v1 flow — four-field job shape, agent-
|
|
5
|
+
> authored validated plans, in-run check exits — validated end-to-end on the real-model
|
|
6
|
+
> acceptance battery: 3/3 conversion, 3/3 over the owned bar, the agent composing its own
|
|
7
|
+
> check exits). The plan shape is the primary path; legacy `steps[]` co-exists but sunsets
|
|
8
|
+
> on landing. Runs on two worker surfaces — the API `Loop` and, for
|
|
9
|
+
> `clipipe-subscription`, the CLI's native tool channel (module 4d). API sections fill in
|
|
10
|
+
> as build-ladder rungs land (PRD §10). What is settled — the boundary, the architecture,
|
|
11
|
+
> the refusals, the constraints — is settled for good. Per LIBRARY_CONVENTIONS §3 this
|
|
12
|
+
> file ships with the package and is the complete adopter contract; the README is only
|
|
13
|
+
> the pitch.
|
|
8
14
|
|
|
9
15
|
## What this is
|
|
10
16
|
|
|
@@ -80,6 +86,22 @@ unknown-field reds.
|
|
|
80
86
|
| `steps[].tools` | unique subset of `read\|grep\|write\|edit\|recall\|get` | the SPEC-side tool grant (`TOOL_MENU`, frozen; defaults to the full menu) — the drafted config cannot express mode or tools; `edit` (BA-13) is the anchored exact-once replace, judged by the SAME writeScope fence as `write`; requesting `run` (`LOCKED_TOOLS`) reds with the DISTINCT code `request-red` (locked-but-listed: the red IS the admission evidence the ledger tallies; a typo stays `invalid-value`) |
|
|
81
87
|
| `escalation` | `{ mode: "decision-ready" }` | the pain channel is not optional |
|
|
82
88
|
|
|
89
|
+
**The plan shape (Layer 2, design record 2026-07-21).** A job-v1 spec declares EITHER the
|
|
90
|
+
`steps[]` chain above OR the four-field plan shape — never both (`shape-conflict` red).
|
|
91
|
+
In the plan shape the AGENT authors the step plan at run time (gated by `validatePlan`);
|
|
92
|
+
the human signs only:
|
|
93
|
+
|
|
94
|
+
| field | shape | notes |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `goal` | non-empty text | what the agent plans against |
|
|
97
|
+
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). v1 ADMITS only `green`; declaring `soft-green`/`hitl` reds `request-red` with the type as a structured `verb` field (declared-but-locked — the tool-menu pattern) |
|
|
98
|
+
| `close` | a close object (table below) | ONE close, the only truth; `green` demands a hard-class close (`close-hierarchy` red on a rubric/hitl close) |
|
|
99
|
+
| `checks` | optional array of `{ name, cmd, expect, judged?, gapKeep? }` | the operator-SIGNED named-check menu: the predicate-close body plus a kebab slug `name`, validated by the same rules, executed under the same runClose machinery. Plans reference them via the `check-passes(name)` exit; the agent can never author, edit, or compose one. **Checks decide nothing and mint nothing** — a check result is a progress gate and a gap source only |
|
|
100
|
+
| `tools` | optional unique subset of `TOOL_MENU` | the CEILING every plan step's grant must fit inside (defaults to the full menu); `run` reds `request-red` here exactly as on a step grant. On a legacy `steps[]` spec this field is an `invalid-value` red (steps grant tools per step) |
|
|
101
|
+
|
|
102
|
+
`steps[]` is co-existing scaffolding with a staged sunset: it archives alongside
|
|
103
|
+
config-v1 once the Layer 2 path proves itself in its battery — not before.
|
|
104
|
+
|
|
83
105
|
**Close types and the hierarchy** (a close is data, never code; verdict-class laundering
|
|
84
106
|
is a named red `close-hierarchy`):
|
|
85
107
|
|
|
@@ -140,18 +162,36 @@ drafted workflow config cannot express it (unknown-field red). The gap path also
|
|
|
140
162
|
**both** streams (stdout + stderr), so a failure printed to stdout survives stderr noise
|
|
141
163
|
(F28's adjacent hazard — the old `err || out` returned stderr alone and lost it).
|
|
142
164
|
|
|
143
|
-
|
|
165
|
+
**The plan document (`schema: "plan-v1"`, AGENT-authored — the only document the emergent
|
|
166
|
+
middle writes; `validatePlan` gates it against the SIGNED job spec before tokens burn):**
|
|
167
|
+
|
|
168
|
+
| field | shape | notes |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `steps` | ordered array, 1..8 (`MAX_PLAN_STEPS`) | strictly sequential — array order IS the order; no `dependsOn` (unknown-field red: an inert knob is a fake contrast lever) |
|
|
171
|
+
| `steps[].id` | kebab slug, unique | |
|
|
172
|
+
| `steps[].action` | non-empty text | the step's task — the worker sees only this step |
|
|
173
|
+
| `steps[].tools` | non-empty unique subset of the SPEC ceiling | a verb beyond the ceiling reds `verb-escape` with the verb as structured data (overreach, distinct from the operator-side `request-red`); `run` is `verb-escape` at every layer |
|
|
174
|
+
| `steps[].rounds` | int 1..shell cap (default 40) | the step's per-attempt tool-round bound (the Gate's `maxTurns` natively) |
|
|
175
|
+
| `steps[].target` | path inside the fence | v1.18 deliverable; REQUIRED on write-granted steps |
|
|
176
|
+
| `steps[].exit` | 1..2 items (`MAX_EXITS_PER_STEP`), ALL must pass (AND-only, no OR/NOT) | closed menu (`EXIT_TYPES`): `artifact-written(path, pattern?)` · `tree-changed(scope)` · `json-valid(path)` · `check-passes(name)`. `check-passes` must name a SIGNED check (`check-unknown` red names the signed menu); on a write-granted step it must be paired with `tree-changed` (`exit-illegal` — the seed tree is green, a lone check would pass untouched, F17/F46). Exits verify FORM, not truth — progress gates; the operator's close stays the one arbiter |
|
|
177
|
+
|
|
178
|
+
Red vocabulary (all three validators): `parse-error`, `unknown-field`, `missing-required`,
|
|
144
179
|
`invalid-value`, `bounds`, `duplicate-id`, `close-type`, `close-hierarchy`,
|
|
145
|
-
`secret-literal`, `scope-escape`, `fence-invalid` (a malformed `jobWriteScope` fence — attributed to `jobWriteScope`, never the workflow config),
|
|
146
|
-
`
|
|
180
|
+
`secret-literal`, `scope-escape`, `fence-invalid` (a malformed `jobWriteScope` fence — attributed to `jobWriteScope`, never the workflow config), `shape-conflict` (both job shapes declared),
|
|
181
|
+
`request-red` (locked-but-listed: a locked tool or verdictType — admission demand the
|
|
182
|
+
ledger tallies), plus the workflow-side verb reds (`verb-illegal`,
|
|
183
|
+
`verb-placement`, `verb-params`, `slot-overflow`) and the plan-side reds (`verb-escape`,
|
|
184
|
+
`exit-illegal`, `check-unknown`, `job-invalid` — a plan validated against a missing or
|
|
185
|
+
non-plan-shape job fails CLOSED). The `secret-literal` sweep is
|
|
147
186
|
defense-in-depth against known token shapes — env-only loading remains the law, not the
|
|
148
187
|
sweep.
|
|
149
188
|
|
|
150
189
|
## Public API
|
|
151
190
|
|
|
152
|
-
*Landed through N2 (spine + shell +
|
|
153
|
-
+ extractor + runJob). Still TBD: N3
|
|
154
|
-
|
|
191
|
+
*Landed through N2 + the Layer 2 core (spine + shell + three validators + interpreter
|
|
192
|
+
with text/tool middles + the plan executor + extractor + runJob). Still TBD: N3
|
|
193
|
+
(contrast-bit extractor live), N4 (verdict classes — gold/rubric close EXECUTION), N5
|
|
194
|
+
(scheduler + budget ops + CLI), N6 (panel).*
|
|
155
195
|
|
|
156
196
|
### `makeSpine(file)` → `emit(type, data?)` — `src/spine.js`
|
|
157
197
|
|
|
@@ -159,7 +199,7 @@ Append-only JSONL event emitter bound to one file. `seq` monotonic per spine, `t
|
|
|
159
199
|
last. Consumers are pure listeners; nothing reads the file back. Returns each event as
|
|
160
200
|
written.
|
|
161
201
|
|
|
162
|
-
### `ralph({ middle, close
|
|
202
|
+
### `ralph({ middle, close?, judge?, capRuns, emit, redact?, closeTimeoutMs?, cwd?, expect?, judged?, gapKeep?, workerWrites? })` → `'green' | 'escalated'` — `src/ralph.js`
|
|
163
203
|
|
|
164
204
|
The dumb outer shell: `while close-red and under-cap: run the middle`. `close` is an argv
|
|
165
205
|
whose exit code is truth (`runClose` is also exported); the red gap text feeds the next
|
|
@@ -205,6 +245,15 @@ to the gate audit's allow-decision write/edit lines (run_id-scoped); no seam or
|
|
|
205
245
|
keeps the old behavior — an instrument crash still escalates `close-crashed`, never retried.
|
|
206
246
|
Measured motivation: battery pass 1 (F31) lost 4 of 7 rows to exactly this escalation.
|
|
207
247
|
|
|
248
|
+
**The judge seam (Layer 2, PRD v1.12 §4).** `judge?: async () => {verdict, gap?, detail?}`
|
|
249
|
+
— a SHELL-injected replacement for `runClose`, so a plan step's micro-loop is judged by
|
|
250
|
+
the exit evaluator instead of a command. It returns the same verdict vocabulary, so the
|
|
251
|
+
forbidden zone, F32 worker-crash routing (a check crashed by the worker's own broken test
|
|
252
|
+
feeds back — the F46 mechanism), and the cap taxonomy apply unchanged. `close` becomes
|
|
253
|
+
optional when `judge` is present; the seam is wired by `runPlan` only and is
|
|
254
|
+
inexpressible in any config or plan — the agent never authors its judge any more than
|
|
255
|
+
its close.
|
|
256
|
+
|
|
208
257
|
Escalations are decision-ready (category, options, spend); cap-halt is its own
|
|
209
258
|
category, never merged with "wrong". A thrown middle is relayed by its `category`
|
|
210
259
|
property (`cap-halt`, `gate-red`, …); an unnamed throw is `interpreter-red`. Close output
|
|
@@ -235,9 +284,35 @@ a leak on the very output it was guarding.
|
|
|
235
284
|
The operator-owned sibling (never an extension) of `validateConfig`: validates a
|
|
236
285
|
`job-v1` spec — see **All options** for the full schema, close types, and hierarchy.
|
|
237
286
|
Never throws on JSON text or plain parsed data (the ingest contract); returns the
|
|
238
|
-
parsed spec on ok, `null` on any red.
|
|
287
|
+
parsed spec on ok, `null` on any red. Validates BOTH job shapes (legacy `steps[]` and
|
|
288
|
+
the Layer 2 plan shape — see the option tables above). Menus exported:
|
|
239
289
|
`CLOSE_TYPES`, `CLASSES`, `CLASS_BY_CLOSE`, `GOLD_COMPARE`, `CADENCE_UNITS`,
|
|
240
|
-
`PROVIDERS`, `CONDITION_KEYS`, `STEP_MODES`, `TOOL_MENU`, `LOCKED_TOOLS
|
|
290
|
+
`PROVIDERS`, `CONDITION_KEYS`, `STEP_MODES`, `TOOL_MENU`, `LOCKED_TOOLS`,
|
|
291
|
+
`VERDICT_TYPES`, `LOCKED_VERDICTS`.
|
|
292
|
+
|
|
293
|
+
### `validatePlan(input, { job, maxStepRounds? })` → `{ ok, reds, plan }` — `src/plan.js`
|
|
294
|
+
|
|
295
|
+
The third validator: gates the AGENT-authored plan doc (`schema: "plan-v1"`) against the
|
|
296
|
+
SIGNED job spec before tokens burn — the ceiling, the fence, and the checks menu all come
|
|
297
|
+
from `job` (a missing or non-plan-shape job fails CLOSED, `job-invalid`). Never throws;
|
|
298
|
+
same `{ code, path, detail }` red shape as its siblings; `verb-escape` reds carry the
|
|
299
|
+
escaping verb as a structured `verb` field (the ledger keys on it). `maxStepRounds`
|
|
300
|
+
(default 40 — the shell's tool-mode per-attempt bound) ceilings every step's `rounds`.
|
|
301
|
+
Menus exported: `EXIT_TYPES`, `MAX_EXITS_PER_STEP`, `MAX_PLAN_STEPS`, `WRITE_VERBS`.
|
|
302
|
+
|
|
303
|
+
### `snapshotScope(dir, scope)` / `evalExits(exits, { dir, snapshot?, runCheck? })` — `src/exits.js`
|
|
304
|
+
|
|
305
|
+
The shell's own fixed code for the closed exit menu — nothing here executes
|
|
306
|
+
agent-authored text. `snapshotScope` hashes every file under a scope prefix (the
|
|
307
|
+
"before" side of `tree-changed`; a missing dir snapshots empty). `evalExits` is AND-only
|
|
308
|
+
and never short-circuits — the result names EVERY failing wall (`{ pass, results }`,
|
|
309
|
+
each result `{ type, pass, detail?, fault? }`). `tree-changed` reads OUTCOME (bytes vs
|
|
310
|
+
the snapshot): an identical re-write is NOT a change (F43) and git status is never
|
|
311
|
+
consulted (F45). `artifact-written` rejects zero-byte files. `check-passes` delegates
|
|
312
|
+
through the `runCheck` seam (the runner wires runClose); an unwired or crashed seam
|
|
313
|
+
fails CLOSED with `fault` carrying a runClose verdict name — an instrument fault
|
|
314
|
+
escalates through `CLOSE_FAULTS`, never masquerades as worker feedback. Failing details
|
|
315
|
+
are counts and names only, never file bodies (they ride the append-only spine).
|
|
241
316
|
|
|
242
317
|
### `jobSpecHash(job)` / `checkApproval(job, approvals)` — `src/job.js`
|
|
243
318
|
|
|
@@ -310,7 +385,7 @@ default-enabled until a stuck job (Layer 2, or a manufactured-fixation probe) sh
|
|
|
310
385
|
beats OFF. Flip the default to `true` the day that evidence lands; a `root-injected` event
|
|
311
386
|
on your spine (when you pass `layerRoot: true`) is a signal worth reading, not noise.
|
|
312
387
|
|
|
313
|
-
### `runJob(spec, { approvals, workdir, provider, emit, target?, capRuns?, shellCapUsd?, closeTimeoutMs?, execCmd?, layerRoot? })` → outcome — `src/run.js`
|
|
388
|
+
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, emit, target?, capRuns?, shellCapUsd?, closeTimeoutMs?, execCmd?, layerRoot? })` → outcome — `src/run.js`
|
|
314
389
|
|
|
315
390
|
The N2 runner — the shell's top layer; composes everything below it and interprets
|
|
316
391
|
nothing itself. Sequence: **approval gate** (human-signs-always — refuses an unapproved
|
|
@@ -349,6 +424,49 @@ launder a missing `spentUsd` into `$0`. A text-mode job invoked without `opts.ta
|
|
|
349
424
|
before ANY provider call (and `interpret` itself throws a TypeError for direct
|
|
350
425
|
callers) — reds-before-tokens applies to the call, not just the spec.
|
|
351
426
|
|
|
427
|
+
**The plan flow (Layer 2).** A plan-shape spec routes through the SAME `runJob` entry —
|
|
428
|
+
same approval gate, same smoke, same metered ledger, same job-end money contract
|
|
429
|
+
(`job-start` carries `shape: 'plan'` + the goal instead of a steps list; no `opts.target`
|
|
430
|
+
needed — plan steps are tool-mode by construction). The flow (`runPlan`, also exported
|
|
431
|
+
for direct callers who own their own ledger): **close precheck** (`already-green` is a
|
|
432
|
+
DISTINCT zero-token outcome; a forbidden-zone verdict escalates before spend) → **checks
|
|
433
|
+
preflight** (every SIGNED check runs once at $0 — an unrunnable check is a `check-red`
|
|
434
|
+
stop before tokens, not a fault mid-plan) → **SCOUT** (read-only by construction: the
|
|
435
|
+
write verbs are not in its menu; hard-bounded rounds) → **PLAN** (the decompose call —
|
|
436
|
+
the planner never sees the repo, only the scout blob; drafted against a schema
|
|
437
|
+
description with check NAMES only; `validatePlan` gates it, one redraft with the reds
|
|
438
|
+
fed back, then `plan-red`) → **EXECUTE** (strictly sequential micro-loops: `ralph` with
|
|
439
|
+
the exit-evaluator judge; tree snapshots at step start; the gap names every failing
|
|
440
|
+
wall — mechanical genre, F46's measured mechanism; artifacts feed forward labeled by
|
|
441
|
+
step id) → **ONE replan**, triggered by exhaustion only (an instrument stop never
|
|
442
|
+
replans) → **the operator's close**, a red feeding ONE bounded fix loop judged by the
|
|
443
|
+
REAL close. `plan-executed` (the plan-as-executed record, design law #2) lands on the
|
|
444
|
+
spine on every path that executed steps. Additional outcomes: `already-green |
|
|
445
|
+
plan-red | check-red | close-red`. Worker prompts hold the v1.12 §5 contract
|
|
446
|
+
(mutation-proven): the absolute repo root, the step's action/target, prior artifacts,
|
|
447
|
+
the gap — NEVER the budget, the close command, a check's command, or the arbiter's books.
|
|
448
|
+
|
|
449
|
+
**Two worker surfaces — API and native clipipe (BA-16, module 4d).** The plan flow is
|
|
450
|
+
provider-agnostic: the close, the checks, and the exit evaluator are commands and form
|
|
451
|
+
checks, so ONLY the worker differs. `job.provider === 'clipipe-subscription'` drives tools
|
|
452
|
+
NATIVELY (the `claude` CLI owns the turn cycle) instead of through the `Loop`; every other
|
|
453
|
+
provider runs the Loop unchanged (byte-identical — the API path cannot regress). Native
|
|
454
|
+
governance is **constructor-time and per-worker**, so the runner cannot reuse one injected
|
|
455
|
+
instance: pass `opts.nativeProvider`, a FACTORY the runner calls fresh per worker as
|
|
456
|
+
`({ policy, onTurn?, maxTurns, hasTools }) => provider`. Return the right mode by `hasTools`:
|
|
457
|
+
`true` → native tool mode (`toolProtocol:'claude-mcp'`, wire the gate's `policy` + `onTurn` +
|
|
458
|
+
`maxTurns` onto the provider — the SAME `wireGate` fence, so a write outside the scope is
|
|
459
|
+
DENIED at the bridge, live-proven); `false` → the toolless drafter, where a native session
|
|
460
|
+
reports NO cost, so return a metered claude-json TEXT provider (`--output-format json`,
|
|
461
|
+
`parse:'claude-json'`) — never an unmetered, invisible-spend session. A missing factory on a
|
|
462
|
+
`clipipe-subscription` job is `interpreter-red`, never a silent fall-back to the metered API.
|
|
463
|
+
Money reconciles PER SESSION (the CLI prices the session, `costUsd` null per turn,
|
|
464
|
+
authoritative at close): the accounted `worker-round` is the session total, per-turn
|
|
465
|
+
`worker-turn` events are attribution only. `maxTurns` (→ CLI `--max-turns`) is the
|
|
466
|
+
per-attempt bound and surfaces `max_turns` as a BOUNDED attempt (judged, gap fed forward),
|
|
467
|
+
the native analog of `loop.stop()`. Live-validated green end-to-end on the real CLI; the
|
|
468
|
+
Loop path is untouched.
|
|
469
|
+
|
|
352
470
|
**Resume-to-cap (close-first skip):** every predicate step runs its close FIRST, before
|
|
353
471
|
any tokens (`close-precheck` on the spine, output scrubbed at capture like every close).
|
|
354
472
|
Already-green skips the step for zero tokens as a DISTINCT record — `step-end` outcome
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build:types"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"bare-agent": "^0.
|
|
26
|
+
"bare-agent": "^0.33.1",
|
|
27
27
|
"bareguard": "^0.12.0",
|
|
28
28
|
"litectx": "^0.29.1"
|
|
29
29
|
},
|
package/src/exits.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Layer 2: the exit evaluator — the SHELL's own fixed code for the closed
|
|
2
|
+
// exit menu (PRD v1.12 §3). An exit is declarative data; nothing here ever
|
|
3
|
+
// executes agent-authored text. Doctrine wired in, not promised:
|
|
4
|
+
//
|
|
5
|
+
// - exits verify FORM, not truth: progress gates only; the operator's close
|
|
6
|
+
// stays the one arbiter (checks decide nothing, mint nothing).
|
|
7
|
+
// - `tree-changed` reads OUTCOME — real bytes vs a pre-step snapshot taken
|
|
8
|
+
// by `snapshotScope` — never git status (always shows the planted bug,
|
|
9
|
+
// F45) and never gate intent (the F43 identical-refire trap: an allowed
|
|
10
|
+
// write that lands the same bytes is NOT a change).
|
|
11
|
+
// - a failing exit's detail is a MECHANICAL gap (a named wall, a count) —
|
|
12
|
+
// the genre that converts (F38/F46); details carry names and counts only,
|
|
13
|
+
// never file bodies (they ride the spine, which is append-only forever).
|
|
14
|
+
// - `check-passes` delegates through the `runCheck` seam the runner wires
|
|
15
|
+
// to the full runClose machinery; the evaluator itself never spawns. An
|
|
16
|
+
// unwired or throwing check fails CLOSED (the broken-close class: an
|
|
17
|
+
// unrunnable check is a stop, never a silent pass).
|
|
18
|
+
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { globToPrefix } from './validate.js';
|
|
23
|
+
|
|
24
|
+
/** The check gap is ALREADY bounded upstream by runClose's boundGap (head +
|
|
25
|
+
* gapKeep block ≤ 8192 + tail ≈ 10KB, with the failing-test NAMES preserved).
|
|
26
|
+
* The evaluator must NOT re-truncate it: a 400-char clamp deletes exactly those
|
|
27
|
+
* `not ok`/`FAILED` names — the mechanical gap the F46 conversion mechanism
|
|
28
|
+
* feeds the worker (F28 reintroduced). This cap is a defensive backstop above
|
|
29
|
+
* boundGap's own envelope: a normal bounded gap passes through intact, and only
|
|
30
|
+
* a pathological seam that returned an unbounded gap would ever be trimmed. */
|
|
31
|
+
const CHECK_GAP_MAX = 12000;
|
|
32
|
+
|
|
33
|
+
/** @typedef {{ type: string, pass: boolean, detail?: string, fault?: string }} ExitResult
|
|
34
|
+
* `fault` present means the INSTRUMENT could not judge (unwired snapshot/seam,
|
|
35
|
+
* a check in runClose's forbidden zone) — it carries the runClose verdict name
|
|
36
|
+
* ('failed'|'timed-out'|'killed'|'crashed') so the micro-loop escalates through
|
|
37
|
+
* the same CLOSE_FAULTS taxonomy instead of feeding a fake gap to the worker.
|
|
38
|
+
* A plain pass:false with no fault is honest worker feedback. */
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Snapshot the file state under a scope prefix — the "before" side of
|
|
42
|
+
* `tree-changed`. Content hashes, not mtimes: only real bytes count as
|
|
43
|
+
* outcome. A scope directory that does not exist yet snapshots empty (the
|
|
44
|
+
* step creating it IS the change). Never throws on a missing dir.
|
|
45
|
+
* @param {string} dir absolute workdir (the run directory)
|
|
46
|
+
* @param {string} scope a validated scope glob (e.g. `tests/**`)
|
|
47
|
+
* @returns {Promise<Map<string, string>>} relative path → sha256
|
|
48
|
+
*/
|
|
49
|
+
export async function snapshotScope(dir, scope) {
|
|
50
|
+
const prefix = globToPrefix(scope);
|
|
51
|
+
/** @type {Map<string, string>} */
|
|
52
|
+
const snap = new Map();
|
|
53
|
+
/** @type {string[]} */
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = /** @type {string[]} */ (await readdir(join(dir, prefix), { recursive: true, encoding: 'utf8' }));
|
|
57
|
+
} catch {
|
|
58
|
+
return snap; // missing scope dir = empty snapshot, never a throw
|
|
59
|
+
}
|
|
60
|
+
for (const rel of entries) {
|
|
61
|
+
const full = join(dir, prefix, rel);
|
|
62
|
+
let body;
|
|
63
|
+
try { body = await readFile(full); } catch { continue; } // directories and vanished files skip
|
|
64
|
+
snap.set(`${prefix}/${rel.replaceAll('\\', '/')}`, createHash('sha256').update(body).digest('hex'));
|
|
65
|
+
}
|
|
66
|
+
return snap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Evaluate a step's exits — AND-only (decision 8): the step passes iff EVERY
|
|
71
|
+
* listed exit passes. All exits are evaluated (never short-circuit): the gap
|
|
72
|
+
* must name every wall, not the first one hit.
|
|
73
|
+
* @param {Array<Record<string, any>>} exits validated exit items
|
|
74
|
+
* @param {{ dir: string, snapshot?: Map<string, string>, runCheck?: (name: string) => Promise<{pass: boolean, gap?: string}> }} ctx
|
|
75
|
+
* `dir`: the run directory; `snapshot`: the pre-step `snapshotScope` result
|
|
76
|
+
* (required by tree-changed — absent fails closed); `runCheck`: the runner's
|
|
77
|
+
* runClose-backed seam (required by check-passes — absent fails closed).
|
|
78
|
+
* @returns {Promise<{ pass: boolean, results: ExitResult[] }>}
|
|
79
|
+
*/
|
|
80
|
+
export async function evalExits(exits, { dir, snapshot, runCheck }) {
|
|
81
|
+
/** @type {ExitResult[]} */
|
|
82
|
+
const results = [];
|
|
83
|
+
for (const e of exits) {
|
|
84
|
+
results.push(await evalOne(e, { dir, snapshot, runCheck }));
|
|
85
|
+
}
|
|
86
|
+
return { pass: results.every((r) => r.pass), results };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {Record<string, any>} e
|
|
91
|
+
* @param {{ dir: string, snapshot?: Map<string, string>, runCheck?: (name: string) => Promise<{pass: boolean, gap?: string}> }} ctx
|
|
92
|
+
* @returns {Promise<ExitResult>}
|
|
93
|
+
*/
|
|
94
|
+
async function evalOne(e, { dir, snapshot, runCheck }) {
|
|
95
|
+
if (e.type === 'artifact-written') {
|
|
96
|
+
let body;
|
|
97
|
+
try { body = await readFile(join(dir, e.path), 'utf8'); } catch {
|
|
98
|
+
return { type: e.type, pass: false, detail: `${e.path} was not written (does not exist)` };
|
|
99
|
+
}
|
|
100
|
+
if (body.length === 0) return { type: e.type, pass: false, detail: `${e.path} is empty — a zero-byte artifact is not progress` };
|
|
101
|
+
if (e.pattern !== undefined && !new RegExp(e.pattern, 'm').test(body)) {
|
|
102
|
+
return { type: e.type, pass: false, detail: `${e.path} exists but does not match the declared pattern ${e.pattern}` };
|
|
103
|
+
}
|
|
104
|
+
return { type: e.type, pass: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (e.type === 'tree-changed') {
|
|
108
|
+
if (!(snapshot instanceof Map)) {
|
|
109
|
+
// fail CLOSED as a FAULT: without the before-side this instrument is
|
|
110
|
+
// blind — it must never read "changed" (F45) and never feed the miss to
|
|
111
|
+
// the worker as if it were inaction
|
|
112
|
+
return { type: e.type, pass: false, fault: 'failed', detail: `no pre-step snapshot for ${e.scope} — the tree-changed instrument is unwired (instrument fault, not worker inaction)` };
|
|
113
|
+
}
|
|
114
|
+
const now = await snapshotScope(dir, e.scope);
|
|
115
|
+
// deletions count only WITHIN this exit's own scope: the step's `snapshot`
|
|
116
|
+
// is merged across every tree-changed scope, so an unscoped keys() walk
|
|
117
|
+
// would read a SIBLING scope's files (absent from this re-scan) as deletions
|
|
118
|
+
// and pass an unchanged scope by borrowing the other's change.
|
|
119
|
+
const prefix = globToPrefix(e.scope);
|
|
120
|
+
const underScope = (/** @type {string} */ p) => p === prefix || p.startsWith(`${prefix}/`);
|
|
121
|
+
let changed = 0;
|
|
122
|
+
for (const [path, hash] of now) if (snapshot.get(path) !== hash) changed++;
|
|
123
|
+
for (const path of snapshot.keys()) if (underScope(path) && !now.has(path)) changed++; // deletions are real changes
|
|
124
|
+
if (changed === 0) {
|
|
125
|
+
return { type: e.type, pass: false, detail: `0 files changed under ${e.scope} — the tree is byte-identical to the step start (an identical re-write is not a change)` };
|
|
126
|
+
}
|
|
127
|
+
return { type: e.type, pass: true, detail: `${changed} file(s) changed under ${e.scope}` };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (e.type === 'json-valid') {
|
|
131
|
+
let body;
|
|
132
|
+
try { body = await readFile(join(dir, e.path), 'utf8'); } catch {
|
|
133
|
+
return { type: e.type, pass: false, detail: `${e.path} was not written (does not exist)` };
|
|
134
|
+
}
|
|
135
|
+
try { JSON.parse(body); } catch (err) {
|
|
136
|
+
return { type: e.type, pass: false, detail: `${e.path} is not valid JSON: ${String(/** @type {Error} */ (err).message).slice(0, 200)}` };
|
|
137
|
+
}
|
|
138
|
+
return { type: e.type, pass: true };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (e.type === 'check-passes') {
|
|
142
|
+
if (typeof runCheck !== 'function') {
|
|
143
|
+
return { type: e.type, pass: false, fault: 'failed', detail: `check "${e.name}" cannot run — runCheck is not wired (instrument fault: an unrunnable check is a stop, never a silent pass)` };
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const r = await runCheck(e.name);
|
|
147
|
+
if (r.pass) return { type: e.type, pass: true };
|
|
148
|
+
// the seam's fault (a runClose forbidden-zone verdict) rides through by
|
|
149
|
+
// NAME: 'crashed' keeps its F32 routing (worker-crash after writes —
|
|
150
|
+
// the F46 broken-test mechanism), 'timed-out' keeps its own decision
|
|
151
|
+
if (typeof (/** @type {any} */ (r).fault) === 'string') {
|
|
152
|
+
return { type: e.type, pass: false, fault: /** @type {any} */ (r).fault, detail: `check "${e.name}": ${(r.gap ?? '').slice(0, CHECK_GAP_MAX)}` };
|
|
153
|
+
}
|
|
154
|
+
return { type: e.type, pass: false, detail: `check "${e.name}" red: ${(r.gap ?? '').slice(0, CHECK_GAP_MAX)}` };
|
|
155
|
+
} catch (err) {
|
|
156
|
+
return { type: e.type, pass: false, fault: 'failed', detail: `check "${e.name}" crashed the seam: ${String(/** @type {Error} */ (err).message).slice(0, 200)}` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// belt — the validator already reds unknown types; the evaluator must not
|
|
161
|
+
// silently pass what it cannot judge
|
|
162
|
+
return { type: String(e.type), pass: false, fault: 'failed', detail: `unknown exit type "${e.type}" — the evaluator cannot judge it (fail closed)` };
|
|
163
|
+
}
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,13 @@ export { makeSpine } from './spine.js';
|
|
|
16
16
|
// exports map admits only ".", so a deep import cannot reach them).
|
|
17
17
|
export { ralph, runClose, CLOSE_FAULTS } from './ralph.js';
|
|
18
18
|
export { validateConfig, diffPaths, globToPrefix, scanSecrets, LOOP_SHAPES, SLOTS, VERBS } from './validate.js';
|
|
19
|
-
export { validateJob, jobSpecHash, checkApproval, CLOSE_TYPES, CLASSES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, STEP_MODES, TOOL_MENU, LOCKED_TOOLS } from './job.js';
|
|
19
|
+
export { validateJob, jobSpecHash, checkApproval, CLOSE_TYPES, CLASSES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, STEP_MODES, TOOL_MENU, LOCKED_TOOLS, VERDICT_TYPES, LOCKED_VERDICTS } from './job.js';
|
|
20
|
+
// Layer 2: the plan-v1 validator — the agent-authored plan doc's gate; the
|
|
21
|
+
// two-doc split's third validator never happens (plan-v1 gates the PLAN, the
|
|
22
|
+
// job spec stays the arbiter's only home).
|
|
23
|
+
export { validatePlan, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS } from './plan.js';
|
|
24
|
+
export { snapshotScope, evalExits } from './exits.js';
|
|
25
|
+
export { runPlan } from './planrun.js';
|
|
20
26
|
export { interpret, STALL_REDS } from './interpret.js';
|
|
21
27
|
export { extractRules, MAX_RULES, MAX_RULE_CHARS } from './extract.js';
|
|
22
28
|
export { runJob } from './run.js';
|