bareloop 0.5.1 → 0.7.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 +1129 -1
- package/README.md +40 -13
- package/bareloop.context.md +799 -245
- package/package.json +3 -3
- package/src/bridges.js +571 -0
- package/src/clock.js +233 -0
- package/src/exits.js +8 -2
- package/src/index.js +52 -17
- package/src/job.js +231 -108
- package/src/ladder.js +196 -0
- package/src/ledger.js +55 -3
- package/src/plan.js +334 -20
- package/src/planrun.js +1519 -134
- package/src/ralph.js +373 -34
- package/src/reuse.js +1602 -0
- package/src/root.js +32 -12
- package/src/run.js +129 -391
- package/src/selection.js +160 -0
- package/src/spine.js +7 -2
- package/src/stall.js +252 -0
- package/src/tools.js +611 -0
- package/src/trend.js +405 -0
- package/src/validate.js +35 -250
- package/types/bridges.d.ts +198 -0
- package/types/clock.d.ts +131 -0
- package/types/index.d.ts +7 -6
- package/types/job.d.ts +83 -8
- package/types/ladder.d.ts +86 -0
- package/types/ledger.d.ts +3 -1
- package/types/plan.d.ts +86 -7
- package/types/planrun.d.ts +142 -5
- package/types/ralph.d.ts +143 -7
- package/types/reuse.d.ts +388 -0
- package/types/root.d.ts +10 -2
- package/types/run.d.ts +56 -17
- package/types/selection.d.ts +46 -0
- package/types/spine.d.ts +8 -1
- package/types/stall.d.ts +80 -0
- package/types/tools.d.ts +262 -0
- package/types/trend.d.ts +108 -0
- package/types/validate.d.ts +21 -38
- package/src/extract.js +0 -103
- package/src/interpret.js +0 -887
- package/types/extract.d.ts +0 -25
- package/types/interpret.d.ts +0 -201
package/types/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export { makeSpine } from "./spine.js";
|
|
2
2
|
export { runPlan } from "./planrun.js";
|
|
3
3
|
export { runJob } from "./run.js";
|
|
4
|
-
export { ralph, runClose, CLOSE_FAULTS } from "./ralph.js";
|
|
5
|
-
export {
|
|
6
|
-
export { validateJob, jobSpecHash, checkApproval,
|
|
7
|
-
export { validatePlan, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS } from "./plan.js";
|
|
4
|
+
export { ralph, runClose, runStages, CLOSE_FAULTS } from "./ralph.js";
|
|
5
|
+
export { globToPrefix, scanSecrets } from "./validate.js";
|
|
6
|
+
export { validateJob, jobSpecHash, checkApproval, checkMenu, CLOSE_TYPES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, TOOL_MENU, LOCKED_TOOLS, STORE_VERBS, VERDICT_TYPES, LOCKED_VERDICTS } from "./job.js";
|
|
7
|
+
export { validatePlan, stageClose, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS } from "./plan.js";
|
|
8
8
|
export { snapshotScope, evalExits } from "./exits.js";
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
9
|
+
export { BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate, mintBridge, appendGreen, appendRed, loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists } from "./bridges.js";
|
|
10
|
+
export { renderListing, selectionPrompt } from "./selection.js";
|
|
11
|
+
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate } from "./reuse.js";
|
|
11
12
|
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from "./ledger.js";
|
package/types/job.d.ts
CHANGED
|
@@ -17,9 +17,52 @@ export function validateJob(input: object | string, { shellCapUsd }?: {
|
|
|
17
17
|
job: object | null;
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
* the
|
|
22
|
-
*
|
|
20
|
+
* The staged close (PRD v1.28) — the close is an ORDERED LIST of named stages,
|
|
21
|
+
* and the check menu DERIVES from it. The principle: *the user authors the
|
|
22
|
+
* destination, never the road.* A user who cannot describe a workflow can still
|
|
23
|
+
* say what done looks like, and everything downstream of that is derived — which
|
|
24
|
+
* is why `checks[]` no longer exists as a field (hamr: *"there shouldn't be user
|
|
25
|
+
* authoring anywhere, that defies the point of bareloop"*).
|
|
26
|
+
*
|
|
27
|
+
* Two stage classes cannot be handed to the agent as a mid-build ruler, and F58
|
|
28
|
+
* measured both against a real close:
|
|
29
|
+
* - a PRECONDITION ("the seed commit exists") is not a ruler for the work; run
|
|
30
|
+
* as a check it passes instantly and teaches nothing. It sets `offer: false`.
|
|
31
|
+
* - a stage that reads what an EARLIER stage built ("the public API matches the
|
|
32
|
+
* emitted declarations") reds on its own for a reason that has nothing to do
|
|
33
|
+
* with the worker's edit. It names `needs`, and picking it runs that chain first.
|
|
34
|
+
* Everything else is offerable: derivation gave job #5 SEVEN rulers where the
|
|
35
|
+
* operator had hand-carved three (F58) — removing the operator does not cost the
|
|
36
|
+
* agent rulers, it hands it more.
|
|
37
|
+
*
|
|
38
|
+
* The `needs` expansion is TRANSITIVE (fixed point, not one level): a
|
|
39
|
+
* prerequisite may itself name one, and running an incomplete chain reproduces
|
|
40
|
+
* the exact false-red `needs` exists to remove, one level deeper. The expanded
|
|
41
|
+
* chain is ordered by the CLOSE, never by the order names were typed —
|
|
42
|
+
* validateStagedClose forces `needs` to name EARLIER stages, so close order IS
|
|
43
|
+
* a topological order. A `needs` cycle is inexpressible in a validated close for
|
|
44
|
+
* that same reason; the walk is still cycle-safe by construction (each stage is
|
|
45
|
+
* collected at most once), because a pure exported helper must terminate on
|
|
46
|
+
* whatever it is handed rather than hang the runner before any token burns.
|
|
47
|
+
*
|
|
48
|
+
* @param {any[]} close the validated stage list
|
|
49
|
+
* @returns {{name: string, run: any[]}[]} offerable stages, each with the ordered
|
|
50
|
+
* chain that must run for it (prerequisites first, the stage itself last)
|
|
51
|
+
*/
|
|
52
|
+
export function checkMenu(close: any[]): {
|
|
53
|
+
name: string;
|
|
54
|
+
run: any[];
|
|
55
|
+
}[];
|
|
56
|
+
/**
|
|
57
|
+
* Content hash of a job spec — what an approval record binds to. Any SEMANTIC
|
|
58
|
+
* edit changes the hash, so a spec whose meaning moved is unapproved by
|
|
59
|
+
* construction (interview decision #1: human signs, always, once per spec
|
|
60
|
+
* VERSION). Not every BYTE edit: the hash is taken over the RESOLVED spec
|
|
61
|
+
* (resolveSpec), so writing today's full TOOL_MENU into a spec that omitted
|
|
62
|
+
* `tools` is hash-neutral — the two spell the same ceiling, and that identity
|
|
63
|
+
* is the point of MED-1, since it is what pins WHICH menu the signature covers.
|
|
64
|
+
* The direction that matters still bites: the menu itself changing (or the
|
|
65
|
+
* explicit list changing) moves the resolved form, and the hash with it.
|
|
23
66
|
* @param {object} job
|
|
24
67
|
* @returns {string} sha256 hex
|
|
25
68
|
*/
|
|
@@ -35,8 +78,11 @@ export function jobSpecHash(job: object): string;
|
|
|
35
78
|
*/
|
|
36
79
|
export function checkApproval(job: object, approvals: unknown): boolean;
|
|
37
80
|
export const CLOSE_TYPES: readonly string[];
|
|
38
|
-
|
|
39
|
-
|
|
81
|
+
/** the hierarchy: which verdict classes a close type may claim (PRD §7). Read by
|
|
82
|
+
* the verdictType check — a declared verdict may not demand a class its close
|
|
83
|
+
* type cannot legally claim (green-on-rubric is laundering). The `CLASSES`
|
|
84
|
+
* vocabulary export went with steps[] (PRD v1.32): a class was a STEP field, and
|
|
85
|
+
* nothing declares one now. */
|
|
40
86
|
export const CLASS_BY_CLOSE: Readonly<{
|
|
41
87
|
predicate: readonly string[];
|
|
42
88
|
gold: readonly string[];
|
|
@@ -44,8 +90,6 @@ export const CLASS_BY_CLOSE: Readonly<{
|
|
|
44
90
|
hitl: readonly string[];
|
|
45
91
|
}>;
|
|
46
92
|
export const GOLD_COMPARE: readonly string[];
|
|
47
|
-
/** step middle modes (2b): text = single-target artifact; tools = Gate-governed file tools */
|
|
48
|
-
export const STEP_MODES: readonly string[];
|
|
49
93
|
/** the tool grant menu (2b interview #1): read/grep/write, plus the two litectx
|
|
50
94
|
* RETRIEVAL verbs (F19) — `run` stays locked-but-listed; a spec requesting it
|
|
51
95
|
* reds, and that red IS the request-red evidence admission waits on (curation
|
|
@@ -65,8 +109,39 @@ export const STEP_MODES: readonly string[];
|
|
|
65
109
|
* WRITE-class verb judged by the SAME writeScope fence as `write` (bareguard
|
|
66
110
|
* action type 'edit'). Admitted because whole-file rewrite is measurably
|
|
67
111
|
* unreliable at size (F31: 4 of 5 big-file whole-writes broke the tree) and
|
|
68
|
-
* taxes output tokens ∝ file size. It admits no execution either.
|
|
112
|
+
* taxes output tokens ∝ file size. It admits no execution either.
|
|
113
|
+
*
|
|
114
|
+
* P (design record 2026-07-28, hamr: full catalog) widened the menu to the four
|
|
115
|
+
* components — write · select · compress · isolate — every verb an EXISTING
|
|
116
|
+
* litectx implementation (menu-is-inventory). impact/related/recent/compress/
|
|
117
|
+
* peek are read-only like the retrieval pair; stash/remember/forget are
|
|
118
|
+
* STORE-writes (`.litectx`, never the tree — own gate action types, and the U
|
|
119
|
+
* runner deletes the store at reset so cold stays cold). None admits execution:
|
|
120
|
+
* the `run` lock is untouched. */
|
|
69
121
|
export const TOOL_MENU: readonly string[];
|
|
122
|
+
/** the write-class split the validator and the scout filter read: tree writes
|
|
123
|
+
* (writeScope fence) vs store writes (isolate) — NEITHER is read-capable. */
|
|
124
|
+
export const WRITE_VERBS: readonly string[];
|
|
125
|
+
export const STORE_VERBS: readonly string[];
|
|
126
|
+
/**
|
|
127
|
+
* The floor for `maxWallMs`: one default close timeout (`runClose`'s 120_000).
|
|
128
|
+
* Addendum 1 MEASURED that a wall deadline can only be read BETWEEN rounds —
|
|
129
|
+
* `loop.stop()` does not cut an in-flight call (fired at 500ms, returned at
|
|
130
|
+
* 4,018ms) — so enforcement is `maxWallMs + closeStages × closeTimeoutMs` (W5: a
|
|
131
|
+
* staged close runs every stage under the FULL timeout, ralph.js `runStages`).
|
|
132
|
+
* Under this floor the overshoot exceeds the budget itself and the advertised
|
|
133
|
+
* number would be more wrong than right, which is the advertised-must-equal-
|
|
134
|
+
* enforced line.
|
|
135
|
+
*
|
|
136
|
+
* The VALUE is a one-stage, default-timeout floor and is deliberately left alone —
|
|
137
|
+
* a threshold is operator territory, not something this module re-derives from a
|
|
138
|
+
* spec's own stage count. It therefore bounds only the degenerate case; a job with
|
|
139
|
+
* many stages or a raised `closeTimeoutMs` can still sit far above it while its
|
|
140
|
+
* overshoot dwarfs its cap. The clock reports that honestly (`closeStages` and
|
|
141
|
+
* `enforcedMs` are both on the `wall-clock` record) rather than the floor pretending
|
|
142
|
+
* to prevent it.
|
|
143
|
+
*/
|
|
144
|
+
export const MIN_WALL_MS: 120000;
|
|
70
145
|
/** locked-but-listed tools: real capabilities deliberately outside the grant
|
|
71
146
|
* menu. Requesting one is a DISTINCT `request-red` (module 4) — the ledger
|
|
72
147
|
* counts admission demand, and a generic invalid-value would bury it as a typo. */
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a gap for comparison. Deliberately NARROW: only bytes a re-run changes
|
|
3
|
+
* on its own are removed. Error COUNTS, file names and line numbers all survive —
|
|
4
|
+
* a trajectory like "30 errors" → "22 errors" must read as two different gaps, and
|
|
5
|
+
* a normalizer that scrubbed numbers would turn the converging case this module
|
|
6
|
+
* exists to protect into an instant strike.
|
|
7
|
+
* @param {string} text @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeGap(text: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* The comparable key for a gap: the normalized text, hashed. Hashed rather than
|
|
12
|
+
* kept whole because the seen-set lives for the whole step and a close's output
|
|
13
|
+
* can be tens of KB — and because the set holds no content, nothing here can leak
|
|
14
|
+
* model or file bytes into a record.
|
|
15
|
+
* @param {string} text @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function gapKey(text: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Build one ladder. ONE PER STEP: the seen-set and the strike count are the step's
|
|
20
|
+
* own history, and a ladder shared across steps would strike a fresh step for a gap
|
|
21
|
+
* an earlier one had already seen.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} opts
|
|
24
|
+
* @param {number} [opts.limit] strikes that end the step (default `STRIKE_LIMIT`)
|
|
25
|
+
* @param {() => number} opts.writeCount the CUMULATIVE count of the worker's
|
|
26
|
+
* allow-decision write/edit records so far this step (planrun reads it off the
|
|
27
|
+
* gate audit). Required, and a throw when it is missing rather than a silent
|
|
28
|
+
* degradation to repeats-only: a strike rule running on one of its two signals
|
|
29
|
+
* with nothing on the record saying so is the blind-instrument class, and this
|
|
30
|
+
* is the BA-4 param-guard class where a throw is the right answer.
|
|
31
|
+
*/
|
|
32
|
+
export function createLadder({ limit, writeCount }: {
|
|
33
|
+
limit?: number | undefined;
|
|
34
|
+
writeCount: () => number;
|
|
35
|
+
}): {
|
|
36
|
+
/**
|
|
37
|
+
* Read one RED iteration. Called by ralph after the judge has rendered a
|
|
38
|
+
* non-green, non-fault verdict — a green never reaches here, so every reading
|
|
39
|
+
* is a failing iteration by construction.
|
|
40
|
+
* @param {{iteration: number, gap?: string}} o
|
|
41
|
+
* @returns {{governor: string, iteration: number, strike: boolean, strikes: number,
|
|
42
|
+
* limit: number, wrote: boolean, repeatOf: number|null, distinctGaps: number}}
|
|
43
|
+
*/
|
|
44
|
+
record({ iteration, gap }: {
|
|
45
|
+
iteration: number;
|
|
46
|
+
gap?: string;
|
|
47
|
+
}): {
|
|
48
|
+
governor: string;
|
|
49
|
+
iteration: number;
|
|
50
|
+
strike: boolean;
|
|
51
|
+
strikes: number;
|
|
52
|
+
limit: number;
|
|
53
|
+
wrote: boolean;
|
|
54
|
+
repeatOf: number | null;
|
|
55
|
+
distinctGaps: number;
|
|
56
|
+
};
|
|
57
|
+
/** has the step run out of strikes? */
|
|
58
|
+
struckOut: () => boolean;
|
|
59
|
+
/** the ladder's own books — counts and iteration numbers, never gap content */
|
|
60
|
+
report: () => {
|
|
61
|
+
iterations: number;
|
|
62
|
+
strikes: number;
|
|
63
|
+
limit: number;
|
|
64
|
+
distinctGaps: number;
|
|
65
|
+
repeats: {
|
|
66
|
+
at: number;
|
|
67
|
+
firstAt: number;
|
|
68
|
+
}[];
|
|
69
|
+
idle: number[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The mechanism, in one sentence, for the replan brief. It is the only channel
|
|
73
|
+
* the redrafting planner adapts to, so it states WHICH shape ended the step and
|
|
74
|
+
* the evidence for it — never a label on its own, and never a culprit file
|
|
75
|
+
* beyond what the close itself reported (F28): the inputs here are iteration
|
|
76
|
+
* numbers and counts, so naming one is inexpressible.
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
brief(): string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The strike ceiling. Shell-owned, exactly where the fixed count was: the runner
|
|
83
|
+
* sets it, the agent cannot express it, and it is arbiter territory — a step's own
|
|
84
|
+
* plan may not tighten it and may not raise it. Two is hamr's number.
|
|
85
|
+
*/
|
|
86
|
+
export const STRIKE_LIMIT: 2;
|
package/types/ledger.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* bareloop vocabulary). Deliberate exclusions — bare cap-halt (a budget story),
|
|
4
4
|
* close-verdict reds and artifact-red (worker stories, the §5b line), gate-red
|
|
5
5
|
* (governance working as intended), pr-red (operator environment, not a suite
|
|
6
|
-
* lib), smoke-red escalations (already counted via primitive-smoke),
|
|
6
|
+
* lib), smoke-red escalations (already counted via primitive-smoke), step-stalled
|
|
7
|
+
* (our own fuse firing on an ABSENCE of beats — no observed lib failure to file),
|
|
8
|
+
* hitl-close
|
|
7
9
|
* and close-unsupported (by design) — classify to nothing.
|
|
8
10
|
* @param {any[]} events one spine's parsed events, in seq order
|
|
9
11
|
* @param {{spine?: string}} [opts] spine id stamped into samples
|
package/types/plan.d.ts
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MENU of legal `tree-changed` scopes — choose-don't-describe (design record
|
|
3
|
+
* §4, hamr's correction: *"did agent choose from a list or not?"*).
|
|
4
|
+
*
|
|
5
|
+
* F60 measured 13 of 18 validator reds as ONE class: the agent writes
|
|
6
|
+
* `scope: "src/*.js"`, the grammar accepts only a trailing `/**` or `/*`, and
|
|
7
|
+
* each red costs a redraft call. Teaching the rule in the prompt or widening the
|
|
8
|
+
* grammar both leave the agent authoring a free-text string and guessing its
|
|
9
|
+
* shape. Enumerating instead makes an illegal scope INEXPRESSIBLE.
|
|
10
|
+
*
|
|
11
|
+
* This is the only producer of scope menus, and it filters by the signed fence —
|
|
12
|
+
* so a menu entry that escapes containment cannot be constructed, rather than
|
|
13
|
+
* being caught by a downstream check. `globToPrefix` is untouched: this changes
|
|
14
|
+
* what the agent is OFFERED, never what the fence ENFORCES.
|
|
15
|
+
*
|
|
16
|
+
* @param {string[]} writeScope the signed fence entries (always offerable — they
|
|
17
|
+
* cover directories a step has not created yet, which `snapshotScope` treats
|
|
18
|
+
* as an empty snapshot)
|
|
19
|
+
* @param {string[]} [dirs] workdir-relative directories discovered under the
|
|
20
|
+
* fence; each becomes `<dir>/**`
|
|
21
|
+
* @param {number} [cap] the menu bound; `Infinity` asks for the UNCAPPED set, so
|
|
22
|
+
* a caller can report how many entries the cap dropped (no silent caps — a menu
|
|
23
|
+
* that reads as complete when it is not is the blind-instrument class)
|
|
24
|
+
* @returns {string[]} the offerable scopes, deduped, shallowest-first, capped
|
|
25
|
+
*/
|
|
26
|
+
export function legalScopes(writeScope: string[], dirs?: string[], cap?: number): string[];
|
|
1
27
|
/**
|
|
2
28
|
* F49 — a heuristic reject for the catastrophic-backtracking footgun: an
|
|
3
29
|
* unbounded quantifier applied to a group whose body itself repeats unboundedly
|
|
@@ -34,22 +60,66 @@
|
|
|
34
60
|
* @returns {boolean} true iff a nested unbounded quantifier is present
|
|
35
61
|
*/
|
|
36
62
|
export function hasNestedQuantifier(src: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The close, as the ordered STAGE LIST the plan flow actually executes (PRD
|
|
65
|
+
* v1.28). A staged close is already that list; the legacy object form is the
|
|
66
|
+
* ONE-stage list it stands for, named `close`. Anything else — a gold, rubric or
|
|
67
|
+
* hitl close, which names no command to run — is null, and the runner escalates
|
|
68
|
+
* `close-unsupported` before any tokens.
|
|
69
|
+
*
|
|
70
|
+
* W4 — this is the ONE staging, and it exists because there used to be two. The
|
|
71
|
+
* runner synthesized the single stage and derived its check menu from THAT, while
|
|
72
|
+
* `planPrompt` and `validatePlan` called `checkMenu` on the RAW spec — where an
|
|
73
|
+
* object close is not an array, so the menu came back empty. The result was a
|
|
74
|
+
* runner announcing and preflighting a stage the drafter was never offered and the
|
|
75
|
+
* validator would have redded `check-unknown`. Same class as the two-transforms
|
|
76
|
+
* defects before it: the fix is one derivation every consumer calls, not three
|
|
77
|
+
* copies kept in step by comment.
|
|
78
|
+
*
|
|
79
|
+
* Lives here rather than beside `checkMenu` in job.js only because job.js owns the
|
|
80
|
+
* SIGNED vocabulary and this is the consumer-side reading of it; `plan.js` is the
|
|
81
|
+
* lowest module every check-menu consumer already imports (planrun imports plan,
|
|
82
|
+
* never the reverse), so it is the one place all three can share without a cycle.
|
|
83
|
+
* @param {any} close the spec's `close` field, any shape
|
|
84
|
+
* @returns {any[]|null} the stage list, or null when the close names no command
|
|
85
|
+
*/
|
|
86
|
+
export function stageClose(close: any): any[] | null;
|
|
37
87
|
/** @typedef {{code: string, path: string, detail?: string, verb?: string}} Red */
|
|
38
88
|
/**
|
|
39
89
|
* Validate an agent-authored plan (`schema: "plan-v1"`) against the SIGNED
|
|
40
90
|
* job spec. Never throws on JSON text or plain parsed data; every failure is
|
|
41
91
|
* a named red. Returns the parsed plan on ok (single parse), null on any red.
|
|
42
92
|
* @param {object|string} input parsed plan, or raw JSON text (parse failures are a red)
|
|
43
|
-
* @param {{ job?: any, maxStepRounds?: number }} [opts] `job`: the
|
|
93
|
+
* @param {{ job?: any, maxStepRounds?: number, scopes?: string[], seedRed?: string[], priorChecks?: string[] }} [opts] `job`: the
|
|
44
94
|
* validateJob-GREEN four-field spec (the ceiling, the fence, and the checks
|
|
45
95
|
* menu all come from it — a missing or non-plan-shape job fails CLOSED);
|
|
46
96
|
* `maxStepRounds`: the shell's per-step rounds ceiling (interpret's
|
|
47
|
-
* tool-mode TURNS_PER_ATTEMPT) — a step may tighten it, never exceed it
|
|
97
|
+
* tool-mode TURNS_PER_ATTEMPT) — a step may tighten it, never exceed it;
|
|
98
|
+
* `scopes`: the offered `tree-changed` menu from `legalScopes` (the SAME array
|
|
99
|
+
* the drafting prompt enumerated). Omitted, it derives from the signed
|
|
100
|
+
* writeScope — never a free-text fallback (F50).
|
|
101
|
+
*
|
|
102
|
+
* The two shape-lottery gate rules (2026-08-04) — both keyed on RECORDED
|
|
103
|
+
* facts, never an LLM judgment of "is this job small", and both inactive
|
|
104
|
+
* when their fact is omitted (resume/reuse callers stay byte-identical):
|
|
105
|
+
* `seedRed`: check names whose PREFLIGHT verdict was needs_revision. Rule
|
|
106
|
+
* A-v2 — a seed-red check is the goal itself, not a milestone; it may only
|
|
107
|
+
* sit on the plan's FINAL write step (`check-placement`). The $0 archive
|
|
108
|
+
* sweep: seed-red-check-on-an-early-step has 0 honest greens ever; the one
|
|
109
|
+
* wide closing step greens 7/7. Green-at-seed checks stay free mid-plan
|
|
110
|
+
* (the 20 TESTGEN mid-plan greens — a guard, not a goal).
|
|
111
|
+
* `priorChecks`: the check names the PREDECESSOR plan carried (replan gate
|
|
112
|
+
* only). Rule B — a redraft may not drop one (`check-shed`): exits without
|
|
113
|
+
* the check verify FORM alone, so a shed lets the run "green" unearned
|
|
114
|
+
* (u-msdsmkid mechanism b; the 3 archived sheds were all on step-red runs).
|
|
48
115
|
* @returns {{ ok: boolean, reds: Red[], plan: object|null }}
|
|
49
116
|
*/
|
|
50
|
-
export function validatePlan(input: object | string, { job, maxStepRounds }?: {
|
|
117
|
+
export function validatePlan(input: object | string, { job, maxStepRounds, scopes, seedRed, priorChecks }?: {
|
|
51
118
|
job?: any;
|
|
52
119
|
maxStepRounds?: number;
|
|
120
|
+
scopes?: string[];
|
|
121
|
+
seedRed?: string[];
|
|
122
|
+
priorChecks?: string[];
|
|
53
123
|
}): {
|
|
54
124
|
ok: boolean;
|
|
55
125
|
reds: Red[];
|
|
@@ -65,13 +135,22 @@ export const MAX_EXITS_PER_STEP: 2;
|
|
|
65
135
|
/** plan size bound — per-step rounds bound the spend, but an unbounded step
|
|
66
136
|
* COUNT is still an unbounded claim on the wallet; same 8 as loop.maxIterations */
|
|
67
137
|
export const MAX_PLAN_STEPS: 8;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
|
|
138
|
+
export { WRITE_VERBS };
|
|
139
|
+
/** scope-menu bound: the menu is a PROMPT ingredient on every draft and
|
|
140
|
+
* redraft, so an unbounded repo would price the drafting call by its directory
|
|
141
|
+
* count. Shallow entries survive the cap (see `legalScopes`) because they are
|
|
142
|
+
* the useful ones — a deep leaf is reachable via its parent. */
|
|
143
|
+
export const MAX_SCOPE_MENU: 24;
|
|
144
|
+
/** P (design record 2026-07-28): the signed per-step model-tier menu. Closed by
|
|
145
|
+
* construction — the agent picks a tier, never names an arbitrary model string
|
|
146
|
+
* (the tier→model mapping and any effort params are the RUNNER's, per provider).
|
|
147
|
+
* opus is deliberately absent: reserved for fresh builds hamr explicitly assigns,
|
|
148
|
+
* never plan-selectable. */
|
|
149
|
+
export const STEP_MODELS: readonly string[];
|
|
72
150
|
export type Red = {
|
|
73
151
|
code: string;
|
|
74
152
|
path: string;
|
|
75
153
|
detail?: string;
|
|
76
154
|
verb?: string;
|
|
77
155
|
};
|
|
156
|
+
import { WRITE_VERBS } from './job.js';
|
package/types/planrun.d.ts
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Files named in an exit gap that the step's worker never wrote — the mechanical
|
|
3
|
+
* fact behind the prose-prohibition trap (u-msdpuaej, 2026-08-03): a drafter wrote
|
|
4
|
+
* "do not modify any file outside X" into a step whose exit check judges the whole
|
|
5
|
+
* goal, the worker obeyed, and the replanner — handed the gap AND the stall note —
|
|
6
|
+
* rebuilt the same shape, because inferring "the exit reds on files this step never
|
|
7
|
+
* touched" is exactly the semantic leap that fails (F38: mechanical gaps convert,
|
|
8
|
+
* semantic ones stall; 3/3 across the mailbox class). This computes that fact from
|
|
9
|
+
* the two books that already exist: path-like tokens in the gap text (extension
|
|
10
|
+
* required — a bare `step/two` is not a file claim) suffix-matched against the F32
|
|
11
|
+
* write audit's absolute paths. Advisory-only and fail-safe: extraction that finds
|
|
12
|
+
* nothing yields no line, never a guess.
|
|
13
|
+
*
|
|
14
|
+
* @param {string | undefined} gap the step's last exit gap text
|
|
15
|
+
* @param {string[] | undefined} writes the step's cumulative write-audit paths
|
|
16
|
+
* @returns {string[]} gap-named files with no matching write, in gap order
|
|
17
|
+
*/
|
|
18
|
+
export function gapFilesNeverWritten(gap: string | undefined, writes: string[] | undefined): string[];
|
|
19
|
+
/**
|
|
20
|
+
* The plan-drafting prompt: a schema DESCRIPTION built from the live validator
|
|
21
|
+
* menus — never a copyable example (the drafter must author, not echo; the
|
|
22
|
+
* run.js draftPrompt precedent). Check NAMES only: a check's command is
|
|
23
|
+
* arbiter territory the planner never sees.
|
|
24
|
+
* @param {any} job @param {string} scoutBlob @param {any[]|null} reds
|
|
25
|
+
* @param {number} maxStepRounds @param {string|null} failure replan context
|
|
26
|
+
* @param {string[]|undefined} scopes the offered `tree-changed` menu (`legalScopes`).
|
|
27
|
+
* Choose-don't-describe (§4): the agent picks a value, never authors a glob and
|
|
28
|
+
* guesses its shape. Omitted, it derives from the signed fence — the SAME
|
|
29
|
+
* fail-closed derivation `validatePlan` uses, so prompt and validator can never
|
|
30
|
+
* disagree about what was offered.
|
|
31
|
+
* @param {{ balanceUsd?: number|null, remainingMs?: number|null, progress?: string }} [materials]
|
|
32
|
+
* what the run has LEFT (T/A). Omitted → no materials block at all, which is the
|
|
33
|
+
* pre-T behaviour and keeps every existing caller byte-identical.
|
|
34
|
+
* @param {object|null} [startingDraft] Layer 3 (D4) — a loaded bridge's plan, handed over
|
|
35
|
+
* as the draft to TWEAK. Omitted/null is the COLD path and renders byte-identically to
|
|
36
|
+
* the pre-Layer-3 prompt: the block is additive, never surgery on the prompt's interior,
|
|
37
|
+
* so the two paths cannot drift (the F47 works-both-ways rule). Only the runner passes
|
|
38
|
+
* it, and only for the FIRST draft phase — a replan drafts from the run's own state.
|
|
39
|
+
* @param {{ seedRed?: string[], priorChecks?: string[] }} [checkFacts] the shape-lottery
|
|
40
|
+
* gate rules, STATED as well as enforced (the mailbox precedent: a rule only the
|
|
41
|
+
* validator knows costs a redraft per draft). `seedRed`: preflight-red check names —
|
|
42
|
+
* Rule A-v2's law (final write step only, framed wide). `priorChecks`: the predecessor
|
|
43
|
+
* plan's carried checks, replan phase only — Rule B's law (keep every one). Empty/omitted
|
|
44
|
+
* renders byte-identically to the pre-rules prompt.
|
|
45
|
+
*/
|
|
46
|
+
export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxStepRounds: number, failure: string | null, scopes: string[] | undefined, materials?: {
|
|
47
|
+
balanceUsd?: number | null;
|
|
48
|
+
remainingMs?: number | null;
|
|
49
|
+
progress?: string;
|
|
50
|
+
}, startingDraft?: object | null, checkFacts?: {
|
|
51
|
+
seedRed?: string[];
|
|
52
|
+
priorChecks?: string[];
|
|
53
|
+
}): string;
|
|
54
|
+
/**
|
|
55
|
+
* Execute a validateJob-GREEN plan-shape job (goal/verdictType/close).
|
|
3
56
|
* Called by runJob after the approval gate, validation, and the smoke — this
|
|
4
57
|
* function owns the plan flow only; the caller owns the job-end record and the
|
|
5
58
|
* one ledger (every provider round is emitted here as `worker-round`, which
|
|
@@ -20,9 +73,25 @@
|
|
|
20
73
|
* @param {() => number} opts.remainingUsd the one wallet: what is left of the signed budget right now
|
|
21
74
|
* @param {() => boolean} [opts.isUnpriced] has any round come back with a null cost? (F6) — the
|
|
22
75
|
* plan flow bails IN-FLIGHT on the first unpriced round instead of burning the whole plan
|
|
23
|
-
* @param {
|
|
76
|
+
* @param {(tier: string) => any} [opts.providerFor] P: per-step model-tier factory
|
|
77
|
+
* (tier ∈ STEP_MODELS → a provider). A plan naming a tier with no factory supplied is an
|
|
78
|
+
* interpreter-red STOP — never silently run the default tier as if the choice was honoured.
|
|
79
|
+
* @param {number} [opts.capRuns] shell-owned iteration cap, RETIRED as a governor
|
|
80
|
+
* (v1.46 §4). It stopped bounding a plan step when the strike ladder landed, and it
|
|
81
|
+
* stopped bounding the close-fix loop when the close trend rule did. It survives as
|
|
82
|
+
* that loop's fallback for the ONE case the trend cannot read — a close whose output
|
|
83
|
+
* carries no number at all — because a governor that cannot see the variable must
|
|
84
|
+
* not be the governor, and the honest fallback is the cruder bound it replaced
|
|
85
|
+
* rather than "unbounded". It lifts the moment a stage reports a comparable number.
|
|
86
|
+
* @param {number} [opts.strikeLimit] shell-owned STRIKE ceiling for a plan step's
|
|
87
|
+
* ladder (src/ladder.js). Arbiter territory exactly as the count it replaced was —
|
|
88
|
+
* the runner sets it, the plan cannot express it, and no step may tighten or raise
|
|
89
|
+
* it.
|
|
24
90
|
* @param {number} [opts.closeTimeoutMs] close/check wall-clock cap (shell territory)
|
|
25
91
|
* @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
|
|
92
|
+
* @param {number} [opts.scoutRounds] the read-only survey's round bound (F59: the LAST round is
|
|
93
|
+
* reserved — a scout that spends every round on tools gets one toolless round to write its
|
|
94
|
+
* survey, because the bound halts it mid-tool-use and text is its only deliverable)
|
|
26
95
|
* @param {boolean} [opts.layerRoot=false] Layer R — the within-run ratchet (src/root.js),
|
|
27
96
|
* scoped PER STEP's ralph loop (each micro-wheel is the Layer-1 atom). Shell-assembled from
|
|
28
97
|
* the step's own books: per-attempt write-sets from the F32 workerWrites audit (teed for
|
|
@@ -32,11 +101,67 @@
|
|
|
32
101
|
* first plan-flow job to emit `root-injected` runs the pre-registered ON-vs-OFF acceptance
|
|
33
102
|
* read (the Layer R default-flip, LAYERS.md ⚠). Excluded on native (clipipe): the native
|
|
34
103
|
* worker has no onToolResult seam, so the tee cannot settle and same-path rewrites are blind.
|
|
104
|
+
* @param {unknown} [opts.bridge] Layer 3 (D3/D4) — a bridge-v1 registry entry to REUSE as the
|
|
105
|
+
* drafter's starting point. Absent is the COLD path and is byte-identical to the pre-Layer-3
|
|
106
|
+
* flow. Present, the D2-split LOAD GATE runs at the door, before the close precheck and
|
|
107
|
+
* before any token: on a pass the newest version's plan (the run-as-executed artifact)
|
|
108
|
+
* rides into the FIRST draft prompt only; on a fail the run returns the distinct terminal
|
|
109
|
+
* `recipe-stale` having spent nothing. Selecting the entry — the listing, the pin, the
|
|
110
|
+
* LLM's pick — is the CALLER's, and so is falling back to cold: a silent automatic
|
|
111
|
+
* fall-back would spend a run's budget on a decision nobody made.
|
|
112
|
+
* @param {number} [opts.priorWallMs=0] RESUME (module C) — wall time a PREVIOUS, killed
|
|
113
|
+
* attempt of this same try already consumed, folded into the run clock (see
|
|
114
|
+
* `createClock`'s `priorElapsedMs`). The SIGNED `maxWallMs` is never rewritten: it is in
|
|
115
|
+
* the spec hash, and a resume that edited it would need a signature nobody typed. The
|
|
116
|
+
* restarted attempt simply starts already partly spent, so a kill can never buy a fresh
|
|
117
|
+
* allotment of the operator's wall.
|
|
118
|
+
* @param {any} [opts.resumeSeed] RESUME (module C v2) — the FINEST honest checkpoint of a
|
|
119
|
+
* KILLED run of this same try, read off its own spine by `readResume` (src/reuse.js) and
|
|
120
|
+
* by nothing else: `{ phase, plan, completedSteps: [{id, seq, by}] }`. hamr's ruling is
|
|
121
|
+
* the whole contract — *"it should allow resume and start last step instead from the
|
|
122
|
+
* beginning, why would i want to waste more money on something i already started"* — so
|
|
123
|
+
* with a seed this flow re-pays for NO paid unit the dead run already bought:
|
|
124
|
+
*
|
|
125
|
+
* - the SCOUT does not run (`scout-skipped`, never silence). Its survey is not on the
|
|
126
|
+
* spine (only its byte count is), so the ONE consequence is stated rather than
|
|
127
|
+
* discovered: a replan after a resume drafts from an empty survey plus the failure
|
|
128
|
+
* brief. Re-scouting to refill it would re-pay the exact call this exists to save.
|
|
129
|
+
* - the PLAN is not re-drafted. It is reloaded from the seed and RE-VALIDATED against
|
|
130
|
+
* the current signed spec; a plan that no longer validates is `plan-red` by name
|
|
131
|
+
* (`resume-plan-red`), never run. It is then emitted as this leg's own
|
|
132
|
+
* `plan-accepted` (`phase: 'resume'`) — R1 mints a bridge version from the plan AS
|
|
133
|
+
* EXECUTED, read back off the spine, so a leg that greens without one would mint
|
|
134
|
+
* nothing.
|
|
135
|
+
* - a step whose exit was already SATISFIED is skipped, in prefix order, each with its
|
|
136
|
+
* own `step-skipped` record naming the event that proves it. Never a fake
|
|
137
|
+
* step-start/step-end pair, and never silence: a reader must be able to tell skipped
|
|
138
|
+
* from run (the resume-to-cap invariant).
|
|
139
|
+
*
|
|
140
|
+
* The worker's conversation is deliberately NOT replayed — a transcript is not a
|
|
141
|
+
* checkpoint, and every attempt is fresh by the loop's own design. Absent is the
|
|
142
|
+
* ordinary path and is byte-identical to a run that was never killed.
|
|
143
|
+
* @param {{stage?: string|null, value?: number|null}[]} [opts.resumeGrades] RESUME — the
|
|
144
|
+
* close GRADES the dead leg recorded, oldest first (`readResume`'s `restart.grades`).
|
|
145
|
+
* A resumed leg is the same run continuing, so its halt readouts must judge the whole
|
|
146
|
+
* chain: without this the trend restarts at this leg's first close and can report
|
|
147
|
+
* `flat` — "revise the goal" — on a run that was converging when its allowance ran
|
|
148
|
+
* out. Baselines only (src/trend.js's THE SEED): it spends none of this leg's own
|
|
149
|
+
* bounds and mints none of its strikes. Empty/omitted is the cold path.
|
|
150
|
+
* @param {() => boolean} [opts.spendComplete] is the run's spend EXACT, or a floor? The
|
|
151
|
+
* ledger one level up owns the answer (unpriced rounds, a self-healed stall, a mid-call
|
|
152
|
+
* cut, an inherited resume floor); the money-halt readout states it beside the remaining
|
|
153
|
+
* it quotes, because `budget − floor` is a CEILING and printing it bare reads as exact
|
|
154
|
+
* (F6). Defaults to exact, which is what every pre-record caller was implicitly saying.
|
|
155
|
+
* @param {() => number} [opts.now] the wall clock's time source, injected. The real clock is the
|
|
156
|
+
* default; a caller supplies this to drive time deterministically (the same seam `createClock`
|
|
157
|
+
* already exposes — a run's terminal cannot otherwise be exercised without waiting out a cap
|
|
158
|
+
* whose floor is one close timeout).
|
|
35
159
|
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
36
|
-
* 'check-red' | 'close-red' | 'close-unsupported' | '
|
|
37
|
-
* 'provider-red' | 'interpreter-red' |
|
|
160
|
+
* 'check-red' | 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' |
|
|
161
|
+
* 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' | 'step-stalled' |
|
|
162
|
+
* `step-red:<id>`
|
|
38
163
|
*/
|
|
39
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, emit, remainingUsd, isUnpriced, capRuns, closeTimeoutMs, maxStepRounds, layerRoot }: {
|
|
164
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades }: {
|
|
40
165
|
workdir: string;
|
|
41
166
|
provider: any;
|
|
42
167
|
nativeProvider?: ((o: {
|
|
@@ -48,10 +173,22 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, emit, rem
|
|
|
48
173
|
emit: (type: string, data?: object) => object;
|
|
49
174
|
remainingUsd: () => number;
|
|
50
175
|
isUnpriced?: (() => boolean) | undefined;
|
|
176
|
+
providerFor?: ((tier: string) => any) | undefined;
|
|
51
177
|
capRuns?: number | undefined;
|
|
178
|
+
strikeLimit?: number | undefined;
|
|
52
179
|
closeTimeoutMs?: number | undefined;
|
|
53
180
|
maxStepRounds?: number | undefined;
|
|
181
|
+
scoutRounds?: number | undefined;
|
|
54
182
|
layerRoot?: boolean | undefined;
|
|
183
|
+
bridge?: unknown;
|
|
184
|
+
priorWallMs?: number | undefined;
|
|
185
|
+
resumeSeed?: any;
|
|
186
|
+
resumeGrades?: {
|
|
187
|
+
stage?: string | null;
|
|
188
|
+
value?: number | null;
|
|
189
|
+
}[] | undefined;
|
|
190
|
+
spendComplete?: (() => boolean) | undefined;
|
|
191
|
+
now?: (() => number) | undefined;
|
|
55
192
|
}): Promise<string>;
|
|
56
193
|
export type CategorizedError = Error & {
|
|
57
194
|
category?: string;
|