bareloop 0.43.0 → 0.44.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 +42 -0
- package/bareloop.context.md +52 -13
- package/package.json +2 -2
- package/src/authorflow.js +26 -11
- package/src/authoring.js +1 -1
- package/src/authorjob.js +27 -15
- package/src/authorreadout.js +7 -6
- package/src/declaredclose.js +4 -1
- package/src/draftspend.js +20 -2
- package/src/facts.js +54 -9
- package/src/factwords.js +58 -6
- package/src/inputlook.js +38 -3
- package/src/job.js +1 -1
- package/src/kinds.js +5 -0
- package/src/pagedropwords.js +64 -0
- package/src/pagemap.js +264 -0
- package/src/pagerules.js +319 -0
- package/src/panel/authorroutes.js +34 -30
- package/src/panel/authorsession.js +29 -5
- package/src/panel/index.html +465 -65
- package/src/panel/jobplan.js +4 -2
- package/src/panel/runroutes.js +46 -22
- package/src/panel/server.js +791 -28
- package/src/panel/status.js +33 -0
- package/src/panelmoney.js +29 -0
- package/src/planrun.js +53 -4
- package/src/promptregisters.js +4 -0
- package/src/replay.js +118 -7
- package/src/rubricstep.js +123 -11
- package/src/runlist.js +2 -1
- package/src/schedulewords.js +6 -3
- package/src/source.js +18 -0
- package/src/thinking.js +63 -2
- package/src/userrun.js +28 -14
- package/types/authorflow.d.ts +42 -6
- package/types/authorjob.d.ts +10 -2
- package/types/declaredclose.d.ts +2 -0
- package/types/draftspend.d.ts +27 -1
- package/types/facts.d.ts +15 -0
- package/types/factwords.d.ts +26 -3
- package/types/inputlook.d.ts +15 -0
- package/types/kinds.d.ts +5 -0
- package/types/pagedropwords.d.ts +26 -0
- package/types/pagemap.d.ts +154 -0
- package/types/pagerules.d.ts +86 -0
- package/types/panel/authorroutes.d.ts +17 -14
- package/types/panel/runroutes.d.ts +2 -2
- package/types/panel/server.d.ts +155 -2
- package/types/panel/status.d.ts +51 -0
- package/types/panelmoney.d.ts +5 -0
- package/types/replay.d.ts +21 -1
- package/types/rubricstep.d.ts +57 -1
- package/types/runlist.d.ts +4 -1
- package/types/schedulewords.d.ts +4 -2
- package/types/source.d.ts +11 -0
- package/types/thinking.d.ts +39 -0
- package/types/userrun.d.ts +4 -0
package/src/rubricstep.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { readFile, lstat } from 'node:fs/promises';
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import { createRubric, checkStep, renderGaps, numbersInQuote } from 'bareguard';
|
|
5
|
+
import { dropRows, dropLine } from './pagedropwords.js';
|
|
5
6
|
import { factPartOf, factShapeReds, factDecides, factClaims, factRows, plainFactItem, factBrief } from './facts.js';
|
|
6
7
|
|
|
7
8
|
// PART 1a (docs/product/ITEM33-BUILD.md, "Addendum 2026-10-12"): the rubric job on LOCAL FILES — what one job line leaves
|
|
@@ -100,15 +101,35 @@ export function rubricGoal({ jobLines, files, confirmedGoal = null, closeDecl =
|
|
|
100
101
|
const inputs = files.map((f) => ` - ${f.label}: ${f.rel}`).join('\n');
|
|
101
102
|
const lines = jobLines.map((l) => `${l.n}. ${l.text}${l.rule ? `\n rule: ${l.rule}` : ''}`).join('\n');
|
|
102
103
|
return [
|
|
103
|
-
|
|
104
|
+
RUBRIC_GOAL_HEAD,
|
|
104
105
|
`The input files (label: file):\n${inputs}`,
|
|
105
106
|
stepFileContract(),
|
|
106
107
|
`The job, line by line:\n${lines}`,
|
|
107
108
|
...(closeDecl ? lineBriefs(closeDecl).map((b) => b.text) : []),
|
|
108
|
-
...(confirmedGoal ? [
|
|
109
|
+
...(confirmedGoal ? [`${CONFIRMED_GOAL_PREFIX}${confirmedGoal}`] : []),
|
|
109
110
|
].join('\n\n');
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
/**
|
|
114
|
+
* What a rubric job's goal text is: the worker brief `rubricGoal` builds. The Job tab's "AI's summary" is only the one
|
|
115
|
+
* sentence the person confirmed, never this brief, so the first line and the confirmed-sentence prefix have ONE spelling.
|
|
116
|
+
*/
|
|
117
|
+
export const RUBRIC_GOAL_HEAD = 'Do the job below from the input files, then write one JSON file per numbered line.';
|
|
118
|
+
const CONFIRMED_GOAL_PREFIX = 'The plan the person confirmed: ';
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The sentence the person confirmed, out of a goal text: for a rubric brief the rest of the `CONFIRMED_GOAL_PREFIX` line,
|
|
122
|
+
* or null when the brief has none; any other goal (a code job's) comes back unchanged.
|
|
123
|
+
* @param {string} goal
|
|
124
|
+
* @returns {string|null}
|
|
125
|
+
*/
|
|
126
|
+
export function confirmedGoalOf(goal) {
|
|
127
|
+
if (!goal.startsWith(RUBRIC_GOAL_HEAD)) return goal;
|
|
128
|
+
const line = goal.split('\n').find((l) => l.startsWith(CONFIRMED_GOAL_PREFIX));
|
|
129
|
+
const sentence = line ? line.slice(CONFIRMED_GOAL_PREFIX.length).trim() : '';
|
|
130
|
+
return sentence || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
// ── the `rubric-step` stage: its parameters, and the rubric they build ───────────────────────────────────────────────
|
|
113
134
|
//
|
|
114
135
|
// A stage serves exactly ONE job line N and reads exactly one file, output/steps/N.json. Its parameters are
|
|
@@ -258,6 +279,46 @@ export function buildStepSpec(params, inputShas) {
|
|
|
258
279
|
/** the sha256 of text, hex @param {string|Buffer} b @returns {string} */
|
|
259
280
|
export const sha256Of = (b) => createHash('sha256').update(b).digest('hex');
|
|
260
281
|
|
|
282
|
+
/**
|
|
283
|
+
* The files one named check READS, tree-relative: a `rubric-step` stage's own step file (`output/steps/N.json`). Any other kind reads
|
|
284
|
+
* none that a try's output can be told by, so `[]`. From the signed declaration, never guessed.
|
|
285
|
+
* @param {any} closeDecl @param {string|null|undefined} checkName
|
|
286
|
+
* @returns {string[]}
|
|
287
|
+
*/
|
|
288
|
+
export function judgedFilesOf(closeDecl, checkName) {
|
|
289
|
+
const stages = Array.isArray(closeDecl?.stages) ? closeDecl.stages : [];
|
|
290
|
+
const st = stages.find((/** @type {any} */ x) => x && x.name === checkName && x.kind === 'rubric-step');
|
|
291
|
+
return st && Number.isInteger(st.params?.step) ? [stepFileRel(st.params.step)] : [];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* PRD v1.106 Part 5: what each file a try's `check-passes` checks read looked like WHEN it was judged: `{path, bytes, sha256}`. The step
|
|
296
|
+
* file is overwritten by the next try, so the spine's `output-judged` record is the only trace; the content itself is never written (hard
|
|
297
|
+
* line). A missing, non-regular or over-size file is skipped — nothing was judged there.
|
|
298
|
+
* @param {any} closeDecl @param {string} workdir @param {Array<{type?: string, name?: string}>} exits a step's declared exits
|
|
299
|
+
* @returns {Promise<Array<{path: string, bytes: number, sha256: string}>>}
|
|
300
|
+
*/
|
|
301
|
+
export async function outputJudgedRecords(closeDecl, workdir, exits) {
|
|
302
|
+
/** @type {Array<{path: string, bytes: number, sha256: string}>} */
|
|
303
|
+
const out = [];
|
|
304
|
+
const seen = new Set();
|
|
305
|
+
for (const e of Array.isArray(exits) ? exits : []) {
|
|
306
|
+
if (!e || e.type !== 'check-passes') continue;
|
|
307
|
+
for (const rel of judgedFilesOf(closeDecl, e.name)) {
|
|
308
|
+
if (seen.has(rel)) continue;
|
|
309
|
+
seen.add(rel);
|
|
310
|
+
try {
|
|
311
|
+
const abs = join(workdir, rel);
|
|
312
|
+
const ls = await lstat(abs);
|
|
313
|
+
if (!ls.isFile() || ls.size > STEP_MAX_BYTES) continue;
|
|
314
|
+
const buf = await readFile(abs);
|
|
315
|
+
out.push({ path: rel, bytes: buf.length, sha256: sha256Of(buf) });
|
|
316
|
+
} catch { /* not there: nothing was judged */ }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
|
|
261
322
|
/**
|
|
262
323
|
* The step file's bytes as the close reads them: the parsed JSON object, or WHY there is none. A file cut off mid-write
|
|
263
324
|
* parses to nothing (reason `not-json`); text cut off INSIDE an otherwise valid JSON object cannot be told from a short
|
|
@@ -358,7 +419,9 @@ export function plainStepGap(step, why, g, params = null) {
|
|
|
358
419
|
* `inputs` maps each cited label to its frozen TEXT; its sha256 is taken here from that text, and bareguard compares it to
|
|
359
420
|
* the manifest's recorded sha via `inputShas`.
|
|
360
421
|
* @param {Record<string, string>} inputShas the manifest's recorded sha256 per cited label
|
|
361
|
-
* @returns {Promise<{verdict: 'green'|'red'|'stopped', gapLines: string[], fault: string|null, detail: Record<string, any
|
|
422
|
+
* @returns {Promise<{verdict: 'green'|'red'|'stopped', gapLines: string[], fault: string|null, detail: Record<string, any>,
|
|
423
|
+
* evidence?: any[], evidenceTotal?: number}>} `evidence`/`evidenceTotal` are bareguard's `full.evidence` (what each `cited` check read), carried out
|
|
424
|
+
* for the report-only `rubric-evidence` spine record; no verdict, gap or count above is derived from them
|
|
362
425
|
*/
|
|
363
426
|
export async function gradeStep({ params, bytes, outputBytes, inputs, factQuotes = {}, factMissing = [] }, inputShas) {
|
|
364
427
|
let rubric;
|
|
@@ -396,8 +459,12 @@ export async function gradeStep({ params, bytes, outputBytes, inputs, factQuotes
|
|
|
396
459
|
const redCount = extraCount + r.full.gaps.reduce((/** @type {number} */ n, /** @type {any} */ g) => n + (g.check === 'cited' && g.kind === 'unsupported' && typeof g.measured === 'number' ? g.measured : 1), 0);
|
|
397
460
|
const detail = { step: params.step, rubricSha: r.rubricSha, outputSha: r.outputSha, why: parsed.why, gaps: r.full.gaps.length, ...(r.verdict === 'red' || extra.length > 0 ? { redCount } : {}) };
|
|
398
461
|
if (r.verdict === 'stopped') return { verdict: 'stopped', gapLines: [], fault: r.fault ? `${r.fault.kind}: ${r.fault.detail}` : 'the rubric stopped', detail };
|
|
399
|
-
|
|
400
|
-
|
|
462
|
+
const seen = {
|
|
463
|
+
...(Array.isArray(r.full.evidence) && r.full.evidence.length > 0 ? { evidence: r.full.evidence } : {}),
|
|
464
|
+
...(typeof r.full.evidenceTotal === 'number' ? { evidenceTotal: r.full.evidenceTotal } : {}),
|
|
465
|
+
};
|
|
466
|
+
if (r.verdict === 'green' && extra.length === 0) return { verdict: 'green', gapLines: [], fault: null, detail, ...seen };
|
|
467
|
+
return { verdict: 'red', gapLines: [...extra, ...r.gaps.flatMap((g) => plainStepGap(params.step, parsed.why ?? 'empty', g, params).split('\n'))], fault: null, detail, ...seen };
|
|
401
468
|
}
|
|
402
469
|
|
|
403
470
|
// ── R11: the sorting rules, applied at DRAFTING ──────────────────────────────────────────────────────────────────────
|
|
@@ -552,6 +619,16 @@ const boundSpellings = (b) => {
|
|
|
552
619
|
return [s, s.replace(/^0+(?=\d)/, ''), s.replace(/\.0+$/, '')];
|
|
553
620
|
};
|
|
554
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Is this rule bound in the words the person wrote? The ONE test for "a number the AI made" (`rubric-fact-unmarked`, the L3 settle, and
|
|
624
|
+
* the early ask of PRD v1.107 Part 5b all read it; never a second spelling).
|
|
625
|
+
* @param {unknown} b @param {string} said @returns {boolean}
|
|
626
|
+
*/
|
|
627
|
+
export const boundInWords = (b, said) => boundSpellings(b).some((sp) => said.includes(sp));
|
|
628
|
+
|
|
629
|
+
/** @param {any} jl one job line @returns {string} the words the person wrote on it: the line and its `~` rules */
|
|
630
|
+
export const wordsSaidOn = (jl) => `${jl.text ?? ''} ${lineRules(jl).join(' ')}`;
|
|
631
|
+
|
|
555
632
|
/**
|
|
556
633
|
* PART 1b, RF16 + RF17 — the rules that need the person's own job lines. Only a close built for REQUIRED FACTS is held to them (a
|
|
557
634
|
* close with a fact part, or any part that says which `~` rules it carries out): an older Part 1a close is left as it was signed.
|
|
@@ -581,12 +658,12 @@ export function checkRubricRules(stages, { jobLines = null, refused = null, red
|
|
|
581
658
|
const fp = factPartOf(st.params);
|
|
582
659
|
if (!fp || !Array.isArray(fp.facts)) continue;
|
|
583
660
|
const jl = jobLines.find((l) => isRec(l) && l.n === st.params.step);
|
|
584
|
-
const said = jl ?
|
|
661
|
+
const said = jl ? wordsSaidOn(jl) : null;
|
|
585
662
|
fp.facts.forEach((/** @type {any} */ f, /** @type {number} */ j) => {
|
|
586
663
|
if (!isRec(f) || !isRec(f.rule)) return;
|
|
587
664
|
const w = `stages[${i}].params.parts[${st.params.parts.indexOf(fp)}].facts[${j}]`;
|
|
588
665
|
const bounds = f.rule.op === 'between' ? [f.rule.lo, f.rule.hi] : [f.rule.value];
|
|
589
|
-
if (said !== null && !f.aiProposal && bounds.some((b) => !
|
|
666
|
+
if (said !== null && !f.aiProposal && bounds.some((b) => !boundInWords(b, said))) {
|
|
590
667
|
red('rubric-fact-unmarked', w, `line ${st.params.step}: the limit on fact "${f.name}" (${bounds.join(' to ')}) is not in the words the person wrote, so it is the AI's own number. Set "aiProposal": true on that fact so the card says "AI's proposal — check it". Nothing spent.`, { line: st.params.step });
|
|
591
668
|
}
|
|
592
669
|
if (f.aiProposal && f.shape === 'time' && f.rule.op !== 'between') {
|
|
@@ -620,12 +697,12 @@ export function settlePersonsWords(declaration, jobLines) {
|
|
|
620
697
|
const jl = jobLines.find((l) => isRec(l) && l.n === st.params.step);
|
|
621
698
|
if (!fp || !Array.isArray(fp.facts) || !jl) return st;
|
|
622
699
|
const rules = lineRules(jl);
|
|
623
|
-
const said =
|
|
700
|
+
const said = wordsSaidOn(jl);
|
|
624
701
|
const ranges = rules.filter((r) => !NEGATING_RE.test(r)).map((r) => TIME_RANGE_RE.exec(r)).filter((m) => m !== null);
|
|
625
702
|
const proposed = fp.facts.filter((/** @type {any} */ f) => isRec(f) && f.aiProposal === true && f.shape === 'time' && isRec(f.rule) && f.rule.op === 'between');
|
|
626
703
|
const facts = fp.facts.map((/** @type {any} */ f) => {
|
|
627
704
|
if (!isRec(f) || !isRec(f.rule) || f.aiProposal !== true) return f;
|
|
628
|
-
const own = (/** @type {unknown[]} */ bounds) => bounds.every((b) =>
|
|
705
|
+
const own = (/** @type {unknown[]} */ bounds) => bounds.every((b) => boundInWords(b, said));
|
|
629
706
|
const bounds = f.rule.op === 'between' ? [f.rule.lo, f.rule.hi] : [f.rule.value];
|
|
630
707
|
const { aiProposal: _drop, ...rest } = f;
|
|
631
708
|
if (own(bounds)) return rest;
|
|
@@ -667,6 +744,36 @@ export function withFactBindings(declaration) {
|
|
|
667
744
|
return { ...declaration, stages };
|
|
668
745
|
}
|
|
669
746
|
|
|
747
|
+
/**
|
|
748
|
+
* NO SILENT DROP (idea A core, spec 4.4): code writes the Not checked rows for what the page rules removed, before the declaration is validated.
|
|
749
|
+
* - a dropped FACT that carries a `~` rule no part carries and no `refused` entry names -> `closeDecl.refused` gets {line, rule, reason}
|
|
750
|
+
* (shown under that line in the person's own rule words by {@link ruleNotChecked});
|
|
751
|
+
* - any other drop (a fact from the job line itself, a rule another fact carries, a word dropped from a fact that is still checked, so a
|
|
752
|
+
* narrower check is still said) -> one line in `closeDecl.notes`, shown as Not checked (the AI's own reading).
|
|
753
|
+
* Never a line-level `refused` entry without `rule` (that would silence every other rule red on the line). A rule the drafter already listed
|
|
754
|
+
* itself is never listed twice, and running this twice adds nothing. Pure; the input is untouched.
|
|
755
|
+
* @param {any} declaration @param {import('./pagerules.js').Dropped[]|null|undefined} dropped @param {any[]|null|undefined} jobLines @returns {any}
|
|
756
|
+
*/
|
|
757
|
+
export function withDropNotes(declaration, dropped, jobLines) {
|
|
758
|
+
if (!isRec(declaration) || !Array.isArray(dropped) || dropped.length === 0) return declaration;
|
|
759
|
+
const stages = Array.isArray(declaration.stages) ? declaration.stages : [];
|
|
760
|
+
const carried = (/** @type {number} */ line, /** @type {number} */ n) => stages.some((/** @type {any} */ st) => isRec(st) && st.kind === 'rubric-step' && isRec(st.params) && st.params.step === line
|
|
761
|
+
&& Array.isArray(st.params.parts) && st.params.parts.some((/** @type {any} */ x) => isRec(x) && Array.isArray(x.rules) && x.rules.includes(n)));
|
|
762
|
+
const refused = Array.isArray(declaration.refused) ? [...declaration.refused] : [];
|
|
763
|
+
const notes = Array.isArray(declaration.notes) ? [...declaration.notes] : [];
|
|
764
|
+
for (const row of dropRows(dropped)) {
|
|
765
|
+
const named = row.rule !== undefined && refused.some((/** @type {any} */ r) => isRec(r) && r.line === row.line && r.rule === row.rule);
|
|
766
|
+
if (named) continue;
|
|
767
|
+
if (row.full && row.rule !== undefined && !carried(row.line, row.rule)) {
|
|
768
|
+
refused.push({ line: row.line, rule: row.rule, reason: row.sentences.join(' ') });
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
const text = dropLine(row, jobLines, lineRules);
|
|
772
|
+
if (!notes.includes(text)) notes.push(text);
|
|
773
|
+
}
|
|
774
|
+
return { ...declaration, ...(refused.length ? { refused } : {}), ...(notes.length ? { notes } : {}) };
|
|
775
|
+
}
|
|
776
|
+
|
|
670
777
|
/**
|
|
671
778
|
* The ~ rules the drafter listed under Not checked (`refused` entries that name a `rule`), each as one line in the person's own
|
|
672
779
|
* words: `line N: "<the rule as written>" — <the drafter's reason>` (PART 1b, RF6 / RF16).
|
|
@@ -686,9 +793,14 @@ export function ruleNotChecked(closeDecl, jobLines) {
|
|
|
686
793
|
* @param {any} closeDecl @returns {string[]}
|
|
687
794
|
*/
|
|
688
795
|
export function proseNotChecked(closeDecl) {
|
|
796
|
+
return proseNotCheckedItems(closeDecl).map((i) => i.text);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** the same items as {@link proseNotChecked} with the job line each belongs to (the stage's own `step`) @param {any} closeDecl @returns {{line: number, text: string}[]} */
|
|
800
|
+
export function proseNotCheckedItems(closeDecl) {
|
|
689
801
|
const stages = isRec(closeDecl) && Array.isArray(closeDecl.stages) ? closeDecl.stages : [];
|
|
690
802
|
return stages.filter((/** @type {any} */ st) => isRec(st) && st.kind === 'rubric-step' && isRec(st.params) && Array.isArray(st.params.parts) && !factPartOf(st.params))
|
|
691
|
-
.map((/** @type {any} */ st) => `line ${st.params.step}: what the text says — you judge it at Accept`);
|
|
803
|
+
.map((/** @type {any} */ st) => ({ line: st.params.step, text: `line ${st.params.step}: what the text says — you judge it at Accept` }));
|
|
692
804
|
}
|
|
693
805
|
|
|
694
806
|
// ── the sign-vs-run invariant: the checks the person is SHOWN are the checks the signed close RUNS ───────────────────
|
package/src/runlist.js
CHANGED
|
@@ -104,7 +104,8 @@ export function appendRun(row, opts = {}) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* @typedef {{ runid: string, type: 'settled'|'released', by: string, at: string, reason?: string, spentUsd?: number, spendComplete?: boolean, leg?: number }} RunEvent
|
|
107
|
+
* @typedef {{ runid: string, type: 'settled'|'released', by: string, at: string, reason?: string, words?: string, spentUsd?: number, spendComplete?: boolean, leg?: number }} RunEvent
|
|
108
|
+
* `words` (2026-10-19): on a `settled` with `reason: 'refused before start'`, the plain refusal the panel shows for that run (it has no spine).
|
|
108
109
|
* `leg` (P5-R): which leg of the run the write-back is about; absent = leg 1 (every entry written before P5-R).
|
|
109
110
|
* A resumed run is ONE row, so `settled`/`released` are per leg: a later leg's `released` gives back THAT leg's
|
|
110
111
|
* claim and never removes the run.
|
package/src/schedulewords.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// its ENDED block is `endedFor` in src/panel/server.js).
|
|
7
7
|
import { basename } from 'node:path';
|
|
8
8
|
import { panelMoney2 } from './panel/authorroutes.js';
|
|
9
|
+
import { STATUS } from './panel/status.js';
|
|
9
10
|
|
|
10
11
|
/** @typedef {import('./trigger.js').Trigger} Trigger */
|
|
11
12
|
|
|
@@ -272,11 +273,13 @@ export function retryNextWords(ms) {
|
|
|
272
273
|
|
|
273
274
|
/**
|
|
274
275
|
* RULED 2026-10-15: the sign on the head of the Audit's `schedule` section. ONE small rule (hamr may still adjust it): [▶] only while a
|
|
275
|
-
* run of the job is running (as everywhere in the panel), [✗] when the SCHEDULE row is red, else [·] (on, waiting).
|
|
276
|
-
*
|
|
276
|
+
* run of the job is running (as everywhere in the panel), [✗] when the SCHEDULE row is red, else [·] (on, waiting). RULED 2026-10-18 (v1.106
|
|
277
|
+
* Part 4): the header reads `[sign] word schedule · ...`, the sign AND the word both from the one status table (src/panel/status.js), never typed here.
|
|
278
|
+
* @param {{running: boolean, red: boolean}} f @returns {{sign: string, word: string}}
|
|
277
279
|
*/
|
|
278
280
|
export function scheduleSignFor(f) {
|
|
279
|
-
|
|
281
|
+
const { sign, word } = f.running ? STATUS.running : f.red ? STATUS.failed : STATUS.waiting;
|
|
282
|
+
return { sign, word };
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
/** Audit schedule section, the `this run` words of a run the schedule started (the head already says which schedule and from where). @param {any} rec `trigger.json` */
|
package/src/source.js
CHANGED
|
@@ -1388,3 +1388,21 @@ export async function refreezeUrlInputs({ into, fetchUrl = (u) => fetchPageText(
|
|
|
1388
1388
|
await writeFile(join(resolve(into), 'source.json'), `${JSON.stringify(next, null, 2)}\n`);
|
|
1389
1389
|
return { ok: true, refreshed: rows.map((/** @type {any} */ r) => r.label), seed };
|
|
1390
1390
|
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* PRD v1.106 Part 5 Q2: what `source.json` says about web inputs, read synchronously and never throwing: whether the copy has any web input
|
|
1394
|
+
* (`inputs[].url`) and when the run-start refetch last finished (`refrozenAt`, written last by {@link refreezeUrlInputs}). The panel decides
|
|
1395
|
+
* "fetching the page..." from this: it applies while a web input exists and no refetch has finished at or after `sinceIso`.
|
|
1396
|
+
* @param {string} sourceJsonPath @param {string|null|undefined} sinceIso
|
|
1397
|
+
* @returns {{ hasWeb: boolean, fetching: boolean }}
|
|
1398
|
+
*/
|
|
1399
|
+
export function webFetchState(sourceJsonPath, sinceIso) {
|
|
1400
|
+
try {
|
|
1401
|
+
const m = JSON.parse(readFileSync(sourceJsonPath, 'utf8'));
|
|
1402
|
+
const hasWeb = Array.isArray(m?.inputs) && m.inputs.some((/** @type {any} */ r) => r && typeof r.url === 'string' && r.url !== '');
|
|
1403
|
+
if (!hasWeb) return { hasWeb: false, fetching: false };
|
|
1404
|
+
const done = typeof m.refrozenAt === 'string' ? Date.parse(m.refrozenAt) : NaN;
|
|
1405
|
+
const since = typeof sinceIso === 'string' ? Date.parse(sinceIso) : NaN;
|
|
1406
|
+
return { hasWeb: true, fetching: !(Number.isFinite(done) && (!Number.isFinite(since) || done >= since)) };
|
|
1407
|
+
} catch { return { hasWeb: false, fetching: false }; }
|
|
1408
|
+
}
|
package/src/thinking.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// NOT switchable), `'unverified'` (not measured — the shape ladder is tried at runtime; if every shape fails with a client
|
|
6
6
|
// validation error ONE plain request decides, and the call still runs, reported `thinkingStatus: 'unsupported'`).
|
|
7
7
|
//
|
|
8
|
-
// The tick is the person's, per job ("Thinking on", the signed optional `thinking` field; absent = off). It covers DRAFTING
|
|
9
|
-
// rubric JUDGE
|
|
8
|
+
// The tick is the person's, per job ("Thinking on", the signed optional `thinking` field; absent = off). It covers DRAFTING, the
|
|
9
|
+
// rubric JUDGE and, as the think-everywhere override, every WORKER call. Without the tick the WORKER's mode is decided here too, in
|
|
10
|
+
// code, from the phase and attempt (`workerThinkingMode`, PRD v1.106 Part 1) — never by the model. PER-CALL options, never constructor options (the drafter's provider is
|
|
10
11
|
// the worker's instance, deliberately left as it was). The raw `thinking` option is never sent (bare-agent throws if both are set).
|
|
11
12
|
import { createRequire } from 'node:module';
|
|
12
13
|
|
|
@@ -128,3 +129,63 @@ export function thinkingRowText({ mode, support, status }) {
|
|
|
128
129
|
if (support === 'unverified') return `thinking ${mode} (not verified for this model)`;
|
|
129
130
|
return `thinking ${mode}`;
|
|
130
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* THE one decision for a worker model call (PRD v1.106 Part 1): from the phase label and attempt iteration `mkWorker` already holds,
|
|
135
|
+
* and the job's tick (`job.thinking === true`). Decided in code, never by the model.
|
|
136
|
+
* tick on -> 'on' for every call
|
|
137
|
+
* plan (draft, redraft, replan) -> 'on' (the planner is the hard part of a run)
|
|
138
|
+
* scout -> 'off'
|
|
139
|
+
* step:<id> attempt 1 -> 'off'
|
|
140
|
+
* step:<id> attempt >= 2 -> 'on'
|
|
141
|
+
* fix (close-fix) -> 'on'
|
|
142
|
+
* A step or fix iteration that is not a whole number >= 1 throws (operator error), as does an unknown phase — never a guess.
|
|
143
|
+
* @param {{ phase: unknown, iteration?: unknown, tick?: unknown }} o
|
|
144
|
+
* @returns {'on'|'off'}
|
|
145
|
+
*/
|
|
146
|
+
export function workerThinkingMode({ phase, iteration, tick }) {
|
|
147
|
+
if (typeof phase !== 'string') throw new Error(`[thinking] worker phase must be a string, got ${JSON.stringify(phase)}`);
|
|
148
|
+
const isStep = phase.startsWith('step:');
|
|
149
|
+
if (isStep || phase === 'fix') {
|
|
150
|
+
if (!Number.isInteger(iteration) || /** @type {number} */ (iteration) < 1) {
|
|
151
|
+
throw new Error(`[thinking] ${isStep ? 'step' : 'fix'} iteration must be a whole number >= 1, got ${JSON.stringify(iteration)} (phase "${phase}")`);
|
|
152
|
+
}
|
|
153
|
+
} else if (phase !== 'scout' && phase !== 'plan') {
|
|
154
|
+
throw new Error(`[thinking] unknown worker phase ${JSON.stringify(phase)}`);
|
|
155
|
+
}
|
|
156
|
+
if (tick === true) return 'on';
|
|
157
|
+
if (phase === 'scout') return 'off';
|
|
158
|
+
if (isStep) return iteration === 1 ? 'off' : 'on';
|
|
159
|
+
return 'on';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* PRD v1.106 Part 3 — the words one worker call carries in the Audit, READ from the run's own spine, never guessed. Finds the LAST
|
|
164
|
+
* `thinking-decision` of `phase` (and `iteration`, when given) whose seq lies strictly between `lo` and `hi`, pairs it with the first
|
|
165
|
+
* `thinking-status` of the same phase and iteration after it (before the next decision), and words both through {@link thinkingRowText}
|
|
166
|
+
* (drafting's own words). `null` when the window holds no decision: a run from before the records existed shows no thinking word.
|
|
167
|
+
* @param {any[]} spine the run's spine records (each with a numeric `seq`)
|
|
168
|
+
* @param {{ phase: string, lo?: number, hi?: number, iteration?: number|null }} q
|
|
169
|
+
* @returns {string|null}
|
|
170
|
+
*/
|
|
171
|
+
export function thinkingWordsFrom(spine, { phase, lo = -Infinity, hi = Infinity, iteration }) {
|
|
172
|
+
const seqOf = (/** @type {any} */ e) => (typeof e?.seq === 'number' ? e.seq : NaN);
|
|
173
|
+
const sameIteration = (/** @type {any} */ e) => iteration === undefined || e.iteration === iteration;
|
|
174
|
+
const decisions = spine.filter((e) => e?.type === 'thinking-decision' && e.phase === phase && sameIteration(e) && seqOf(e) > lo && seqOf(e) < hi && (e.mode === 'on' || e.mode === 'off'));
|
|
175
|
+
const dec = decisions[decisions.length - 1];
|
|
176
|
+
if (!dec) return null;
|
|
177
|
+
const nextDecision = spine.find((e) => e?.type === 'thinking-decision' && seqOf(e) > seqOf(dec));
|
|
178
|
+
const stop = nextDecision ? seqOf(nextDecision) : Infinity;
|
|
179
|
+
const st = spine.find((e) => e?.type === 'thinking-status' && e.phase === dec.phase && e.iteration === dec.iteration && seqOf(e) > seqOf(dec) && seqOf(e) < stop);
|
|
180
|
+
return thinkingRowText({ mode: dec.mode, status: typeof st?.thinkingStatus === 'string' ? st.thinkingStatus : undefined });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The Job tab's row words (PRD v1.106 Part 3): the default rule, or the job's "Thinking on" tick, but only for a run whose spine
|
|
185
|
+
* recorded a `thinking-decision`; a run from before the thinking rule reads `—`, never a guess from the job's tick (fwdloop M4d am28).
|
|
186
|
+
* @param {boolean} tick @param {boolean} recorded the run's spine holds at least one `thinking-decision` @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
export function thinkingJobText(tick, recorded) {
|
|
189
|
+
if (!recorded) return '\u2014';
|
|
190
|
+
return tick ? 'always on' : 'on retries';
|
|
191
|
+
}
|
package/src/userrun.js
CHANGED
|
@@ -304,6 +304,7 @@ class ExitSignal extends Error {
|
|
|
304
304
|
* @property {string} seed
|
|
305
305
|
* @property {string} spineName
|
|
306
306
|
* @property {string} [into] see `RunOpts.into`
|
|
307
|
+
* @property {boolean} [refreeze] a FRESH `--spec` run: fetch the web inputs again AFTER the run is listed (never on resume/door/decide)
|
|
307
308
|
* @property {string|null} model
|
|
308
309
|
* @property {string|null} providerKey
|
|
309
310
|
* @property {string|null} readShim
|
|
@@ -493,7 +494,8 @@ async function execute(ctx) {
|
|
|
493
494
|
const REGISTRY_TAIL = (ctx.registry ?? null) !== null ? ` --registry ${(ctx.registry ?? null)} --workflow ${(ctx.workflow ?? null) ?? spec.job}` : '';
|
|
494
495
|
|
|
495
496
|
const WORKDIR = target.workdir;
|
|
496
|
-
|
|
497
|
+
// `let`: a fresh run's refetch of its web inputs commits a NEW seed (below, after the run is listed)
|
|
498
|
+
let SEED = target.seed;
|
|
497
499
|
const specHash = jobSpecHash(spec);
|
|
498
500
|
// the re-invocation commands this engine prints: a bundle names its own door and hash,
|
|
499
501
|
// every other caller gets today's byte-identical strings.
|
|
@@ -1584,6 +1586,24 @@ async function execute(ctx) {
|
|
|
1584
1586
|
err(`WARNING: could not add this run to ~/.config/bareloop/runs.jsonl (${e.message}) — the run continues; the panel's list will be missing this row.`);
|
|
1585
1587
|
}
|
|
1586
1588
|
|
|
1589
|
+
// RUN START REFETCH (PART 1c RF22; moved here 2026-10-19, run mvez8s54: the ~18 s barebrowse fetch ran in main() BEFORE this run was
|
|
1590
|
+
// listed, so Sign & run showed nothing for 18 s). The run is listed FIRST (row above, with its pid and cap = the monthly claim), THEN
|
|
1591
|
+
// fetches: the Run tab reads "fetching the page…" while source.json has no refrozenAt at or after the row. A refusal cannot be a
|
|
1592
|
+
// `released` row (that unlists the run and its words); it SETTLES the claim at $0 and keeps the plain reason for the panel.
|
|
1593
|
+
if (ctx.refreeze === true && ctx.into !== undefined) {
|
|
1594
|
+
const fresh = await refreezeUrlInputs({ into: ctx.into, ...(deps.fetchUrl ? { fetchUrl: deps.fetchUrl } : {}) });
|
|
1595
|
+
if (!fresh.ok) {
|
|
1596
|
+
const words = /did not start/.test(fresh.error) ? fresh.error : `${fresh.error}. The run did not start. Nothing spent.`;
|
|
1597
|
+
err(`${SELECTOR}: ${fresh.error}`);
|
|
1598
|
+
if (claimedRow) {
|
|
1599
|
+
try { appendRunEvent({ runid, type: 'settled', by: runid, reason: 'refused before start', words, spentUsd: 0, spendComplete: true, at: new Date().toISOString() }, { home: claimedRow.home }); } catch { /* the exit stands; the dead pid's claim is closed by the next run */ }
|
|
1600
|
+
claimedRow = null; // settled, not released: a released leg-1 row is unlisted, and the person must see why
|
|
1601
|
+
}
|
|
1602
|
+
throw new ExitSignal(2);
|
|
1603
|
+
}
|
|
1604
|
+
if (fresh.refreshed.length > 0 && typeof fresh.seed === 'string' && fresh.seed) SEED = fresh.seed;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1587
1607
|
const git = (/** @type {string[]} */ a) => execFileSync('git', ['-C', wd, ...a], { encoding: 'utf8' }).trim();
|
|
1588
1608
|
if (dead) {
|
|
1589
1609
|
// A RESUME is the OPPOSITE case from a cold launch: the halted run's edits are work
|
|
@@ -2054,7 +2074,7 @@ async function execute(ctx) {
|
|
|
2054
2074
|
const legDraftSpentUsd = typeof jobStartRec?.draftSpentUsd === 'number' && jobStartRec.draftSpentUsd > 0 ? jobStartRec.draftSpentUsd : null;
|
|
2055
2075
|
const legDraftSpendComplete = legDraftSpentUsd === null ? null : jobStartRec.draftSpendComplete !== false;
|
|
2056
2076
|
const spendCore = je?.spentUsd == null ? 'UNKNOWN' : `${je.spendComplete === false ? '≥' : ''}$${je.spentUsd.toFixed(4)}`;
|
|
2057
|
-
out(`spent ${moneyWithDraft(spendCore, legDraftSpentUsd, spec.budgetUsd, legDraftSpendComplete)}`);
|
|
2077
|
+
out(`spent ${moneyWithDraft(spendCore, legDraftSpentUsd, spec.budgetUsd, legDraftSpendComplete, je?.spentUsd ?? null)}`);
|
|
2058
2078
|
// FOLDED, exactly like the money line above it: on a resume the cap governs both legs
|
|
2059
2079
|
// together, and a leg-only wall next to a folded spend is two framings on one cap with
|
|
2060
2080
|
// no label to tell them apart (F83). The leg stays on the line beside it.
|
|
@@ -2516,6 +2536,7 @@ export async function main(argv, deps = {}) {
|
|
|
2516
2536
|
/** how a "go edit the spec" hint renders — `jobs/<file>` for `--job`, the
|
|
2517
2537
|
* spec's own path for `--spec` (it has no `jobs/` home to point at). */
|
|
2518
2538
|
let SPEC_DESC;
|
|
2539
|
+
let refreeze = false;
|
|
2519
2540
|
let spec;
|
|
2520
2541
|
|
|
2521
2542
|
if (jobArg !== null) {
|
|
@@ -2592,24 +2613,16 @@ export async function main(argv, deps = {}) {
|
|
|
2592
2613
|
+ 'with exactly one source-*/ sibling.');
|
|
2593
2614
|
}
|
|
2594
2615
|
const into = candidates[0];
|
|
2595
|
-
|
|
2616
|
+
const manifestRead = await readSourceManifest(into);
|
|
2596
2617
|
if (manifestRead.stop !== null) {
|
|
2597
2618
|
die(`--spec ${specPath}: ${manifestRead.code} — ${manifestRead.stop}`);
|
|
2598
2619
|
}
|
|
2599
2620
|
if (!manifestRead.present || !manifestRead.manifest) {
|
|
2600
2621
|
die(`--spec ${specPath}: ${into} carries no source.json — not a prepared copy this runner can trust`);
|
|
2601
2622
|
}
|
|
2602
|
-
// PART 1c (RF22): a FRESH run
|
|
2603
|
-
//
|
|
2604
|
-
|
|
2605
|
-
if (argFlag('resume') === null && argFlag('door') === null && argFlag('decide') === null) {
|
|
2606
|
-
const fresh = await refreezeUrlInputs({ into, ...(deps.fetchUrl ? { fetchUrl: deps.fetchUrl } : {}) });
|
|
2607
|
-
if (!fresh.ok) die(`--spec ${specPath}: ${fresh.error}`);
|
|
2608
|
-
if (fresh.refreshed.length > 0) {
|
|
2609
|
-
manifestRead = await readSourceManifest(into);
|
|
2610
|
-
if (manifestRead.stop !== null || !manifestRead.manifest) die(`--spec ${specPath}: the run copy's record could not be read after the fresh fetch`);
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2623
|
+
// PART 1c (RF22): a FRESH run with web inputs fetches each address again before the worker starts. That happens in execute(),
|
|
2624
|
+
// AFTER the run is listed (`ctx.refreeze`); a resume / door / decision keeps the copy its first start froze.
|
|
2625
|
+
refreeze = argFlag('resume') === null && argFlag('door') === null && argFlag('decide') === null;
|
|
2613
2626
|
const seed = manifestRead.manifest.seed;
|
|
2614
2627
|
if (typeof seed !== 'string' || !seed) {
|
|
2615
2628
|
die(`--spec ${specPath}: ${into}/source.json carries no seed commit — not a prepared copy this runner can trust`);
|
|
@@ -2647,6 +2660,7 @@ export async function main(argv, deps = {}) {
|
|
|
2647
2660
|
seed: target.seed,
|
|
2648
2661
|
spineName: target.spine,
|
|
2649
2662
|
...(target.into === undefined ? {} : { into: target.into }),
|
|
2663
|
+
...(refreeze ? { refreeze: true } : {}),
|
|
2650
2664
|
model: argFlag('model'),
|
|
2651
2665
|
providerKey: argFlag('provider'),
|
|
2652
2666
|
readShim: argFlag('read-shim'),
|
package/types/authorflow.d.ts
CHANGED
|
@@ -10,12 +10,16 @@ export function linesNamedByReds(reds: Red[], declaration: any, lineNums: number
|
|
|
10
10
|
* The machine's keep-or-drop of the try-3 questions. Kept only when its line is one the previous try's reds named and its
|
|
11
11
|
* text is non-blank, at most {@link MAX_QUESTIONS}; every drop comes back with its reason, for the draft's log.
|
|
12
12
|
* @param {any} raw @param {number[]} failedLines
|
|
13
|
-
* @returns {{kept: {line: number, question: string}[], dropped: {reason: string, raw: any}[]}}
|
|
13
|
+
* @returns {{kept: {line: number, question: string, rule?: number, source?: string, ask?: 'reading'|'cant-check', reading?: string}[], dropped: {reason: string, raw: any}[]}}
|
|
14
14
|
*/
|
|
15
15
|
export function normalizeQuestions(raw: any, failedLines: number[]): {
|
|
16
16
|
kept: {
|
|
17
17
|
line: number;
|
|
18
18
|
question: string;
|
|
19
|
+
rule?: number;
|
|
20
|
+
source?: string;
|
|
21
|
+
ask?: "reading" | "cant-check";
|
|
22
|
+
reading?: string;
|
|
19
23
|
}[];
|
|
20
24
|
dropped: {
|
|
21
25
|
reason: string;
|
|
@@ -1093,8 +1097,8 @@ export const REVISE_RED_CAP: 20;
|
|
|
1093
1097
|
export const AUTHOR_MAX_TOKENS: 32000;
|
|
1094
1098
|
/** the one slot the model fills on an injected guard */
|
|
1095
1099
|
export const FILL_MARKER: "<FILL IN>";
|
|
1096
|
-
/** P7 item 5 (fwdloop am24/25): the most questions the MACHINE keeps from the try-3 drafter. The drafter is never told this number. */
|
|
1097
|
-
export const MAX_QUESTIONS:
|
|
1100
|
+
/** P7 item 5 (fwdloop am24/25): the most questions the MACHINE keeps from the try-3 drafter. The drafter is never told this number. PRD v1.107 Part 5b: three (hamr's ruling), shared with the code-forced early asks. */
|
|
1101
|
+
export const MAX_QUESTIONS: 3;
|
|
1098
1102
|
/**
|
|
1099
1103
|
* The model-visible words of the optional `questions` property — offered ONLY on the last drafting try and only after the
|
|
1100
1104
|
* two before it both failed validation (a mechanism, never a wish: the schema carries it on that one call and nowhere
|
|
@@ -1680,12 +1684,44 @@ export const PARAM_SCHEMAS: Readonly<{
|
|
|
1680
1684
|
description: string;
|
|
1681
1685
|
};
|
|
1682
1686
|
unit: {
|
|
1683
|
-
|
|
1684
|
-
|
|
1687
|
+
anyOf: ({
|
|
1688
|
+
type: string;
|
|
1689
|
+
minLength: number;
|
|
1690
|
+
minItems?: undefined;
|
|
1691
|
+
maxItems?: undefined;
|
|
1692
|
+
uniqueItems?: undefined;
|
|
1693
|
+
items?: undefined;
|
|
1694
|
+
} | {
|
|
1695
|
+
type: string;
|
|
1696
|
+
minItems: number;
|
|
1697
|
+
maxItems: number;
|
|
1698
|
+
uniqueItems: boolean;
|
|
1699
|
+
items: {
|
|
1700
|
+
type: string;
|
|
1701
|
+
minLength: number;
|
|
1702
|
+
};
|
|
1703
|
+
minLength?: undefined;
|
|
1704
|
+
})[];
|
|
1685
1705
|
description: string;
|
|
1686
1706
|
};
|
|
1687
1707
|
currency: {
|
|
1688
|
-
|
|
1708
|
+
anyOf: ({
|
|
1709
|
+
enum: string[];
|
|
1710
|
+
type?: undefined;
|
|
1711
|
+
minItems?: undefined;
|
|
1712
|
+
maxItems?: undefined;
|
|
1713
|
+
uniqueItems?: undefined;
|
|
1714
|
+
items?: undefined;
|
|
1715
|
+
} | {
|
|
1716
|
+
type: string;
|
|
1717
|
+
minItems: number;
|
|
1718
|
+
maxItems: number;
|
|
1719
|
+
uniqueItems: boolean;
|
|
1720
|
+
items: {
|
|
1721
|
+
enum: string[];
|
|
1722
|
+
};
|
|
1723
|
+
enum?: undefined;
|
|
1724
|
+
})[];
|
|
1689
1725
|
description: string;
|
|
1690
1726
|
};
|
|
1691
1727
|
phrase: {
|
package/types/authorjob.d.ts
CHANGED
|
@@ -91,8 +91,8 @@ export function refuseLockedKind(kind: string): Refusal;
|
|
|
91
91
|
* authorFn?: Function, authorOpts?: object, writeScope?: string[]|null,
|
|
92
92
|
* signerFix?: Function|any, proposeFn?: Function, compileOpts?: object,
|
|
93
93
|
* ask?: ((step: {kind: string, [k: string]: any}) => Promise<string|null>)|null,
|
|
94
|
-
* answerQuestions?: ((o: {questions: {line: number, question: string}[], dropped: {reason: string, raw: any}[], confirmed: any}) =>
|
|
95
|
-
* Promise<{jobLines: {n: number, text: string, rule?: string}[], answers: Record<string|number, any
|
|
94
|
+
* answerQuestions?: ((o: {questions: {line: number, question: string, rule?: number, source?: string, ask?: string, reading?: string}[], dropped: {reason: string, raw: any}[], confirmed: any}) =>
|
|
95
|
+
* Promise<{jobLines: {n: number, text: string, rule?: string}[], answers: Record<string|number, any>, left?: {line: number, rule: number}[]}|null>)|null,
|
|
96
96
|
* confirmGenerate?: Function|null, isRepo?: boolean,
|
|
97
97
|
* langResult?: {kind: string, candidates?: string[], [k: string]: any}|null}} o
|
|
98
98
|
* @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
|
|
@@ -151,6 +151,10 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
|
|
|
151
151
|
questions: {
|
|
152
152
|
line: number;
|
|
153
153
|
question: string;
|
|
154
|
+
rule?: number;
|
|
155
|
+
source?: string;
|
|
156
|
+
ask?: string;
|
|
157
|
+
reading?: string;
|
|
154
158
|
}[];
|
|
155
159
|
dropped: {
|
|
156
160
|
reason: string;
|
|
@@ -164,6 +168,10 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
|
|
|
164
168
|
rule?: string;
|
|
165
169
|
}[];
|
|
166
170
|
answers: Record<string | number, any>;
|
|
171
|
+
left?: {
|
|
172
|
+
line: number;
|
|
173
|
+
rule: number;
|
|
174
|
+
}[];
|
|
167
175
|
} | null>) | null;
|
|
168
176
|
confirmGenerate?: Function | null;
|
|
169
177
|
isRepo?: boolean;
|
package/types/declaredclose.d.ts
CHANGED
|
@@ -118,6 +118,7 @@ export function guardNames(closeDecl: any, verdictType: string): string[];
|
|
|
118
118
|
* judgeLoop?: ((o: {system: string}) => any)|null,
|
|
119
119
|
* judgeModel?: string|null,
|
|
120
120
|
* onJudgeCost?: ((c: any) => void)|null,
|
|
121
|
+
* onRubricEvidence?: ((e: any) => void)|null,
|
|
121
122
|
* callBounds?: (() => {timeoutMs?: number, deadlineMs?: number})|null}} [opts]
|
|
122
123
|
* @returns {Promise<any>}
|
|
123
124
|
*/
|
|
@@ -138,6 +139,7 @@ export function runDeclaredStages(stages: any[], redact?: (s: string) => string,
|
|
|
138
139
|
}) => any) | null;
|
|
139
140
|
judgeModel?: string | null;
|
|
140
141
|
onJudgeCost?: ((c: any) => void) | null;
|
|
142
|
+
onRubricEvidence?: ((e: any) => void) | null;
|
|
141
143
|
callBounds?: (() => {
|
|
142
144
|
timeoutMs?: number;
|
|
143
145
|
deadlineMs?: number;
|