bareloop 0.6.0 → 0.8.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 +690 -0
- package/README.md +30 -11
- package/bareloop.context.md +658 -38
- package/package.json +2 -2
- package/src/bridges.js +571 -0
- package/src/clock.js +57 -5
- package/src/exits.js +8 -2
- package/src/index.js +29 -0
- package/src/job.js +43 -6
- package/src/ladder.js +196 -0
- package/src/ledger.js +84 -9
- package/src/plan.js +106 -107
- package/src/planrun.js +1040 -168
- package/src/ralph.js +156 -29
- package/src/reuse.js +1665 -0
- package/src/run.js +92 -6
- package/src/selection.js +160 -0
- package/src/spine.js +7 -2
- package/src/trend.js +405 -0
- package/src/validate.js +121 -0
- package/types/bridges.d.ts +198 -0
- package/types/clock.d.ts +25 -3
- package/types/exits.d.ts +14 -0
- package/types/index.d.ts +3 -0
- package/types/job.d.ts +13 -2
- package/types/ladder.d.ts +86 -0
- package/types/plan.d.ts +41 -41
- package/types/planrun.d.ts +208 -9
- package/types/ralph.d.ts +98 -4
- package/types/reuse.d.ts +392 -0
- package/types/run.d.ts +63 -3
- package/types/selection.d.ts +46 -0
- package/types/spine.d.ts +8 -1
- package/types/trend.d.ts +108 -0
- package/types/validate.d.ts +50 -0
package/types/planrun.d.ts
CHANGED
|
@@ -1,3 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This step's own last EXIT results, rendered for the REPLAN brief.
|
|
3
|
+
*
|
|
4
|
+
* The label says exits and not "the close", and the distinction is the whole of one
|
|
5
|
+
* fix: `res.gap` is `lastGap`, the join of EVERY failing exit's detail in exit order.
|
|
6
|
+
* With `MAX_EXITS_PER_STEP` at 2 and the mandatory `tree-changed` pairing on a write
|
|
7
|
+
* step, a step that wrote nothing leads the block with the EVALUATOR's own prose
|
|
8
|
+
* (`0 files changed under src/** …`) — captured verbatim from a real replan prompt,
|
|
9
|
+
* under a label reading "the verification's own output". The close stage's output is
|
|
10
|
+
* one of the details here, never reliably all of them. The payload is left alone: "the
|
|
11
|
+
* step wrote nothing" is exactly what a replanner should be told, and stripping it to
|
|
12
|
+
* make an old label true would trade a real fact for a tidy sentence.
|
|
13
|
+
*
|
|
14
|
+
* u-mshcpdg4: a run one strict error from done, killed with $1.10 and 82 seconds
|
|
15
|
+
* unspent. The close named the remaining work exactly — `src/recurse.js(978,115)`
|
|
16
|
+
* — and the worker read that every attempt. The REPLANNER, which is the component
|
|
17
|
+
* that chooses which files the next plan targets, was handed only the trend line
|
|
18
|
+
* (`still progressing — typecheck 30 → 15 → 15 → 1`): converging, and silent about
|
|
19
|
+
* WHERE. It re-targeted `src/loop.js`, already at zero errors; that step wrote
|
|
20
|
+
* nothing and struck out. F85 gave the brief the trajectory; this gives it the
|
|
21
|
+
* artifact the trajectory is a summary OF.
|
|
22
|
+
*
|
|
23
|
+
* Handed over as TEXT, never as a parsed file list, and the trap is worth naming
|
|
24
|
+
* because the parse is the obvious-looking fix: line 1 of that very gap reads
|
|
25
|
+
* `reports 1 error(s) in src/recurse.js, src/loop.js` — every file in SCOPE — and
|
|
26
|
+
* only line 2 names the culprit. The `never wrote` advisory that used to sit here
|
|
27
|
+
* did exactly that parse, and on this gap it resolved to `src/loop.js` — the file
|
|
28
|
+
* already CLEAN — then said so as a directive beside the artifact that said the
|
|
29
|
+
* opposite. It was deleted with this change (hamr, 2026-08-05): two readers of one
|
|
30
|
+
* question, the parsed one wrong, is the same failure the variance meter just had.
|
|
31
|
+
* Nothing generic can tell a summary line from a detail line, and the
|
|
32
|
+
* shapes differ per close anyway (tsc file:line, pytest test ids, a count close
|
|
33
|
+
* that names no file at all). A model reading the artifact can make that
|
|
34
|
+
* distinction; a regex reproduces the bug. So: no extraction, no digest, no
|
|
35
|
+
* inference — the artifact, bounded and scrubbed.
|
|
36
|
+
*
|
|
37
|
+
* Two boundaries, both reused rather than respelled:
|
|
38
|
+
* - SCRUB. A prompt is an egress point and this repo has ONE secret inventory
|
|
39
|
+
* (`SECRET_PATTERNS` via `redactSecrets`). The gap arrives already scrubbed
|
|
40
|
+
* from `judge`, which makes this defense in depth rather than the only guard —
|
|
41
|
+
* and defense in depth at the egress is exactly where it belongs, because the
|
|
42
|
+
* next caller of this helper will not remember the upstream one.
|
|
43
|
+
* - BOUND, and it is a BACKSTOP rather than an envelope. What arrives here has
|
|
44
|
+
* ALREADY been bounded: `runClose` ran `boundGap` with the stage's own `gapKeep`,
|
|
45
|
+
* which deliberately rescues the `not ok`/`FAILED` names out of the elided middle
|
|
46
|
+
* — the mechanical gap F46's conversion mechanism feeds the worker. Re-bounding
|
|
47
|
+
* that with a fresh 400/1500 envelope deletes exactly those names a second time,
|
|
48
|
+
* which is F28 reintroduced; measured on a 120-red-line close output, the first
|
|
49
|
+
* bound rescued 78 names and this one dropped 12 of them. src/exits.js states the
|
|
50
|
+
* same rule for the same artifact one seam earlier, and this now uses ITS number:
|
|
51
|
+
* `CHECK_GAP_MAX`, imported rather than respelled, so the two seams cannot drift
|
|
52
|
+
* on how big a check gap may be. Under it the gap passes through VERBATIM.
|
|
53
|
+
*
|
|
54
|
+
* A backstop is still needed, and the headroom is why the ceiling is not smaller.
|
|
55
|
+
* `res.gap` is `lastGap`, the JOIN of up to `MAX_EXITS_PER_STEP` failing exit
|
|
56
|
+
* details, and the check-passes branch slices EACH at `CHECK_GAP_MAX` — so what
|
|
57
|
+
* arrives is bounded per detail, never in total. Measured, the real maximum is
|
|
58
|
+
* comfortably under: `runClose`'s own envelope tops out at ~10.1KB (400 head +
|
|
59
|
+
* 8192 keep + 1500 tail + labels), and joined with a `tree-changed` line that is
|
|
60
|
+
* ~10.2KB, so the shipped path passes through untouched with ~1.8KB to spare. The
|
|
61
|
+
* backstop therefore fires only where src/exits.js says it should: a seam that
|
|
62
|
+
* returned something never bounded at all.
|
|
63
|
+
*
|
|
64
|
+
* Over the line `boundGap` runs with `REPLAN_GAP_KEEP`, the same envelope the
|
|
65
|
+
* close path uses, and every trim announces itself (F28). Note what that keep-set
|
|
66
|
+
* cannot do: `\S` matches every non-blank line, so the keep block degenerates into
|
|
67
|
+
* "the first 50 lines" and re-surfaces nothing from the middle. That is the honest
|
|
68
|
+
* limit of a red-set that cannot know the stage's own pattern (see
|
|
69
|
+
* `REPLAN_GAP_KEEP`), and it is affordable precisely because it now applies only
|
|
70
|
+
* to a gap nothing upstream ever bounded — on that gap ANY envelope loses names,
|
|
71
|
+
* and the choice is a bounded prompt over an unbounded one.
|
|
72
|
+
*
|
|
73
|
+
* No gap → the empty string, so the brief renders byte-identically to the pre-F86
|
|
74
|
+
* one. A labelled empty section would be an invitation to explain an absence the
|
|
75
|
+
* run never observed (a stall never judged its exits at all).
|
|
76
|
+
*
|
|
77
|
+
* @param {string | null | undefined} gap the step's last exit gap text
|
|
78
|
+
* @returns {string} the labelled block, or '' when there is nothing to show
|
|
79
|
+
*/
|
|
80
|
+
export function closeGapBlock(gap: string | null | undefined): string;
|
|
1
81
|
/**
|
|
2
82
|
* The plan-drafting prompt: a schema DESCRIPTION built from the live validator
|
|
3
83
|
* menus — never a copyable example (the drafter must author, not echo; the
|
|
@@ -13,16 +93,26 @@
|
|
|
13
93
|
* @param {{ balanceUsd?: number|null, remainingMs?: number|null, progress?: string }} [materials]
|
|
14
94
|
* what the run has LEFT (T/A). Omitted → no materials block at all, which is the
|
|
15
95
|
* pre-T behaviour and keeps every existing caller byte-identical.
|
|
16
|
-
* @param {
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
96
|
+
* @param {object|null} [startingDraft] Layer 3 (D4) — a loaded bridge's plan, handed over
|
|
97
|
+
* as the draft to TWEAK. Omitted/null is the COLD path and renders byte-identically to
|
|
98
|
+
* the pre-Layer-3 prompt: the block is additive, never surgery on the prompt's interior,
|
|
99
|
+
* so the two paths cannot drift (the F47 works-both-ways rule). Only the runner passes
|
|
100
|
+
* it, and only for the FIRST draft phase — a replan drafts from the run's own state.
|
|
101
|
+
* @param {{ seedRed?: string[], priorChecks?: string[] }} [checkFacts] the shape-lottery
|
|
102
|
+
* gate rules, STATED as well as enforced (the mailbox precedent: a rule only the
|
|
103
|
+
* validator knows costs a redraft per draft). `seedRed`: preflight-red check names —
|
|
104
|
+
* Rule A-v2's law (final write step only, framed wide). `priorChecks`: the predecessor
|
|
105
|
+
* plan's carried checks, replan phase only — Rule B's law (keep every one). Empty/omitted
|
|
106
|
+
* renders byte-identically to the pre-rules prompt.
|
|
20
107
|
*/
|
|
21
108
|
export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxStepRounds: number, failure: string | null, scopes: string[] | undefined, materials?: {
|
|
22
109
|
balanceUsd?: number | null;
|
|
23
110
|
remainingMs?: number | null;
|
|
24
111
|
progress?: string;
|
|
25
|
-
},
|
|
112
|
+
}, startingDraft?: object | null, checkFacts?: {
|
|
113
|
+
seedRed?: string[];
|
|
114
|
+
priorChecks?: string[];
|
|
115
|
+
}): string;
|
|
26
116
|
/**
|
|
27
117
|
* Execute a validateJob-GREEN plan-shape job (goal/verdictType/close).
|
|
28
118
|
* Called by runJob after the approval gate, validation, and the smoke — this
|
|
@@ -48,7 +138,17 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
48
138
|
* @param {(tier: string) => any} [opts.providerFor] P: per-step model-tier factory
|
|
49
139
|
* (tier ∈ STEP_MODELS → a provider). A plan naming a tier with no factory supplied is an
|
|
50
140
|
* interpreter-red STOP — never silently run the default tier as if the choice was honoured.
|
|
51
|
-
* @param {number} [opts.capRuns] shell-owned
|
|
141
|
+
* @param {number} [opts.capRuns] shell-owned iteration cap, RETIRED as a governor
|
|
142
|
+
* (v1.46 §4). It stopped bounding a plan step when the strike ladder landed, and it
|
|
143
|
+
* stopped bounding the close-fix loop when the close trend rule did. It survives as
|
|
144
|
+
* that loop's fallback for the ONE case the trend cannot read — a close whose output
|
|
145
|
+
* carries no number at all — because a governor that cannot see the variable must
|
|
146
|
+
* not be the governor, and the honest fallback is the cruder bound it replaced
|
|
147
|
+
* rather than "unbounded". It lifts the moment a stage reports a comparable number.
|
|
148
|
+
* @param {number} [opts.strikeLimit] shell-owned STRIKE ceiling for a plan step's
|
|
149
|
+
* ladder (src/ladder.js). Arbiter territory exactly as the count it replaced was —
|
|
150
|
+
* the runner sets it, the plan cannot express it, and no step may tighten or raise
|
|
151
|
+
* it.
|
|
52
152
|
* @param {number} [opts.closeTimeoutMs] close/check wall-clock cap (shell territory)
|
|
53
153
|
* @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
|
|
54
154
|
* @param {number} [opts.scoutRounds] the read-only survey's round bound (F59: the LAST round is
|
|
@@ -63,15 +163,101 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
63
163
|
* first plan-flow job to emit `root-injected` runs the pre-registered ON-vs-OFF acceptance
|
|
64
164
|
* read (the Layer R default-flip, LAYERS.md ⚠). Excluded on native (clipipe): the native
|
|
65
165
|
* worker has no onToolResult seam, so the tee cannot settle and same-path rewrites are blind.
|
|
166
|
+
* @param {unknown} [opts.bridge] Layer 3 (D3/D4) — a bridge-v1 registry entry to REUSE as the
|
|
167
|
+
* drafter's starting point. Absent is the COLD path and is byte-identical to the pre-Layer-3
|
|
168
|
+
* flow. Present, the D2-split LOAD GATE runs at the door, before the close precheck and
|
|
169
|
+
* before any token: on a pass the newest version's plan (the run-as-executed artifact)
|
|
170
|
+
* rides into the FIRST draft prompt only; on a fail the run returns the distinct terminal
|
|
171
|
+
* `recipe-stale` having spent nothing. Selecting the entry — the listing, the pin, the
|
|
172
|
+
* LLM's pick — is the CALLER's, and so is falling back to cold: a silent automatic
|
|
173
|
+
* fall-back would spend a run's budget on a decision nobody made.
|
|
174
|
+
* @param {number} [opts.priorWallMs=0] RESUME (module C) — wall time a PREVIOUS, killed
|
|
175
|
+
* attempt of this same try already consumed, folded into the run clock (see
|
|
176
|
+
* `createClock`'s `priorElapsedMs`). The SIGNED `maxWallMs` is never rewritten: it is in
|
|
177
|
+
* the spec hash, and a resume that edited it would need a signature nobody typed. The
|
|
178
|
+
* restarted attempt simply starts already partly spent, so a kill can never buy a fresh
|
|
179
|
+
* allotment of the operator's wall.
|
|
180
|
+
* @param {any} [opts.resumeSeed] RESUME (module C v2) — the FINEST honest checkpoint of a
|
|
181
|
+
* KILLED run of this same try, read off its own spine by `readResume` (src/reuse.js) and
|
|
182
|
+
* by nothing else: `{ phase, plan, completedSteps: [{id, seq, by}] }`. hamr's ruling is
|
|
183
|
+
* the whole contract — *"it should allow resume and start last step instead from the
|
|
184
|
+
* beginning, why would i want to waste more money on something i already started"* — so
|
|
185
|
+
* with a seed this flow re-pays for NO paid unit the dead run already bought:
|
|
186
|
+
*
|
|
187
|
+
* - the SCOUT does not run (`scout-skipped`, never silence). Its survey is not on the
|
|
188
|
+
* spine (only its byte count is), so the ONE consequence is stated rather than
|
|
189
|
+
* discovered: a replan after a resume drafts from an empty survey plus the failure
|
|
190
|
+
* brief. Re-scouting to refill it would re-pay the exact call this exists to save.
|
|
191
|
+
* - the PLAN is not re-drafted. It is reloaded from the seed and RE-VALIDATED against
|
|
192
|
+
* the current signed spec; a plan that no longer validates is `plan-red` by name
|
|
193
|
+
* (`resume-plan-red`), never run. It is then emitted as this leg's own
|
|
194
|
+
* `plan-accepted` (`phase: 'resume'`) — R1 mints a bridge version from the plan AS
|
|
195
|
+
* EXECUTED, read back off the spine, so a leg that greens without one would mint
|
|
196
|
+
* nothing.
|
|
197
|
+
* - a step whose exit was already SATISFIED is skipped, in prefix order, each with its
|
|
198
|
+
* own `step-skipped` record naming the event that proves it. Never a fake
|
|
199
|
+
* step-start/step-end pair, and never silence: a reader must be able to tell skipped
|
|
200
|
+
* from run (the resume-to-cap invariant).
|
|
201
|
+
*
|
|
202
|
+
* The worker's conversation is deliberately NOT replayed — a transcript is not a
|
|
203
|
+
* checkpoint, and every attempt is fresh by the loop's own design. Absent is the
|
|
204
|
+
* ordinary path and is byte-identical to a run that was never killed.
|
|
205
|
+
* @param {{stage?: string|null, value?: number|null}[]} [opts.resumeGrades] RESUME — the
|
|
206
|
+
* close GRADES the dead leg recorded, oldest first (`readResume`'s `restart.grades`).
|
|
207
|
+
* A resumed leg is the same run continuing, so its halt readouts must judge the whole
|
|
208
|
+
* chain: without this the trend restarts at this leg's first close and can report
|
|
209
|
+
* `flat` — "revise the goal" — on a run that was converging when its allowance ran
|
|
210
|
+
* out. Baselines only (src/trend.js's THE SEED): it spends none of this leg's own
|
|
211
|
+
* bounds and mints none of its strikes. Empty/omitted is the cold path.
|
|
212
|
+
* @param {{count?: number, grantUsed?: boolean}|null} [opts.resumeReplans] RESUME — the
|
|
213
|
+
* replan LEDGER the chain has already spent (`readResume`'s `restart.replans` /
|
|
214
|
+
* `restart.replanGrantUsed`), seeding the ceiling below. `resumeGrades`' sibling and
|
|
215
|
+
* deliberately NOT its twin: a grade seed feeds a READOUT, this one feeds a BOUND.
|
|
216
|
+
*
|
|
217
|
+
* Without it the ceiling is reborn on every call, and a resume is another call:
|
|
218
|
+
* measured, a leg that spent both replans and step-redded handed its checkpoint to a
|
|
219
|
+
* second leg that replanned twice more. Two became four by being killed once, which is
|
|
220
|
+
* the creep the latch exists to make impossible ("unlimited replanning launders thrash
|
|
221
|
+
* as adaptation", PRD v1.12).
|
|
222
|
+
*
|
|
223
|
+
* WHY THIS SEED SPENDS THE LEG'S BOUND WHERE `resumeGrades` DOES NOT. src/trend.js's
|
|
224
|
+
* THE SEED refuses to seed the fix loop's ITERATIONS from history "because the leg's
|
|
225
|
+
* own bounds must not be spent by history" — and that is right, because an attempt
|
|
226
|
+
* allowance is a LEG bound: a restarted leg buys its own attempts with its own money.
|
|
227
|
+
* The replan ceiling is a RUN bound by doctrine. It bounds how many times the WORKFLOW
|
|
228
|
+
* may be redrawn before redrawing it is thrash, and that question spans exactly the
|
|
229
|
+
* chain the halt readout already spans. The two seeds therefore disagree on purpose;
|
|
230
|
+
* they are not the same rule applied inconsistently.
|
|
231
|
+
*
|
|
232
|
+
* CHAIN SEMANTICS, and the one place this differs from the grade seed's mechanism.
|
|
233
|
+
* `readGradeSeed`'s documented KNOWN LIMIT is that it reads ONE spine, so a resume of a
|
|
234
|
+
* resume inherits leg 2's grades and not leg 1's — the chain shortens by one leg per
|
|
235
|
+
* resume, which is fail-safe for a READOUT (a shorter chain can only under-claim a
|
|
236
|
+
* direction). It is the DANGEROUS direction for a ceiling: an under-claimed ledger is a
|
|
237
|
+
* refilled allowance, and every kill would buy one. So this seed does NOT reuse that
|
|
238
|
+
* mechanism. It follows the MONEY fold instead (`priorSpentUsd`, `try-start`/
|
|
239
|
+
* `job-start`): each leg DECLARES the ledger it inherited on its own spine, and the
|
|
240
|
+
* next reader adds only that leg's own `replan` records to the declared number. Leg 3
|
|
241
|
+
* therefore inherits the whole chain, not leg 2's slice.
|
|
242
|
+
*
|
|
243
|
+
* `count` seeds `replans` (and, above zero, latches `replanned`); `grantUsed` seeds the
|
|
244
|
+
* F85-C variance latch, so a killed leg cannot re-earn an extra the arbiter has already
|
|
245
|
+
* granted. Null/omitted is the cold path and is byte-identical to a run nobody resumed.
|
|
246
|
+
* @param {() => boolean} [opts.spendComplete] is the run's spend EXACT, or a floor? The
|
|
247
|
+
* ledger one level up owns the answer (unpriced rounds, a self-healed stall, a mid-call
|
|
248
|
+
* cut, an inherited resume floor); the money-halt readout states it beside the remaining
|
|
249
|
+
* it quotes, because `budget − floor` is a CEILING and printing it bare reads as exact
|
|
250
|
+
* (F6). Defaults to exact, which is what every pre-record caller was implicitly saying.
|
|
66
251
|
* @param {() => number} [opts.now] the wall clock's time source, injected. The real clock is the
|
|
67
252
|
* default; a caller supplies this to drive time deterministically (the same seam `createClock`
|
|
68
253
|
* already exposes — a run's terminal cannot otherwise be exercised without waiting out a cap
|
|
69
254
|
* whose floor is one close timeout).
|
|
70
255
|
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
71
|
-
* 'check-red' | 'close-red' | 'close-unsupported' | '
|
|
72
|
-
* 'wall-halt' | 'provider-red' | 'interpreter-red' | 'step-stalled' |
|
|
256
|
+
* 'check-red' | 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' |
|
|
257
|
+
* 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' | 'step-stalled' |
|
|
258
|
+
* `step-red:<id>`
|
|
73
259
|
*/
|
|
74
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, capRuns, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, now }: {
|
|
260
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans }: {
|
|
75
261
|
workdir: string;
|
|
76
262
|
provider: any;
|
|
77
263
|
nativeProvider?: ((o: {
|
|
@@ -85,10 +271,23 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
85
271
|
isUnpriced?: (() => boolean) | undefined;
|
|
86
272
|
providerFor?: ((tier: string) => any) | undefined;
|
|
87
273
|
capRuns?: number | undefined;
|
|
274
|
+
strikeLimit?: number | undefined;
|
|
88
275
|
closeTimeoutMs?: number | undefined;
|
|
89
276
|
maxStepRounds?: number | undefined;
|
|
90
277
|
scoutRounds?: number | undefined;
|
|
91
278
|
layerRoot?: boolean | undefined;
|
|
279
|
+
bridge?: unknown;
|
|
280
|
+
priorWallMs?: number | undefined;
|
|
281
|
+
resumeSeed?: any;
|
|
282
|
+
resumeGrades?: {
|
|
283
|
+
stage?: string | null;
|
|
284
|
+
value?: number | null;
|
|
285
|
+
}[] | undefined;
|
|
286
|
+
resumeReplans?: {
|
|
287
|
+
count?: number;
|
|
288
|
+
grantUsed?: boolean;
|
|
289
|
+
} | null | undefined;
|
|
290
|
+
spendComplete?: (() => boolean) | undefined;
|
|
92
291
|
now?: (() => number) | undefined;
|
|
93
292
|
}): Promise<string>;
|
|
94
293
|
export type CategorizedError = Error & {
|
package/types/ralph.d.ts
CHANGED
|
@@ -111,6 +111,42 @@ export function runStages(stages: any[], redact?: (s: string) => string, { timeo
|
|
|
111
111
|
timeoutMs?: number;
|
|
112
112
|
cwd?: string;
|
|
113
113
|
}): Promise<any>;
|
|
114
|
+
/**
|
|
115
|
+
* Bound an over-long close stream to a head sample + elided middle + tail. When
|
|
116
|
+
* `keepPattern` is set (F28), lines of the WHOLE stream matching it are also
|
|
117
|
+
* carried in a clearly-delimited block between head and tail — capped — so the
|
|
118
|
+
* failing-test names buried in the middle still reach the worker.
|
|
119
|
+
* Exported for the ONE reason `GAP_KEEP_TRIM_MARKER` above is: a second consumer
|
|
120
|
+
* that needs a gap bounded must reuse THIS envelope, not spell its own. A private
|
|
121
|
+
* copy would be a second truncation scheme, and the two would drift on exactly the
|
|
122
|
+
* property that matters — whether the red lines survive, and whether the trim
|
|
123
|
+
* announces itself (F28: silent truncation is the disease this function cures).
|
|
124
|
+
* The close path is unchanged: `runClose` still calls it with the stage's own
|
|
125
|
+
* `gapKeep`, byte-identically.
|
|
126
|
+
* @param {string} s the (redacted) close output
|
|
127
|
+
* @param {string} [keepPattern] regex source; matching lines survive the elision
|
|
128
|
+
*/
|
|
129
|
+
export function boundGap(s: string, keepPattern?: string): string;
|
|
130
|
+
/**
|
|
131
|
+
* The exhaustion-by-PROGRESS seam, as a contract rather than "whatever one module
|
|
132
|
+
* happens to return". Two governors implement it and they read different signals —
|
|
133
|
+
* the step ladder's repeats-and-writes (src/ladder.js) and the close-fix loop's
|
|
134
|
+
* per-stage graded numbers (src/trend.js) — which is the whole reason ralph takes an
|
|
135
|
+
* interface here and interprets nothing itself.
|
|
136
|
+
*
|
|
137
|
+
* `record` returns the governor's own reading, which ralph emits verbatim and never
|
|
138
|
+
* inspects; each reading names its `governor` so two instruments under one spine type
|
|
139
|
+
* can never be averaged into one number. `report` is the only shape ralph itself
|
|
140
|
+
* reads. `terminal` is optional and overrides the exhaustion PROSE only — never the
|
|
141
|
+
* category, never the outcome.
|
|
142
|
+
*
|
|
143
|
+
* @typedef {object} LoopGovernor
|
|
144
|
+
* @property {(o: {iteration: number, gap?: string}) => object} record
|
|
145
|
+
* @property {() => boolean} struckOut
|
|
146
|
+
* @property {() => {strikes: number, limit: number, iterations: number, distinctGaps?: number}} report
|
|
147
|
+
* @property {() => string} brief
|
|
148
|
+
* @property {() => {decision: string, options: string[]}} [terminal]
|
|
149
|
+
*/
|
|
114
150
|
/**
|
|
115
151
|
* The loop: `while close-red and under-cap: run the middle`. Stops at first
|
|
116
152
|
* green (within-run tuning past a visible close is the fit-to-pass surface,
|
|
@@ -136,7 +172,28 @@ export function runStages(stages: any[], redact?: (s: string) => string, { timeo
|
|
|
136
172
|
* zone, worker-crash routing (F32), and the cap taxonomy apply unchanged.
|
|
137
173
|
* Injected by run.js only; inexpressible in any config or plan — the agent
|
|
138
174
|
* never authors its judge any more than its close.
|
|
139
|
-
* @param {number} opts.capRuns budget: max middle runs
|
|
175
|
+
* @param {number} [opts.capRuns] budget: max middle runs. Required UNLESS `ladder`
|
|
176
|
+
* is supplied — the two exhaustion rules are alternatives, never both at once.
|
|
177
|
+
* The "unless" is ENFORCED at entry (a param guard that throws), not merely
|
|
178
|
+
* documented: an optional-in-the-contract bound with nothing checking it made a
|
|
179
|
+
* loop that never ran report a governance stop that never happened.
|
|
180
|
+
* @param {LoopGovernor} [opts.ladder]
|
|
181
|
+
* the PROGRESS governor (src/ladder.js) that replaces the fixed count for a plan
|
|
182
|
+
* STEP's micro-loop. With it, the loop is bounded by strikes rather than by a
|
|
183
|
+
* number of runs: iterations float, and money, the wall, the variance meter and
|
|
184
|
+
* the stall fuse keep every bit of their current authority (each still ends the
|
|
185
|
+
* loop through the middle's own throw). Ralph still interprets nothing — it hands
|
|
186
|
+
* the ladder the iteration's gap and reads back a boolean.
|
|
187
|
+
*
|
|
188
|
+
* The CLOSE-FIX loop now supplies one too (PRD v1.46 §4, once its own $0 replay
|
|
189
|
+
* over all 8 archived fix loops came back clean: 0 greens harmed, 1 waste case
|
|
190
|
+
* caught). Its governor reads a different signal — the close's own per-stage
|
|
191
|
+
* graded numbers (src/trend.js), not repeats and writes — which is exactly why
|
|
192
|
+
* this is a SEAM and not a hard-coded rule: ralph still interprets nothing.
|
|
193
|
+
* An optional `terminal()` on the governor overrides the exhaustion prose, because
|
|
194
|
+
* the step ladder's copy offers the human a replan and there is no planner at the
|
|
195
|
+
* close. The category and the outcome are NOT overridable: one exhaustion terminal,
|
|
196
|
+
* two triggers.
|
|
140
197
|
* @param {(type: string, data?: object) => object} opts.emit a spine emitter
|
|
141
198
|
* @param {(s: string) => string} [opts.redact] source scrubber for close output
|
|
142
199
|
* (secrets never enter the spine); injected so the shell stays stdlib-only
|
|
@@ -155,7 +212,7 @@ export function runStages(stages: any[], redact?: (s: string) => string, { timeo
|
|
|
155
212
|
* with zero writes, a crash stays what it always was: an instrument stop.
|
|
156
213
|
* @returns {Promise<'green'|'escalated'>}
|
|
157
214
|
*/
|
|
158
|
-
export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
|
|
215
|
+
export function ralph({ middle, close, judge, capRuns, ladder, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
|
|
159
216
|
middle: (iteration: number, gap?: string) => void | Promise<void>;
|
|
160
217
|
close?: string[] | undefined;
|
|
161
218
|
judge?: (() => Promise<{
|
|
@@ -167,7 +224,8 @@ export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeou
|
|
|
167
224
|
gap?: string;
|
|
168
225
|
detail?: string;
|
|
169
226
|
}) | undefined;
|
|
170
|
-
capRuns
|
|
227
|
+
capRuns?: number | undefined;
|
|
228
|
+
ladder?: LoopGovernor | undefined;
|
|
171
229
|
emit: (type: string, data?: object) => object;
|
|
172
230
|
redact?: ((s: string) => string) | undefined;
|
|
173
231
|
closeTimeoutMs?: number | undefined;
|
|
@@ -194,7 +252,12 @@ export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeou
|
|
|
194
252
|
* Two rules, deliberately kept BOTH ways round:
|
|
195
253
|
* - `names` / `prefixes` — the knowns. A literal denylist is a chase (every
|
|
196
254
|
* new provider mints a new variable), so it is not load-bearing on its own;
|
|
197
|
-
* it exists for names the shape rule cannot see
|
|
255
|
+
* it exists for names the shape rule STRUCTURALLY cannot see — either no
|
|
256
|
+
* `_`-bounded credential suffix at all (`AWS_ACCESS_KEY_ID`, `PGPASSWORD`,
|
|
257
|
+
* `MYSQL_PWD`), a `*_KEY` that is not `*_API_KEY` (`SSH_PRIVATE_KEY`), or a
|
|
258
|
+
* BARE spelling with nothing in front of the suffix (`TOKEN`, `SECRET`).
|
|
259
|
+
* Names are matched EXACTLY, which is what keeps connection config out of
|
|
260
|
+
* the strip (`PGHOST`, `SECRETS_DIR`, `TOKENIZERS_PARALLELISM` all survive).
|
|
198
261
|
* - `shape` — the NAME-SHAPE rule, which is what bounds the chase: anything
|
|
199
262
|
* whose name ends in the credential suffixes goes, whoever minted it and
|
|
200
263
|
* whether or not anyone here has heard of it. Case-insensitive; no `g` flag,
|
|
@@ -228,3 +291,34 @@ export const CLOSE_FAULTS: Record<string, {
|
|
|
228
291
|
decision: string;
|
|
229
292
|
options: string[];
|
|
230
293
|
}>;
|
|
294
|
+
/**
|
|
295
|
+
* The exhaustion-by-PROGRESS seam, as a contract rather than "whatever one module
|
|
296
|
+
* happens to return". Two governors implement it and they read different signals —
|
|
297
|
+
* the step ladder's repeats-and-writes (src/ladder.js) and the close-fix loop's
|
|
298
|
+
* per-stage graded numbers (src/trend.js) — which is the whole reason ralph takes an
|
|
299
|
+
* interface here and interprets nothing itself.
|
|
300
|
+
*
|
|
301
|
+
* `record` returns the governor's own reading, which ralph emits verbatim and never
|
|
302
|
+
* inspects; each reading names its `governor` so two instruments under one spine type
|
|
303
|
+
* can never be averaged into one number. `report` is the only shape ralph itself
|
|
304
|
+
* reads. `terminal` is optional and overrides the exhaustion PROSE only — never the
|
|
305
|
+
* category, never the outcome.
|
|
306
|
+
*/
|
|
307
|
+
export type LoopGovernor = {
|
|
308
|
+
record: (o: {
|
|
309
|
+
iteration: number;
|
|
310
|
+
gap?: string;
|
|
311
|
+
}) => object;
|
|
312
|
+
struckOut: () => boolean;
|
|
313
|
+
report: () => {
|
|
314
|
+
strikes: number;
|
|
315
|
+
limit: number;
|
|
316
|
+
iterations: number;
|
|
317
|
+
distinctGaps?: number;
|
|
318
|
+
};
|
|
319
|
+
brief: () => string;
|
|
320
|
+
terminal?: (() => {
|
|
321
|
+
decision: string;
|
|
322
|
+
options: string[];
|
|
323
|
+
}) | undefined;
|
|
324
|
+
};
|