bareloop 0.9.0 → 0.10.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 +204 -0
- package/README.md +101 -117
- package/bareloop.context.md +139 -12
- package/package.json +1 -1
- package/src/authorflow.js +189 -22
- package/src/authoring.js +190 -0
- package/src/authorjob.js +11 -3
- package/src/authorscout.js +70 -9
- package/src/bridges.js +14 -1
- package/src/index.js +3 -0
- package/src/kinds.js +102 -11
- package/src/ledger.js +5 -1
- package/src/planrun.js +210 -25
- package/src/ralph.js +5 -0
- package/src/reuse.js +52 -2
- package/src/run.js +2 -1
- package/src/text.js +79 -0
- package/src/tools.js +11 -0
- package/src/validate.js +168 -11
- package/types/authorflow.d.ts +52 -8
- package/types/authoring.d.ts +112 -0
- package/types/authorjob.d.ts +3 -2
- package/types/authorscout.d.ts +16 -3
- package/types/index.d.ts +1 -1
- package/types/kinds.d.ts +58 -6
- package/types/planrun.d.ts +43 -0
- package/types/reuse.d.ts +1 -1
- package/types/text.d.ts +67 -0
- package/types/validate.d.ts +74 -9
package/src/validate.js
CHANGED
|
@@ -177,6 +177,69 @@ function unboundedQuantLen(src, i) {
|
|
|
177
177
|
return len;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/** How many characters of `(`-prologue sit at `src[i]` before the group's BODY:
|
|
181
|
+
* 1 for a plain or capturing group, 3 for `(?:` `(?=` `(?!`, 4 for `(?<=` `(?<!`,
|
|
182
|
+
* and past the `>` of a named group `(?<name>`. The body is read back when the
|
|
183
|
+
* group closes, and a prologue counted as body text would make `(?:a|aa)` split
|
|
184
|
+
* as `?:a` / `aa` — no prefix relation, and the hazard read as safe.
|
|
185
|
+
* @param {string} src @param {number} i index of the `(` */
|
|
186
|
+
function groupPrologueLen(src, i) {
|
|
187
|
+
if (src[i + 1] !== '?') return 1;
|
|
188
|
+
const c = src[i + 2];
|
|
189
|
+
if (c === ':' || c === '=' || c === '!') return 3;
|
|
190
|
+
if (c === '<') {
|
|
191
|
+
if (src[i + 3] === '=' || src[i + 3] === '!') return 4; // lookbehind
|
|
192
|
+
const close = src.indexOf('>', i + 3); // named group
|
|
193
|
+
return close === -1 ? 3 : close - i + 1;
|
|
194
|
+
}
|
|
195
|
+
return 1; // modifier groups etc — read the lot as body (fail-safe)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Do a group body's TOP-LEVEL alternation branches overlap — is one a prefix of
|
|
200
|
+
* another, equality included? That is the practical detectable core of the
|
|
201
|
+
* overlapping-alternation blowup (`(a|aa)`, `(a|a)`, `(\d|\d\d)`, `(a|a?)`):
|
|
202
|
+
* two branches that can consume the same text give the engine two ways to reach
|
|
203
|
+
* the same position, and an unbounded repeat multiplies them.
|
|
204
|
+
*
|
|
205
|
+
* The comparison is over the branches' SOURCE TEXT, which is what makes it
|
|
206
|
+
* conservative rather than exact — `(ab|abc)+` carries the prefix relation and
|
|
207
|
+
* did not blow up on the body measured. Exactness needs the real regex engine
|
|
208
|
+
* F49 declined to take as a dependency, and guessing it wrong would ADMIT an
|
|
209
|
+
* exponential pattern, so the over-rejection stands as a named limit.
|
|
210
|
+
*
|
|
211
|
+
* Splitting respects escapes, character classes and nested groups: a `|` inside
|
|
212
|
+
* `[a|b]` or inside `(x|y)` is not this group's alternation, and treating it as
|
|
213
|
+
* one would invent branches nobody wrote.
|
|
214
|
+
* @param {string} body the text between a group's prologue and its `)`
|
|
215
|
+
* @returns {boolean}
|
|
216
|
+
*/
|
|
217
|
+
function altBranchesOverlap(body) {
|
|
218
|
+
/** @type {string[]} */
|
|
219
|
+
const branches = [];
|
|
220
|
+
let start = 0;
|
|
221
|
+
let depth = 0;
|
|
222
|
+
for (let i = 0; i < body.length; i++) {
|
|
223
|
+
const c = body[i];
|
|
224
|
+
if (c === '\\') { i++; continue; }
|
|
225
|
+
if (c === '[') { i++; if (body[i] === '^') i++; while (i < body.length && body[i] !== ']') { if (body[i] === '\\') i++; i++; } continue; }
|
|
226
|
+
if (c === '(') { depth++; continue; }
|
|
227
|
+
if (c === ')') { depth--; continue; }
|
|
228
|
+
if (c === '|' && depth === 0) { branches.push(body.slice(start, i)); start = i + 1; }
|
|
229
|
+
}
|
|
230
|
+
if (branches.length === 0) return false; // no top-level alternation at all
|
|
231
|
+
branches.push(body.slice(start));
|
|
232
|
+
for (let i = 0; i < branches.length; i++) {
|
|
233
|
+
for (let j = 0; j < branches.length; j++) {
|
|
234
|
+
// `startsWith` covers EQUALITY, which is the 2ⁿ case: `(a|a)+$` took 52.3s
|
|
235
|
+
// on a 27-character body. Skipping i===j is what keeps that from being
|
|
236
|
+
// every pattern's own reflexive match.
|
|
237
|
+
if (i !== j && branches[j].startsWith(branches[i])) return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
180
243
|
/**
|
|
181
244
|
* F49 — a heuristic reject for the catastrophic-backtracking footgun: an
|
|
182
245
|
* unbounded quantifier applied to a group whose body itself repeats unboundedly
|
|
@@ -201,16 +264,40 @@ function unboundedQuantLen(src, i) {
|
|
|
201
264
|
* So the reject targets the dominant exponential class and fails it as a
|
|
202
265
|
* mechanical gap at the validation gate, before any tokens burn. A full ReDoS
|
|
203
266
|
* analyzer needs a real regex engine (an external native dep we do not take for
|
|
204
|
-
* a LOW issue)
|
|
205
|
-
*
|
|
267
|
+
* a LOW issue).
|
|
268
|
+
*
|
|
269
|
+
* SECOND CLASS, added 2026-08-12 — OVERLAPPING ALTERNATION under an unbounded
|
|
270
|
+
* repeat (`(a|aa)+`, `(a|a)+`, `(\d|\d\d)*`). F49 recorded this as a genuine
|
|
271
|
+
* false NEGATIVE of the shape approach and PARKED the widening, because changing
|
|
272
|
+
* the detector changes which SIGNED specs are admissible. It is closed here on
|
|
273
|
+
* measurement, one fresh process per point (a hot RegExp tiers up out of the V8
|
|
274
|
+
* interpreter, and a single-process sweep read a LONGER body as faster —
|
|
275
|
+
* backwards for an exponential, and the harness rather than the regex).
|
|
276
|
+
* `(a|aa)+$` against a run of `a` with one non-matching tail character:
|
|
277
|
+
*
|
|
278
|
+
* 27ch 218ms · 31ch 1,879ms · 35ch 12,082ms · 39ch 52,563ms · 41ch 189,420ms
|
|
279
|
+
*
|
|
280
|
+
* ~6.8× per 4 characters — the Fibonacci decomposition count, φ⁴. `(a|a)+$` (the
|
|
281
|
+
* 2ⁿ equal-branch case) took 52,256ms on 27ch, and `((a|aa))+$` — the redundant
|
|
282
|
+
* wrapper the `((a+))+` entry above exists for — 15,426ms on 35ch, which is why
|
|
283
|
+
* the overlap propagates up through a wrapper exactly as the inner repeat does.
|
|
284
|
+
* Input-bounding is theater here for F49's own reason: tens of characters suffice.
|
|
285
|
+
*
|
|
286
|
+
* The detected core is the PREFIX RELATION between top-level branches (one branch
|
|
287
|
+
* a prefix of another, equality included — `(a|a)+` is the 2ⁿ case). Whether a
|
|
288
|
+
* given prefix overlap can actually be DRIVEN exponentially depends on what
|
|
289
|
+
* follows the group, and deciding that needs the real engine this file declined
|
|
290
|
+
* to take as a dependency — so the reject is conservative by construction, which
|
|
291
|
+
* is the fail-safe direction.
|
|
292
|
+
*
|
|
293
|
+
* MONOTONIC, and that is the rule this change lives under: it only ever ADDS
|
|
294
|
+
* rejections. Nothing above is narrowed, and no previously-rejected shape became
|
|
295
|
+
* admissible (asserted over the whole corpus in tests/plan.test.js). F49's
|
|
296
|
+
* "never sharpen the detector" bars chasing false POSITIVES; closing a false
|
|
297
|
+
* NEGATIVE by adding rejections is the opposite, allowed direction — distinct
|
|
298
|
+
* rules, not in tension (the `((a+))+` widening set the precedent).
|
|
206
299
|
*
|
|
207
300
|
* SCOPE IS ASYMMETRIC, both directions named on purpose:
|
|
208
|
-
* - false NEGATIVE: overlapping-alternation blowup (`(a|ab)+`-class) is not
|
|
209
|
-
* detected — there is no inner QUANTIFIER to find, so the shape scan cannot
|
|
210
|
-
* see it; the blowup comes from overlapping alternation BRANCHES under a
|
|
211
|
-
* repeat. A genuine limit of the approach, not a bug in it. Widening the
|
|
212
|
-
* detector changes which SIGNED specs are admissible, which is
|
|
213
|
-
* arbiter-adjacent — parked, not chased (PRD v1.55).
|
|
214
301
|
* - false POSITIVE: a group whose repetitions are disambiguated by a literal
|
|
215
302
|
* anchor/delimiter (`(?:^- .+$\n?)+`, `(?:CHANGELOG:.+\n)+`) is FLAGGED even
|
|
216
303
|
* though a real engine runs it linearly — the scan sees the nested-quantifier
|
|
@@ -221,13 +308,28 @@ function unboundedQuantLen(src, i) {
|
|
|
221
308
|
* and guessing it wrong would ADMIT an exponential pattern — so the shape
|
|
222
309
|
* reject stands, and the over-rejection is a named, accepted limitation.
|
|
223
310
|
*
|
|
311
|
+
* The alternation class has its OWN instance of the same limit, measured and
|
|
312
|
+
* locked in `REDOS_OVERREJECTED_ALT`: `(ab|abc)+$`, `(x|xy|xyz)+$` and
|
|
313
|
+
* `(x(a|aa))+$` carry the prefix relation and did NOT blow up on the bodies
|
|
314
|
+
* measured (0.3–0.4ms at 41–91 characters). "Did not blow up on the body
|
|
315
|
+
* measured" is the honest claim — no adversarial search was run for a worse
|
|
316
|
+
* input — and rejecting them is again the direction that never admits an
|
|
317
|
+
* exponential pattern.
|
|
318
|
+
*
|
|
224
319
|
* The input is guaranteed to compile as a RegExp (checked first by the caller),
|
|
225
320
|
* so this scan assumes valid, balanced JS regex syntax.
|
|
226
321
|
* @param {string} src a compiled regex source string
|
|
227
|
-
* @returns {boolean} true iff a nested unbounded quantifier
|
|
322
|
+
* @returns {boolean} true iff a nested unbounded quantifier, or an unboundedly
|
|
323
|
+
* repeated group with overlapping alternation branches, is present
|
|
228
324
|
*/
|
|
229
325
|
export function hasNestedQuantifier(src) {
|
|
230
|
-
/**
|
|
326
|
+
/** `quant`: the body repeats unboundedly. `alt`: the body (or a group beneath
|
|
327
|
+
* it) carries overlapping alternation branches. `at`: where the body starts,
|
|
328
|
+
* so the branches can be read back when the group closes. The two hazard flags
|
|
329
|
+
* are tracked separately and propagate separately — merging them would let a
|
|
330
|
+
* plain nested repeat be reported as an alternation overlap and vice versa,
|
|
331
|
+
* and the two are different rewrites for the author. */
|
|
332
|
+
/** @type {{ quant: boolean, alt: boolean, at: number }[]} */
|
|
231
333
|
const stack = [];
|
|
232
334
|
for (let i = 0; i < src.length; i++) {
|
|
233
335
|
const c = src[i];
|
|
@@ -254,11 +356,19 @@ export function hasNestedQuantifier(src) {
|
|
|
254
356
|
while (i < src.length && src[i] !== ']') { if (src[i] === '\\') i++; i++; }
|
|
255
357
|
continue;
|
|
256
358
|
}
|
|
257
|
-
if (c === '(') { stack.push({ quant: false }); continue; }
|
|
359
|
+
if (c === '(') { stack.push({ quant: false, alt: false, at: i + groupPrologueLen(src, i) }); continue; }
|
|
258
360
|
if (c === ')') {
|
|
259
361
|
const g = stack.pop();
|
|
260
362
|
const qlen = unboundedQuantLen(src, i + 1);
|
|
261
363
|
if (g && g.quant && qlen) return true; // group repeats unboundedly AND its body did too
|
|
364
|
+
// …and the SECOND class: the body's own top-level branches overlap, so the
|
|
365
|
+
// engine has more than one way to consume the same text and an unbounded
|
|
366
|
+
// repeat multiplies those ways (MEASURED: `(a|aa)+$` 12.1s on a 35-char
|
|
367
|
+
// body). `g.alt` carries an overlap found in a group BENEATH this one, for
|
|
368
|
+
// the redundant-wrapper reason one comment down.
|
|
369
|
+
const overlap = !!g && (g.alt || altBranchesOverlap(src.slice(g.at, i)));
|
|
370
|
+
if (overlap && qlen) return true;
|
|
371
|
+
if (overlap && stack.length) stack[stack.length - 1].alt = true;
|
|
262
372
|
// The group is an unbounded-repeated atom of its parent when it is directly
|
|
263
373
|
// re-quantified (qlen) OR its own body already repeats unboundedly (g.quant):
|
|
264
374
|
// a redundant wrapper — ((a+))+ , (?:(a+))* , (((\d*)))+ — is the SAME
|
|
@@ -276,3 +386,50 @@ export function hasNestedQuantifier(src) {
|
|
|
276
386
|
}
|
|
277
387
|
return false;
|
|
278
388
|
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The catastrophic-backtracking reject, applied to a WHOLE signed document by
|
|
392
|
+
* tree walk — `sweepSecretLiterals`'s twin, and for its reason: some signed
|
|
393
|
+
* documents carry their regexes at a depth no per-field check enumerates.
|
|
394
|
+
*
|
|
395
|
+
* WHY IT EXISTS (PRD v1.60 §1, an OPEN GAP now closed). v1.55 took F49's reject
|
|
396
|
+
* to the operator's own regex fields, and the admissibility sweep that backed it
|
|
397
|
+
* walked `jobs/*.json`. That is not the population of signed specs: a REGISTRY
|
|
398
|
+
* BRIDGE is signed the same way, ships its plans VERBATIM (reuse stores the
|
|
399
|
+
* whole plan, never a template), and lives outside `jobs/`, so a pathological
|
|
400
|
+
* pattern arriving through a bridge was never swept. Nothing had decided
|
|
401
|
+
* bridge-borne patterns were safe — the sweep was written against the directory
|
|
402
|
+
* that held every spec at the time and was never re-aimed.
|
|
403
|
+
*
|
|
404
|
+
* WHAT is rejected is UNCHANGED: `hasNestedQuantifier`, one inventory, shared
|
|
405
|
+
* with job.js and plan.js. Only WHERE it looks is wider. Widening the DETECTOR
|
|
406
|
+
* is arbiter-adjacent and stays parked (F49); widening the POPULATION is this.
|
|
407
|
+
*
|
|
408
|
+
* KEY-SCOPED, deliberately. `pattern` and `gapKeep` are the exactly-three regex
|
|
409
|
+
* fields F49 names (`artifact-written.pattern`, `judged.pattern`, `gapKeep`);
|
|
410
|
+
* every other string in a signed document is prose, a path or a command, and
|
|
411
|
+
* running a regex-shape scan over prose would red goals for containing `(a+)+`
|
|
412
|
+
* in a sentence. A field carrying a regex under a NEW key is not swept until
|
|
413
|
+
* this list names it — stated so the limit is read rather than rediscovered.
|
|
414
|
+
*
|
|
415
|
+
* COMPILE-FIRST, like both per-field callers: the scan's contract is that its
|
|
416
|
+
* input is valid regex syntax, and a string that does not compile is the OTHER
|
|
417
|
+
* validator's red. Two reds on one string bury the real one.
|
|
418
|
+
* @param {any} root
|
|
419
|
+
* @param {(code: string, path: string, detail?: string) => void} red
|
|
420
|
+
*/
|
|
421
|
+
export function sweepNestedQuantifiers(root, red) {
|
|
422
|
+
(/** @type {(node: any, at: string, key: string) => void} */
|
|
423
|
+
function sweep(node, at, key) {
|
|
424
|
+
if (typeof node === 'string') {
|
|
425
|
+
if (key !== 'pattern' && key !== 'gapKeep') return;
|
|
426
|
+
try { new RegExp(node, 'm'); } catch { return; }
|
|
427
|
+
if (hasNestedQuantifier(node)) {
|
|
428
|
+
red('invalid-value', at, `catastrophic-backtracking risk: a nested unbounded quantifier (${node}) can hang the untimed evaluator on the MAIN event loop, where the in-process stall fuse cannot fire (F49). Rewrite the pattern — drop the outer repeat, or match once.`);
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(node)) node.forEach((v, i) => sweep(v, `${at}.${i}`, key));
|
|
433
|
+
else if (isObj(node)) for (const [k, v] of Object.entries(node)) sweep(v, at ? `${at}.${k}` : k, k);
|
|
434
|
+
})(root, '', '');
|
|
435
|
+
}
|
package/types/authorflow.d.ts
CHANGED
|
@@ -149,6 +149,31 @@ export function declarationTool(box: {
|
|
|
149
149
|
* @param {Record<string, any>} [catalogue]
|
|
150
150
|
*/
|
|
151
151
|
export function catalogueBlock(catalogue?: Record<string, any>): string;
|
|
152
|
+
/**
|
|
153
|
+
* HOW THE GENRE'S OWN TOOLS PRINT — rendered FROM `genreInstruments`, so the
|
|
154
|
+
* pattern the author is handed and the pattern the tree already grades with are
|
|
155
|
+
* one string.
|
|
156
|
+
*
|
|
157
|
+
* This block is the fix for run msmbpjk6: a parser for a tool WE OWN THE FACTS
|
|
158
|
+
* ABOUT was left to the model to compose, and it composed tsc's terminal-only
|
|
159
|
+
* shape against piped output — 0 matches on 67 real error lines. The delivery is
|
|
160
|
+
* deliberately the MANDATORY-GUARD delivery, not advice: the pattern arrives
|
|
161
|
+
* spelled out, marked genre property, with the real captured line beside it, in
|
|
162
|
+
* the same register as the guard battery that arrives already parameterised.
|
|
163
|
+
*
|
|
164
|
+
* It states the MECHANISM (pipe, not a terminal) rather than only the answer,
|
|
165
|
+
* because the same trap is waiting behind every tool that pretty-prints for a
|
|
166
|
+
* human, and only some of them are in the table yet. That prediction has since
|
|
167
|
+
* been paid off by measurement: `node --test` prints TAP down a pipe and the
|
|
168
|
+
* spec reporter on a terminal, the identical flip, on a second tool.
|
|
169
|
+
*
|
|
170
|
+
* Each row also carries its `capture`, because a count term and a tally term are
|
|
171
|
+
* read by DIFFERENT aggregates and the wrong pairing is a silent misreading, not
|
|
172
|
+
* an error — `first` over a line the tool omits on a good run reports a broken
|
|
173
|
+
* instrument. The block states that mapping once rather than per row.
|
|
174
|
+
* @param {string} lang
|
|
175
|
+
*/
|
|
176
|
+
export function instrumentsBlock(lang: string): string;
|
|
152
177
|
/** The three laws. Stated to the author; enforced, where checkable, by M2. */
|
|
153
178
|
export function lawsBlock(): string;
|
|
154
179
|
/**
|
|
@@ -321,7 +346,10 @@ export function resolveSourcePrefixes({ workdir, sourcePaths, seedFiles }: {
|
|
|
321
346
|
* run could not be read afterwards. A cost entry with no raw beside it is that
|
|
322
347
|
* run again.
|
|
323
348
|
*/
|
|
324
|
-
|
|
349
|
+
/** @param {{ceilingUsd?: number|null}} [o] the OPERATOR's ceiling; `null`/absent is UNBOUNDED */
|
|
350
|
+
export function makeCostBook({ ceilingUsd }?: {
|
|
351
|
+
ceilingUsd?: number | null;
|
|
352
|
+
}): {
|
|
325
353
|
/** @param {string} label @param {any} r a bare-agent run result @param {number} [attempt] */
|
|
326
354
|
add(label: string, r: any, attempt?: number): {
|
|
327
355
|
label: string;
|
|
@@ -347,17 +375,28 @@ export function makeCostBook(): {
|
|
|
347
375
|
reason: string | null;
|
|
348
376
|
}[];
|
|
349
377
|
report(): {
|
|
350
|
-
costUsd: number | null;
|
|
351
|
-
knownUsd: number;
|
|
352
|
-
spendComplete: boolean;
|
|
353
|
-
nullCostCalls: number;
|
|
354
|
-
unpricedRounds: number;
|
|
355
378
|
calls: {
|
|
356
379
|
label: string;
|
|
357
380
|
costUsd: number | null;
|
|
358
381
|
unpricedRounds: number;
|
|
359
382
|
}[];
|
|
383
|
+
costUsd: number | null;
|
|
384
|
+
knownUsd: number;
|
|
385
|
+
spendComplete: boolean;
|
|
386
|
+
nullCostCalls: number;
|
|
387
|
+
unpricedRounds: number;
|
|
360
388
|
};
|
|
389
|
+
/**
|
|
390
|
+
* THE CEILING'S OWN READING, asked between metered calls. The book is the one
|
|
391
|
+
* place this pipeline's spend accumulates, so it is the one place that can
|
|
392
|
+
* answer honestly — and it answers through the shared predicate rather than a
|
|
393
|
+
* second hand-spelled comparison (`src/text.js`, beside `priceOf`).
|
|
394
|
+
* @returns {'cap-halt'|'pricing-red'|null}
|
|
395
|
+
*/
|
|
396
|
+
capStop(): "cap-halt" | "pricing-red" | null;
|
|
397
|
+
/** what was set, stated — `null` is "nobody set one", which is UNBOUNDED and
|
|
398
|
+
* a visible operator choice, never a defaulted number */
|
|
399
|
+
ceilingUsd: number | null;
|
|
361
400
|
};
|
|
362
401
|
/**
|
|
363
402
|
* The default model boundary: one bare-agent Loop per call, with the declaration
|
|
@@ -406,13 +445,15 @@ export function makeLoopGenerate(provider: any, { system, maxTokens }?: {
|
|
|
406
445
|
*
|
|
407
446
|
* @param {{workdir: string, seedRef: string, lang: string, verdictType: string,
|
|
408
447
|
* answers: Record<string|number, string>, questions?: Record<string|number, string>,
|
|
409
|
-
* scout: {state: string, facts: any, reason?: string|null, calls?: any[], raws?: any[]
|
|
448
|
+
* scout: {state: string, facts: any, reason?: string|null, cause?: string|null, calls?: any[], raws?: any[],
|
|
449
|
+
* budgetStop?: 'cap-halt'|'pricing-red'|null},
|
|
410
450
|
* listing?: {stop: string|null, files: string[]|null, block: string|null, meta: any}|null,
|
|
411
451
|
* generate: Function, seedReadFn?: Function, closeCtx?: any,
|
|
452
|
+
* ceilingUsd?: number|null,
|
|
412
453
|
* maxRevisions?: number, structureRetries?: number,
|
|
413
454
|
* structuredMode?: 'tool'|'text', catalogue?: Record<string, any>}} o
|
|
414
455
|
*/
|
|
415
|
-
export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, scout, listing, generate, seedReadFn, closeCtx, maxRevisions, structureRetries, structuredMode, catalogue, }: {
|
|
456
|
+
export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, scout, listing, generate, seedReadFn, closeCtx, ceilingUsd, maxRevisions, structureRetries, structuredMode, catalogue, }: {
|
|
416
457
|
workdir: string;
|
|
417
458
|
seedRef: string;
|
|
418
459
|
lang: string;
|
|
@@ -423,8 +464,10 @@ export function authorClose({ workdir, seedRef, lang, verdictType, answers, ques
|
|
|
423
464
|
state: string;
|
|
424
465
|
facts: any;
|
|
425
466
|
reason?: string | null;
|
|
467
|
+
cause?: string | null;
|
|
426
468
|
calls?: any[];
|
|
427
469
|
raws?: any[];
|
|
470
|
+
budgetStop?: "cap-halt" | "pricing-red" | null;
|
|
428
471
|
};
|
|
429
472
|
listing?: {
|
|
430
473
|
stop: string | null;
|
|
@@ -435,6 +478,7 @@ export function authorClose({ workdir, seedRef, lang, verdictType, answers, ques
|
|
|
435
478
|
generate: Function;
|
|
436
479
|
seedReadFn?: Function;
|
|
437
480
|
closeCtx?: any;
|
|
481
|
+
ceilingUsd?: number | null;
|
|
438
482
|
maxRevisions?: number;
|
|
439
483
|
structureRetries?: number;
|
|
440
484
|
structuredMode?: "tool" | "text";
|
package/types/authoring.d.ts
CHANGED
|
@@ -102,6 +102,60 @@ export function genreEnv(lang: string, { sourcePrefixes }?: {
|
|
|
102
102
|
* one of them, because ownership is only real if it is mechanical.
|
|
103
103
|
* @param {string} lang @returns {string[]} */
|
|
104
104
|
export function genreOwnedEnvNames(lang: string): string[];
|
|
105
|
+
/**
|
|
106
|
+
* HOW THE GENRE'S OWN TOOLS PRINT — genre property, like the suppression battery
|
|
107
|
+
* and like `MYPYPATH`, and for the same reason: it is a FACT ABOUT AN INSTRUMENT
|
|
108
|
+
* WE ALREADY OWN, not a composition choice. Seven hand-written closes read tsc
|
|
109
|
+
* with the identical `/error TS\d+/` (`scripts/u-pulselog-close.mjs:122`,
|
|
110
|
+
* `u-bareguard-close.mjs:123`, `u-bareagent-close.mjs:123`,
|
|
111
|
+
* `u-baremobile-close.mjs:122`, `u-litectx-close.mjs:140`, `types-close.mjs:190`,
|
|
112
|
+
* `types-check.mjs:60`) and one reads mypy with `' error: '`
|
|
113
|
+
* (`u-spawner-close.mjs:133`) — the same provenance the SUPPRESSIONS table has:
|
|
114
|
+
* operator knowledge minted by paying for live runs.
|
|
115
|
+
*
|
|
116
|
+
* WHY IT BECAME GENRE DATA (run msmbpjk6, 2026-08-09, $0.43). Left to the model,
|
|
117
|
+
* the parser for a known tool is a DRAFT-TIME LOTTERY on a fact nobody needed to
|
|
118
|
+
* guess: that run composed tsc's TTY-only pretty shape
|
|
119
|
+
* (`^\S+\.js:\d+:\d+ - error TS\d+:`), which matched 0 of the 67 real error lines
|
|
120
|
+
* the piped run actually printed, while the tool exited 2. The gates held — the
|
|
121
|
+
* stage read as a crashed instrument and signing was refused, correctly — but the
|
|
122
|
+
* refusal cost a whole authoring run to learn something the tree already knew. An
|
|
123
|
+
* earlier run (mslwbkz7) happened to roll the right shape. A coin-flip on an
|
|
124
|
+
* owned fact is not a capability question.
|
|
125
|
+
*
|
|
126
|
+
* WHAT THIS IS NOT: it does not close the parser. A stage counting something we
|
|
127
|
+
* have NOT measured still writes its own pattern, and that remains a real gap —
|
|
128
|
+
* named, not smoothed. This table is where a measured instrument fact lands, one
|
|
129
|
+
* at a time, with its provenance.
|
|
130
|
+
*
|
|
131
|
+
* THE TEST-COUNT FACTS (2026-08-13) landed under exactly that rule, and closed
|
|
132
|
+
* two of the three gaps the paragraph above used to name by hand: the executed
|
|
133
|
+
* count and the failure line, for both runners, are now measured facts rather
|
|
134
|
+
* than draft-time lotteries. Provenance rides on each entry.
|
|
135
|
+
*
|
|
136
|
+
* WHAT STAYED OUT, deliberately, because no evidence covers it:
|
|
137
|
+
* - a pytest FAIL-COUNT figure. A green pytest run omits the segment entirely
|
|
138
|
+
* (`1 passed in 0.04s`), so a figure term over it reports "never reported" on
|
|
139
|
+
* precisely the runs that went well. Failures are counted with the `^FAILED `
|
|
140
|
+
* tally instead, where no match IS zero.
|
|
141
|
+
* - a pytest EXECUTED count. `collected N items` is printed under `-ra` and NOT
|
|
142
|
+
* under `-q` (measured both ways), so no single spelling survives the modes a
|
|
143
|
+
* patient may configure; a stage needing it still composes its own and says so.
|
|
144
|
+
*
|
|
145
|
+
* Returns a fresh copy per call, for the reason `classGuards` does: a frozen
|
|
146
|
+
* constant handed to a caller that edits it is one shared table away from a
|
|
147
|
+
* silently changed ruler.
|
|
148
|
+
* @param {string} lang
|
|
149
|
+
* @returns {{id: string, what: string, lineMatch: string, capture: number|null, example: string, why: string}[]}
|
|
150
|
+
*/
|
|
151
|
+
export function genreInstruments(lang: string): {
|
|
152
|
+
id: string;
|
|
153
|
+
what: string;
|
|
154
|
+
lineMatch: string;
|
|
155
|
+
capture: number | null;
|
|
156
|
+
example: string;
|
|
157
|
+
why: string;
|
|
158
|
+
}[];
|
|
105
159
|
/**
|
|
106
160
|
* GROUNDING for a RECORDED genre environment — the provenance half of the
|
|
107
161
|
* owned-env rule, and the reason the by-value comparison is worth anything.
|
|
@@ -368,6 +422,42 @@ export const TYPES_GENRE: Readonly<{
|
|
|
368
422
|
/** no import-resolution hazard on this runner — an EMPTY list, declared,
|
|
369
423
|
* never an absent field that would read as "not looked into" (F59) */
|
|
370
424
|
env: readonly never[];
|
|
425
|
+
instruments: readonly (Readonly<{
|
|
426
|
+
id: "tsc-error-line";
|
|
427
|
+
what: "one type error, as the TypeScript checker prints it";
|
|
428
|
+
lineMatch: "error TS\\d+";
|
|
429
|
+
capture: null;
|
|
430
|
+
example: "src/backup.js(21,19): error TS7006: Parameter 'now' implicitly has an 'any' type.";
|
|
431
|
+
why: string;
|
|
432
|
+
}> | Readonly<{
|
|
433
|
+
id: "node-test-total-count";
|
|
434
|
+
what: "how many tests the runner EXECUTED, off the TAP summary";
|
|
435
|
+
lineMatch: "^# tests (\\d+)$";
|
|
436
|
+
capture: 1;
|
|
437
|
+
example: "# tests 317";
|
|
438
|
+
why: string;
|
|
439
|
+
}> | Readonly<{
|
|
440
|
+
id: "node-test-pass-count";
|
|
441
|
+
what: "how many tests passed, off the TAP summary";
|
|
442
|
+
lineMatch: "^# pass (\\d+)$";
|
|
443
|
+
capture: 1;
|
|
444
|
+
example: "# pass 316";
|
|
445
|
+
why: string;
|
|
446
|
+
}> | Readonly<{
|
|
447
|
+
id: "node-test-fail-count";
|
|
448
|
+
what: "how many tests failed, off the TAP summary";
|
|
449
|
+
lineMatch: "^# fail (\\d+)$";
|
|
450
|
+
capture: 1;
|
|
451
|
+
example: "# fail 1";
|
|
452
|
+
why: string;
|
|
453
|
+
}> | Readonly<{
|
|
454
|
+
id: "node-test-failed-line";
|
|
455
|
+
what: "one failing test, as the TAP reporter names it";
|
|
456
|
+
lineMatch: "^not ok \\d+ ";
|
|
457
|
+
capture: null;
|
|
458
|
+
example: "not ok 7 - edit-renotify: a non-participant edit, and edits on a pending event, ping no one";
|
|
459
|
+
why: string;
|
|
460
|
+
}>)[];
|
|
371
461
|
}>;
|
|
372
462
|
python: Readonly<{
|
|
373
463
|
suppressions: readonly (Readonly<{
|
|
@@ -393,6 +483,28 @@ export const TYPES_GENRE: Readonly<{
|
|
|
393
483
|
join: ":";
|
|
394
484
|
why: string;
|
|
395
485
|
}>[];
|
|
486
|
+
instruments: readonly (Readonly<{
|
|
487
|
+
id: "mypy-error-line";
|
|
488
|
+
what: "one type error, as mypy prints it";
|
|
489
|
+
lineMatch: " error: ";
|
|
490
|
+
capture: null;
|
|
491
|
+
example: "packages/spawner/src/aurora_spawner/timeout_policy.py:89: error: Statement is unreachable [unreachable]";
|
|
492
|
+
why: string;
|
|
493
|
+
}> | Readonly<{
|
|
494
|
+
id: "pytest-pass-count";
|
|
495
|
+
what: "how many tests passed, off pytest's final counts line";
|
|
496
|
+
lineMatch: "(\\d+) passed";
|
|
497
|
+
capture: 1;
|
|
498
|
+
example: "= 1 failed, 2690 passed, 3 skipped, 18 deselected, 1775 warnings in 326.12s (0:05:26) =";
|
|
499
|
+
why: string;
|
|
500
|
+
}> | Readonly<{
|
|
501
|
+
id: "pytest-failed-line";
|
|
502
|
+
what: "one failing test, as pytest's short summary names it";
|
|
503
|
+
lineMatch: "^FAILED ";
|
|
504
|
+
capture: null;
|
|
505
|
+
example: "FAILED packages/soar/tests/test_agent_registry_deprecation.py::TestAgentRegistryDeprecation::test_registry_and_discovery_equivalent_results";
|
|
506
|
+
why: string;
|
|
507
|
+
}>)[];
|
|
396
508
|
}>;
|
|
397
509
|
}>;
|
|
398
510
|
}>;
|
package/types/authorjob.d.ts
CHANGED
|
@@ -81,14 +81,14 @@ export function refuseLockedKind(kind: string): Refusal;
|
|
|
81
81
|
* @param {{answers: Record<string|number, any>, repoPath?: string|null, lang: string,
|
|
82
82
|
* verdictType?: string|null,
|
|
83
83
|
* questions?: Record<string|number, string>|null, generate?: Function, provider?: any,
|
|
84
|
-
* seedRef?: string|null, scout?: any, listing?: any,
|
|
84
|
+
* seedRef?: string|null, scout?: any, listing?: any, ceilingUsd?: number|null,
|
|
85
85
|
* seedFn?: Function, scoutFn?: Function, listingFn?: Function,
|
|
86
86
|
* authorFn?: Function, authorOpts?: object}} o
|
|
87
87
|
* @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
|
|
88
88
|
* closeDecl: any, seedRef: string|null, authoring: any, reds: Red[], stop: string|null,
|
|
89
89
|
* cost: any, interview: any}>}
|
|
90
90
|
*/
|
|
91
|
-
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, seedFn, scoutFn, listingFn, authorFn, authorOpts, }: {
|
|
91
|
+
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, seedFn, scoutFn, listingFn, authorFn, authorOpts, }: {
|
|
92
92
|
answers: Record<string | number, any>;
|
|
93
93
|
repoPath?: string | null;
|
|
94
94
|
lang: string;
|
|
@@ -99,6 +99,7 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
|
|
|
99
99
|
seedRef?: string | null;
|
|
100
100
|
scout?: any;
|
|
101
101
|
listing?: any;
|
|
102
|
+
ceilingUsd?: number | null;
|
|
102
103
|
seedFn?: Function;
|
|
103
104
|
scoutFn?: Function;
|
|
104
105
|
listingFn?: Function;
|
package/types/authorscout.d.ts
CHANGED
|
@@ -91,13 +91,14 @@ export function defaultSurveyor({ workdir, granted, auditPath, ctx }: {
|
|
|
91
91
|
*
|
|
92
92
|
* @param {{workdir: string, provider?: any,
|
|
93
93
|
* rounds?: number, minBytes?: number, blobMax?: number, maxTokens?: number, ctx?: boolean,
|
|
94
|
-
* attempts?: number,
|
|
94
|
+
* attempts?: number, ceilingUsd?: number|null,
|
|
95
95
|
* createLoop?: (o: {system: string, policy: any, onLlmResult: any, provider: any}) => any,
|
|
96
96
|
* createSurveyor?: (o: {workdir: string, granted: readonly string[], ctx: boolean}) => Promise<any>}} o
|
|
97
97
|
* @returns {Promise<Survey & {raw: string, raws: ReturnType<typeof scrubRaw>[],
|
|
98
|
-
* calls: {label: string, costUsd: number|null, unpricedRounds: number}[]
|
|
98
|
+
* calls: {label: string, costUsd: number|null, unpricedRounds: number}[],
|
|
99
|
+
* budgetStop: 'cap-halt'|'pricing-red'|null}>}
|
|
99
100
|
*/
|
|
100
|
-
export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, createLoop, createSurveyor, }: {
|
|
101
|
+
export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, ceilingUsd, createLoop, createSurveyor, }: {
|
|
101
102
|
workdir: string;
|
|
102
103
|
provider?: any;
|
|
103
104
|
rounds?: number;
|
|
@@ -106,6 +107,7 @@ export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, m
|
|
|
106
107
|
maxTokens?: number;
|
|
107
108
|
ctx?: boolean;
|
|
108
109
|
attempts?: number;
|
|
110
|
+
ceilingUsd?: number | null;
|
|
109
111
|
createLoop?: (o: {
|
|
110
112
|
system: string;
|
|
111
113
|
policy: any;
|
|
@@ -125,6 +127,7 @@ export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, m
|
|
|
125
127
|
costUsd: number | null;
|
|
126
128
|
unpricedRounds: number;
|
|
127
129
|
}[];
|
|
130
|
+
budgetStop: "cap-halt" | "pricing-red" | null;
|
|
128
131
|
}>;
|
|
129
132
|
/**
|
|
130
133
|
* `git ls-tree -r --name-only <seed>` — the files as of the SEED COMMIT.
|
|
@@ -249,6 +252,16 @@ export const SURVEY_CAUSES: Readonly<{
|
|
|
249
252
|
NON_OBJECT: "non-object";
|
|
250
253
|
/** valid JSON, no content — F59's whole point, and a SEMANTIC failure */
|
|
251
254
|
EMPTY_OBJECT: "empty-object";
|
|
255
|
+
/**
|
|
256
|
+
* THE SURVEY WAS NEVER ASKED FOR: the operator's money ceiling was already
|
|
257
|
+
* spent when the first attempt came due, so no call was made. Named as its own
|
|
258
|
+
* cause for the reason this enum exists at all — a new route to ABSENT has to
|
|
259
|
+
* be named, and naming it is the moment somebody decides whether it retries.
|
|
260
|
+
* This one does not, and cannot: a retry is precisely what the cap forbade.
|
|
261
|
+
* It is deliberately ABSENT of the model's own doing, and the prose says so —
|
|
262
|
+
* "the scout did not complete" would blame a model that never spoke.
|
|
263
|
+
*/
|
|
264
|
+
NOT_FUNDED: "not-funded";
|
|
252
265
|
}>;
|
|
253
266
|
/**
|
|
254
267
|
* THE MALFORMED CLASS — the only causes a retry fires on.
|
package/types/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBr
|
|
|
12
12
|
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from "./ledger.js";
|
|
13
13
|
export { workBranchName, prepareWorkBranch, WORK_BRANCH_PREFIX, WORK_BRANCH_RE } from "./workbranch.js";
|
|
14
14
|
export { runDeclaredClose, runStage, seedRead, seedAtHead, seedListing, changedSet, makeSeedTrees, LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER, EXIT_GREEN, EXIT_RED, EXIT_STOP } from "./kinds.js";
|
|
15
|
-
export { validateDeclaration, normalizeDeclaration, KIND_CATALOGUE, CATALOGUE_KINDS, CATALOGUE_LIVE_KINDS, LOCKED_KINDS, TYPES_GENRE, TYPES_GENRE_TEMPLATE, GENRE_LANGUAGES, genreEnv, genreOwnedEnvNames, DIRECTIONS, BASELINES, MAX_STAGES, VERDICT_CLASSES, LOCKED_CLASSES, LIVE_CLASSES, CLASS_BATTERIES, classGuards, closeCeiling } from "./authoring.js";
|
|
15
|
+
export { validateDeclaration, normalizeDeclaration, KIND_CATALOGUE, CATALOGUE_KINDS, CATALOGUE_LIVE_KINDS, LOCKED_KINDS, TYPES_GENRE, TYPES_GENRE_TEMPLATE, GENRE_LANGUAGES, genreEnv, genreOwnedEnvNames, genreInstruments, DIRECTIONS, BASELINES, MAX_STAGES, VERDICT_CLASSES, LOCKED_CLASSES, LIVE_CLASSES, CLASS_BATTERIES, classGuards, closeCeiling } from "./authoring.js";
|
|
16
16
|
export { runAuthorScout, buildSeedListing, seedFileList, classifySurvey, AUTHOR_SCOUT_VERBS, SCOUT_ATTEMPTS, SURVEY_CAUSES, SCOUT_RETRY_CAUSES } from "./authorscout.js";
|
|
17
17
|
export { scrubRaw, RAW_PERSIST_MAX, RAW_TRIM_MARKER } from "./text.js";
|
|
18
18
|
export { authorClose, authorPrompt, declarationSchema, makeLoopGenerate, MAX_REVISIONS, QUESTION_SETS, GREEN_QUESTIONS, CLASS_STATEMENTS, questionsFor, requiredAnswersFor } from "./authorflow.js";
|
package/types/kinds.d.ts
CHANGED
|
@@ -36,15 +36,25 @@ export function seedListing(workdir: string, seedRef: string): Promise<{
|
|
|
36
36
|
files: string[];
|
|
37
37
|
}>;
|
|
38
38
|
/**
|
|
39
|
-
* The
|
|
39
|
+
* The arbiter books, and the shape of each exclusion is the whole point.
|
|
40
40
|
*
|
|
41
|
-
* `.litectx/`
|
|
42
|
-
* arbiter. `gate-audit.jsonl` is an EXACT repo-relative path and
|
|
43
|
-
* basename or a pattern: the gate audit is written to
|
|
41
|
+
* `.litectx/` and `.smoke/` are PREFIXES — everything beneath either store
|
|
42
|
+
* belongs to the arbiter. `gate-audit.jsonl` is an EXACT repo-relative path and
|
|
43
|
+
* never a basename or a pattern: the gate audit is written to
|
|
44
44
|
* `join(workdir, 'gate-audit.jsonl')` and nowhere else, so a worker-authored
|
|
45
45
|
* `src/gate-audit.jsonl` is NOT an arbiter book and must still count as work.
|
|
46
46
|
* Matching by basename would swallow it — and swallowing a worker's real
|
|
47
|
-
* write is the failure direction that reads as a clean tree.
|
|
47
|
+
* write is the failure direction that reads as a clean tree. `src/smoke.js` and
|
|
48
|
+
* `tests/smoke.test.js` are that same decoy for the store below.
|
|
49
|
+
*
|
|
50
|
+
* `.smoke/` — F98's parked residual, closed here. The fence has always denied
|
|
51
|
+
* `join(workdir, '.smoke')` on every worker (src/planrun.js, src/authorscout.js)
|
|
52
|
+
* and the persona now states the rule; this reader, which decides what COUNTS AS
|
|
53
|
+
* WORK, was the one place that never knew the name. On patients whose .gitignore
|
|
54
|
+
* default-denies dot-directories the store never reaches `ls-files --others` and
|
|
55
|
+
* the gap is masked — by the PATIENT's config, not by anything here. Incidental
|
|
56
|
+
* masking is not an exclusion: on a patient without that line the arbiter's own
|
|
57
|
+
* store would enter the changed set as the worker's writing.
|
|
48
58
|
* @param {string} rel a repo-relative POSIX path
|
|
49
59
|
* @returns {string|null} the book it belongs to, or null
|
|
50
60
|
*/
|
|
@@ -146,13 +156,36 @@ export function normalizeParser(parser: any, at?: string): {
|
|
|
146
156
|
* that matched BEFORE the filter judged them — kept, unattributable and dropped
|
|
147
157
|
* alike — and it is the only evidence of liveness a caller has once the scope
|
|
148
158
|
* has removed the whole population. It is never the number being read.
|
|
159
|
+
*
|
|
160
|
+
* `matched` is the KEPT lines themselves, PER TERM and index-aligned with
|
|
161
|
+
* `breakdown`: entry i holds, in output order, exactly the lines term i's
|
|
162
|
+
* subtotal was computed FROM. Run u-msn227nq paid for the echo — the executor
|
|
163
|
+
* counted 8 real `error TS…` lines and told the worker "8 match(es)", discarding
|
|
164
|
+
* every address, and a worker with a number and no file to open went looking for
|
|
165
|
+
* the arbiter's own books instead.
|
|
166
|
+
*
|
|
167
|
+
* TWO EXCLUSIONS, both of them "this line is not what the count is made of":
|
|
168
|
+
* - a scope-DROPPED line is never in here. It was never counted, so aiming the
|
|
169
|
+
* worker at it would aim it outside its own population.
|
|
170
|
+
* - under `first`, only the line the aggregate actually READ. The later matches
|
|
171
|
+
* are real matches (`breakdown[i].matches` still counts them) but the value
|
|
172
|
+
* never contained them, and echoing them points the worker at a number that
|
|
173
|
+
* is not in the reading.
|
|
174
|
+
*
|
|
175
|
+
* And the GROUPING is the third: a term's sign lives in `breakdown[i]`, so lines
|
|
176
|
+
* pooled flat across terms lose the one fact that says what they DID to the
|
|
177
|
+
* number. A subtracted term's line rendered beside an added one reads as a wall
|
|
178
|
+
* to go fix, and fixing it moves the count the wrong way. Deduplication is
|
|
179
|
+
* therefore within a term only — one line matched by two terms plays two
|
|
180
|
+
* arithmetic roles and is named under each. The count is computed from `values`,
|
|
181
|
+
* never from this list.
|
|
149
182
|
* @param {string} output @param {any[]} terms normalised terms
|
|
150
183
|
* Every stop here is `crashed` in the arbiter's vocabulary — the command RAN,
|
|
151
184
|
* came back, and the number could not be read out of it. Nothing in this
|
|
152
185
|
* function can observe a spawn failure, a timeout or a signal, so it never
|
|
153
186
|
* claims one.
|
|
154
187
|
* @param {{scope?: any, workdir?: string}} [o]
|
|
155
|
-
* @returns {{stop: string, fault: string, notes?: string[]}|{stop: null, value: number, breakdown: any[], notes: string[], preScopeMatches: number}}
|
|
188
|
+
* @returns {{stop: string, fault: string, notes?: string[]}|{stop: null, value: number, breakdown: any[], notes: string[], preScopeMatches: number, matched: string[][]}}
|
|
156
189
|
*/
|
|
157
190
|
export function parseValue(output: string, terms: any[], { scope, workdir }?: {
|
|
158
191
|
scope?: any;
|
|
@@ -167,6 +200,7 @@ export function parseValue(output: string, terms: any[], { scope, workdir }?: {
|
|
|
167
200
|
breakdown: any[];
|
|
168
201
|
notes: string[];
|
|
169
202
|
preScopeMatches: number;
|
|
203
|
+
matched: string[][];
|
|
170
204
|
};
|
|
171
205
|
/**
|
|
172
206
|
* A caller-scoped holder for detached seed worktrees. Scoped rather than
|
|
@@ -296,6 +330,24 @@ export const SIGNS: readonly number[];
|
|
|
296
330
|
* counted demand (the request-red path), and reaching one at runtime is a
|
|
297
331
|
* stop — never a red, and never a silent pass. */
|
|
298
332
|
export const LIVE_KINDS: readonly string[];
|
|
333
|
+
/**
|
|
334
|
+
* The arbiter's book NAMES, one home. F98's mechanism was exactly this list
|
|
335
|
+
* living as independent literals: the fence deny arrays (src/planrun.js,
|
|
336
|
+
* src/authorscout.js) knew `.smoke` while `isArbiterBook` — the reader that
|
|
337
|
+
* decides what COUNTS AS WORK — did not, and the drift was masked by the
|
|
338
|
+
* patient's own .gitignore. Every consumer (the fence deny lists, this file's
|
|
339
|
+
* reader, the persona's stated law, run.js's smoke-store root) now spells the
|
|
340
|
+
* names from here, so adding or renaming a book is one edit that cannot leave
|
|
341
|
+
* a site behind.
|
|
342
|
+
*
|
|
343
|
+
* `ARBITER_BOOK_STORES` are directory PREFIXES; `GATE_AUDIT_FILE` is an EXACT
|
|
344
|
+
* repo-relative path (see `isArbiterBook`'s docstring for why never a
|
|
345
|
+
* basename). Order is the fence deny-list order and the persona's prose order.
|
|
346
|
+
*/
|
|
347
|
+
export const SMOKE_STORE: ".smoke";
|
|
348
|
+
export const LITECTX_STORE: ".litectx";
|
|
349
|
+
export const GATE_AUDIT_FILE: "gate-audit.jsonl";
|
|
350
|
+
export const ARBITER_BOOK_STORES: readonly string[];
|
|
299
351
|
export type Red = {
|
|
300
352
|
code: string;
|
|
301
353
|
path: string;
|