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.
Files changed (57) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/bareloop.context.md +52 -13
  3. package/package.json +2 -2
  4. package/src/authorflow.js +26 -11
  5. package/src/authoring.js +1 -1
  6. package/src/authorjob.js +27 -15
  7. package/src/authorreadout.js +7 -6
  8. package/src/declaredclose.js +4 -1
  9. package/src/draftspend.js +20 -2
  10. package/src/facts.js +54 -9
  11. package/src/factwords.js +58 -6
  12. package/src/inputlook.js +38 -3
  13. package/src/job.js +1 -1
  14. package/src/kinds.js +5 -0
  15. package/src/pagedropwords.js +64 -0
  16. package/src/pagemap.js +264 -0
  17. package/src/pagerules.js +319 -0
  18. package/src/panel/authorroutes.js +34 -30
  19. package/src/panel/authorsession.js +29 -5
  20. package/src/panel/index.html +465 -65
  21. package/src/panel/jobplan.js +4 -2
  22. package/src/panel/runroutes.js +46 -22
  23. package/src/panel/server.js +791 -28
  24. package/src/panel/status.js +33 -0
  25. package/src/panelmoney.js +29 -0
  26. package/src/planrun.js +53 -4
  27. package/src/promptregisters.js +4 -0
  28. package/src/replay.js +118 -7
  29. package/src/rubricstep.js +123 -11
  30. package/src/runlist.js +2 -1
  31. package/src/schedulewords.js +6 -3
  32. package/src/source.js +18 -0
  33. package/src/thinking.js +63 -2
  34. package/src/userrun.js +28 -14
  35. package/types/authorflow.d.ts +42 -6
  36. package/types/authorjob.d.ts +10 -2
  37. package/types/declaredclose.d.ts +2 -0
  38. package/types/draftspend.d.ts +27 -1
  39. package/types/facts.d.ts +15 -0
  40. package/types/factwords.d.ts +26 -3
  41. package/types/inputlook.d.ts +15 -0
  42. package/types/kinds.d.ts +5 -0
  43. package/types/pagedropwords.d.ts +26 -0
  44. package/types/pagemap.d.ts +154 -0
  45. package/types/pagerules.d.ts +86 -0
  46. package/types/panel/authorroutes.d.ts +17 -14
  47. package/types/panel/runroutes.d.ts +2 -2
  48. package/types/panel/server.d.ts +155 -2
  49. package/types/panel/status.d.ts +51 -0
  50. package/types/panelmoney.d.ts +5 -0
  51. package/types/replay.d.ts +21 -1
  52. package/types/rubricstep.d.ts +57 -1
  53. package/types/runlist.d.ts +4 -1
  54. package/types/schedulewords.d.ts +4 -2
  55. package/types/source.d.ts +11 -0
  56. package/types/thinking.d.ts +39 -0
  57. package/types/userrun.d.ts +4 -0
@@ -303,6 +303,69 @@ export function getAcceptContext(runid: string, opts?: {
303
303
  specPath: string | null;
304
304
  specHash: string | null;
305
305
  } | null;
306
+ /**
307
+ * The files a run delivered: its own `destination-written` spine records (userrun.js, on a minted green only), one per path, the last record of a
308
+ * path winning. This is the ONLY source of a delivered path anywhere in the panel — no request ever names one.
309
+ * @param {any[]} records
310
+ * @returns {{path: string, bytes: number|null, sha256: string|null}[]}
311
+ */
312
+ export function deliveredOf(records: any[]): {
313
+ path: string;
314
+ bytes: number | null;
315
+ sha256: string | null;
316
+ }[];
317
+ /**
318
+ * `GET /api/runs/:runid/result` — the Result tab: for each rubric job line, its words and `~` rules from the signed spec, the facts it requires
319
+ * (worded by {@link factSentence}), and — read from the run's own delivered step file, sha256-checked against its `destination-written`
320
+ * record — one row per result with each fact's cell. `passed` is read from the run's own `check-run` record; the tab never re-decides. A red run
321
+ * delivers nothing, so it has no rows: it carries the check's own first gap instead. `null` = no such run or no result tab for it.
322
+ * @param {string} runid @param {{ home?: string }} opts
323
+ */
324
+ export function getRunResult(runid: string, opts?: {
325
+ home?: string;
326
+ sessionsRoot?: string;
327
+ }): {
328
+ runid: string;
329
+ lines: any[];
330
+ pages: string[];
331
+ pageNote: string | null;
332
+ notChecked: string[];
333
+ door: {
334
+ head: "waiting for you";
335
+ ask: string;
336
+ };
337
+ delivered: {
338
+ i: number;
339
+ path: string;
340
+ name: string;
341
+ bytes: number | null;
342
+ state: "ok" | "changed" | "gone";
343
+ words: string | null;
344
+ }[];
345
+ gap: {
346
+ line: string;
347
+ more: number;
348
+ moreText: string | null;
349
+ } | null;
350
+ } | null;
351
+ /**
352
+ * `GET /api/runs/:runid/download?i=<n>` — delivered file `n` (an index into the run's own `destination-written` list), or why not. The path is
353
+ * the record's, never the request's; the sha256 must still match the record.
354
+ * @param {string} runid @param {unknown} index @param {{ home?: string }} opts
355
+ * @returns {{ok: true, name: string, type: string, buf: Buffer}|{ok: false, code: number, error: string}}
356
+ */
357
+ export function getRunDownload(runid: string, index: unknown, opts?: {
358
+ home?: string;
359
+ }): {
360
+ ok: true;
361
+ name: string;
362
+ type: string;
363
+ buf: Buffer;
364
+ } | {
365
+ ok: false;
366
+ code: number;
367
+ error: string;
368
+ };
306
369
  /**
307
370
  * `GET /api/runs/:runid` — the full replay for the Run tab: steps (or
308
371
  * iterations), counters, summary-box fields. Looks `runid` up in the run
@@ -387,11 +450,12 @@ export function getRunAudit(runid: string, opts?: {
387
450
  * resolve to a real window, or the spine file is missing — the caller
388
451
  * renders 404, same posture as {@link getRunAudit}/{@link getRunJob}.
389
452
  * @param {string} runid
390
- * @param {{ part?: number, attempt?: number, offset?: number, limit?: number }} query
453
+ * @param {{ all?: boolean, part?: number, attempt?: number, offset?: number, limit?: number }} query
391
454
  * @param {{ home?: string }} [opts]
392
455
  * @returns {any|null}
393
456
  */
394
457
  export function getRunRounds(runid: string, query?: {
458
+ all?: boolean;
395
459
  part?: number;
396
460
  attempt?: number;
397
461
  offset?: number;
@@ -399,6 +463,78 @@ export function getRunRounds(runid: string, query?: {
399
463
  }, opts?: {
400
464
  home?: string;
401
465
  }): any | null;
466
+ /** the Round table's check row: the sign and the plain words, ONE place (PRD v1.106 Part 5 C) @param {number} passed @param {number} failed */
467
+ export function checkRowWords(passed: number, failed: number): {
468
+ sign: string;
469
+ text: string;
470
+ };
471
+ /**
472
+ * "71 model · 91 tool · 0 denied" — the Run summary's `calls:` row (PRD v1.106 Part 5 F). Model calls are the run's own spend records
473
+ * (`worker-round`, `judge-round`; the attempt-level `worker-result` echoes are not rounds and drafting is not the run's), tool and denied come
474
+ * from the gate-audit rows scoped to this run (the same figure `tools:` shows). No tool log saved says so, never a zero.
475
+ * @param {any[]} records the run's raw spine @param {{totalCalls: number, denied: number}|null} behaviour
476
+ * @returns {string}
477
+ */
478
+ export function callsLineOf(records: any[], behaviour: {
479
+ totalCalls: number;
480
+ denied: number;
481
+ } | null): string;
482
+ /**
483
+ * "16 run · 8 passed · 8 failed (write-flight-facts tries 1-5, then tries 1-3)" — counts of what ran, never a fraction (the "checks N/M"
484
+ * headline stays withdrawn). Every `exit-eval` result plus every graded `outer-close` stage; the parenthesis names the steps and try ranges
485
+ * that failed, from the same `tries[]` the Audit reads. A run with neither says `not recorded`.
486
+ * @param {any[]} records the run's raw spine @param {any[]|null|undefined} parts the enriched parts
487
+ * @returns {string}
488
+ */
489
+ export function checksLineOf(records: any[], parts: any[] | null | undefined): string;
490
+ /**
491
+ * The step card's extra line (PRD v1.106 Part 5 G): "5 tries · last try: 1 check passed, 1 failed", with the cut-off try named when there is one
492
+ * ("4 tries (the 4th cut off before its checks ran) · last try: no checks ran"). Words for what passed and failed, never a fraction. `null`
493
+ * when the step has no graded try (an old spine).
494
+ * @param {Array<{n: number, cutOff: boolean, passed: number, failed: number, ran: number}>} tries
495
+ * @returns {string|null}
496
+ */
497
+ export function triesLineOf(tries: Array<{
498
+ n: number;
499
+ cutOff: boolean;
500
+ passed: number;
501
+ failed: number;
502
+ ran: number;
503
+ }>): string | null;
504
+ /**
505
+ * "tools: read 16 · grep 22 · write 5 · denied 0" — the step's tool calls by kind, then how many the gate denied (PRD v1.106 Part 5 C). The
506
+ * `shell_`/`ctx_` prefixes are stripped like the rest of the page. `null` when no tool log was saved (nothing honest to count).
507
+ * @param {Record<string, number>|null|undefined} byTool @param {number|null|undefined} blocked
508
+ * @returns {string|null}
509
+ */
510
+ export function toolsTallyLine(byTool: Record<string, number> | null | undefined, blocked: number | null | undefined): string | null;
511
+ /**
512
+ * The try line's gap: the first failing reason, and how many more there are (PRD v1.106 Part 5 C). ONE function, beside {@link stageQuestionText}.
513
+ * @param {string|null|undefined} detail
514
+ * @returns {{ line: string, more: number }|null}
515
+ */
516
+ export function gapOneLine(detail: string | null | undefined): {
517
+ line: string;
518
+ more: number;
519
+ } | null;
520
+ /**
521
+ * "from line 1 ~ under $450; not a red-eye" — the job line(s) a check serves and the `~` rules written under them (the signed `jobLines`).
522
+ * `null` when the stage serves no job line (a mandatory guard).
523
+ * @param {number[]} fromLine @param {any} spec
524
+ * @returns {string|null}
525
+ */
526
+ export function fromLineText(fromLine: number[], spec: any): string | null;
527
+ /**
528
+ * "output judged: output/steps/1.json · 2.1 KB · sha256 674b…30" — the file a try's check read (PRD v1.106 Part 5 choice 14). ONE place owns
529
+ * these words. `size` is bytes under 1 KB, else KB with one decimal; the hash shows its first 4 and last 2 characters.
530
+ * @param {{path: string|null, bytes: number|null, sha256: string|null}} o
531
+ * @returns {string}
532
+ */
533
+ export function judgedFileLine(o: {
534
+ path: string | null;
535
+ bytes: number | null;
536
+ sha256: string | null;
537
+ }): string;
402
538
  /**
403
539
  * The Signed row of the Job tab: `YYYY-MM-DD HH:MM · <first 6 of specHash>`, the time being the run's FIRST `job-start`
404
540
  * in local time (hamr, P7). `null` when either part is not on record — never a made-up stamp.
@@ -627,10 +763,11 @@ export function getStartFromImport(id: string, opts?: {
627
763
  * only `createPanelServer` below always supplies one.
628
764
  * @param {import('node:http').IncomingMessage} req
629
765
  * @param {import('node:http').ServerResponse} res
630
- * @param {{ home?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes>, schedules?: ReturnType<typeof createScheduleDoor> }} opts
766
+ * @param {{ home?: string, sessionsRoot?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes>, schedules?: ReturnType<typeof createScheduleDoor> }} opts
631
767
  */
632
768
  export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
633
769
  home?: string;
770
+ sessionsRoot?: string;
634
771
  packageJsonPath?: string;
635
772
  port: number;
636
773
  token?: string;
@@ -731,11 +868,27 @@ export const RUNS_LIST_LIMITS: Readonly<{
731
868
  olderRuns: 7;
732
869
  needsYouDays: 7;
733
870
  }>;
871
+ /** what the Result tab's open door asks, after the door's own head words */
872
+ export const RESULT_DOOR_ASK: "the checks passed; is this what you wanted?";
873
+ /** the two sentences for a delivered file that is no longer what the run wrote (code-owned, one spelling) */
874
+ export const RESULT_FILE_GONE: "the saved file is gone";
875
+ export const RESULT_FILE_CHANGED: "the saved file changed after the run";
876
+ /** the same two states for a red run's file, which is the last one the check read (not a delivered file) */
877
+ export const RESULT_JUDGED_GONE: "the file is gone";
878
+ export const RESULT_JUDGED_CHANGED: "the file changed after the check";
879
+ /** what a red run's rows are, said once above them */
880
+ export const RESULT_JUDGED_NOTE: "a red run delivers nothing; these rows are the last file the check read";
881
+ /** the words beside a Result-tab page link: the page is read again from the web, so it can differ from what the run read */
882
+ export const RESULT_PAGE_MAY_CHANGE: "The page may have changed since this run.";
883
+ /** the words after a fact whose limit the person agreed to in a Part 5b look ask */
884
+ export const RESULT_YOU_CONFIRMED: "(you confirmed)";
734
885
  /** The default/max page size for {@link getRunRounds} — tighten-only,
735
886
  * named so a future change is a deliberate, visible edit (item 5,
736
887
  * 2026-09-25). */
737
888
  export const ROUNDS_PAGE_DEFAULT: 50;
738
889
  export const ROUNDS_PAGE_MAX: 200;
890
+ /** the Audit step table's page size (PRD v1.106 Part 5 choice 3): rows, not rounds */
891
+ export const STEP_ROWS_PAGE: 20;
739
892
  import { createScheduleDoor } from './schedules.js';
740
893
  import { createAuthorRoutes } from './authorroutes.js';
741
894
  import { createSettingsRoutes } from './settingsroutes.js';
@@ -34,6 +34,26 @@ export function partStatusFor(part: {
34
34
  sign: string;
35
35
  word: string;
36
36
  };
37
+ /**
38
+ * The words one door wears: header phrase, the `last answer:` line (with the time `HH:MM` once decided) and the table rows.
39
+ * @param {{at: string|null, paths: string[], decision: string|null, text: string|null, decidedAt: string|null}} door
40
+ * @returns {{ head: string, last: string, rows: Array<{path: string|null, text: string, time: string|null}> }}
41
+ */
42
+ export function doorViewOf(door: {
43
+ at: string | null;
44
+ paths: string[];
45
+ decision: string | null;
46
+ text: string | null;
47
+ decidedAt: string | null;
48
+ }): {
49
+ head: string;
50
+ last: string;
51
+ rows: Array<{
52
+ path: string | null;
53
+ text: string;
54
+ time: string | null;
55
+ }>;
56
+ };
37
57
  /** the sentence a passed run carries as its reason (the Ended block's `line`) */
38
58
  export const GOAL_MET_LINE: "goal met";
39
59
  /** @typedef {'running'|'waiting'|'passed'|'failed'|'capped'|'stopped'|'died'} StatusKey */
@@ -68,4 +88,35 @@ export const STATUS: Readonly<{
68
88
  word: "died";
69
89
  }>;
70
90
  }>;
91
+ /**
92
+ * PRD v1.106 Part 5 (C, choice 9): the review door's words, ONE table. The door is the only human step bareloop has; there is no "you
93
+ * cancelled" (cancel is deleted). `decision` is the spine's `door-decision.decision`, or null while nothing has been decided.
94
+ */
95
+ export const DOOR_WORDS: Readonly<{
96
+ waiting: Readonly<{
97
+ head: "waiting for you";
98
+ row: "paused for you";
99
+ last: "none yet";
100
+ }>;
101
+ accept: Readonly<{
102
+ head: "you accepted";
103
+ row: "you accepted";
104
+ last: "you accepted";
105
+ }>;
106
+ rerun: Readonly<{
107
+ head: "you said rerun";
108
+ row: "you said rerun";
109
+ last: "you said rerun";
110
+ }>;
111
+ pause: Readonly<{
112
+ head: "paused";
113
+ row: "paused";
114
+ last: "paused";
115
+ }>;
116
+ }>;
117
+ /** the marker a part wears for who did its work (PRD v1.106 Part 5 C/G): the model, or a person asked */
118
+ export const ACTOR_MARKER: Readonly<{
119
+ model: "[model]";
120
+ human: "[human · ask]";
121
+ }>;
71
122
  export type StatusKey = "running" | "waiting" | "passed" | "failed" | "capped" | "stopped" | "died";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @param {number|null|undefined} n a dollar amount
3
+ * @returns {string}
4
+ */
5
+ export function panelMoney2(n: number | null | undefined): string;
package/types/replay.d.ts CHANGED
@@ -180,7 +180,26 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
180
180
  startTs: number | null;
181
181
  endTs: number | null;
182
182
  exitEvalDetail: string | null;
183
+ checks: Array<{
184
+ type: string;
185
+ pass: boolean;
186
+ detail: string | null;
187
+ name: string | null;
188
+ }>;
189
+ outputJudged: Array<{
190
+ path: string | null;
191
+ bytes: number | null;
192
+ sha256: string | null;
193
+ }>;
183
194
  }[];
195
+ cutOffTry: {
196
+ n: number;
197
+ iteration: any;
198
+ startSeq: any;
199
+ endSeq: any;
200
+ startTs: number | null;
201
+ endTs: number | null;
202
+ } | null;
184
203
  stepStartSeq: any;
185
204
  stepEndSeq: any;
186
205
  stepStartTs: number | null;
@@ -291,9 +310,10 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
291
310
  * @param {boolean|null} [draftSpendComplete] whether the drafting fold above was EXACT;
292
311
  * `null`/`undefined`/`true` all read as exact (never a new enum value elsewhere) —
293
312
  * only an explicit `false` triggers the floor wording.
313
+ * @param {number|null} [runUsd] the run's own spend as a number (excluding drafting), so the sum can be written
294
314
  * @returns {string}
295
315
  */
296
- export function moneyWithDraft(spendCore: string, draftSpentUsd: number | null, budgetUsd?: number | null, draftSpendComplete?: boolean | null): string;
316
+ export function moneyWithDraft(spendCore: string, draftSpentUsd: number | null, budgetUsd?: number | null, draftSpendComplete?: boolean | null, runUsd?: number | null): string;
297
317
  /**
298
318
  * Render a {@link replayRun} summary as one printable page: a header block
299
319
  * (goal/shape/signed/branch/outcome/spent), a timeline (steps or iterations,
@@ -35,6 +35,13 @@ export function rubricGoal({ jobLines, files, confirmedGoal, closeDecl }: {
35
35
  confirmedGoal?: string | null;
36
36
  closeDecl?: any;
37
37
  }): string;
38
+ /**
39
+ * The sentence the person confirmed, out of a goal text: for a rubric brief the rest of the `CONFIRMED_GOAL_PREFIX` line,
40
+ * or null when the brief has none; any other goal (a code job's) comes back unchanged.
41
+ * @param {string} goal
42
+ * @returns {string|null}
43
+ */
44
+ export function confirmedGoalOf(goal: string): string | null;
38
45
  /**
39
46
  * The SHAPE of a `rubric-step` stage's parameters — what a part may carry — and nothing about which class is allowed in
40
47
  * this build (that is `checkRubricParts`, which also sees the whole job). Pure, never throws.
@@ -58,6 +65,28 @@ export function buildStepSpec(params: {
58
65
  step: number;
59
66
  parts: any[];
60
67
  }, inputShas: Record<string, string>): Record<string, any>;
68
+ /**
69
+ * The files one named check READS, tree-relative: a `rubric-step` stage's own step file (`output/steps/N.json`). Any other kind reads
70
+ * none that a try's output can be told by, so `[]`. From the signed declaration, never guessed.
71
+ * @param {any} closeDecl @param {string|null|undefined} checkName
72
+ * @returns {string[]}
73
+ */
74
+ export function judgedFilesOf(closeDecl: any, checkName: string | null | undefined): string[];
75
+ /**
76
+ * 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
77
+ * 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
78
+ * line). A missing, non-regular or over-size file is skipped — nothing was judged there.
79
+ * @param {any} closeDecl @param {string} workdir @param {Array<{type?: string, name?: string}>} exits a step's declared exits
80
+ * @returns {Promise<Array<{path: string, bytes: number, sha256: string}>>}
81
+ */
82
+ export function outputJudgedRecords(closeDecl: any, workdir: string, exits: Array<{
83
+ type?: string;
84
+ name?: string;
85
+ }>): Promise<Array<{
86
+ path: string;
87
+ bytes: number;
88
+ sha256: string;
89
+ }>>;
61
90
  /**
62
91
  * 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
63
92
  * parses to nothing (reason `not-json`); text cut off INSIDE an otherwise valid JSON object cannot be told from a short
@@ -101,7 +130,9 @@ export function plainStepGap(step: number, why: string, g: any, params?: any): s
101
130
  * `inputs` maps each cited label to its frozen TEXT; its sha256 is taken here from that text, and bareguard compares it to
102
131
  * the manifest's recorded sha via `inputShas`.
103
132
  * @param {Record<string, string>} inputShas the manifest's recorded sha256 per cited label
104
- * @returns {Promise<{verdict: 'green'|'red'|'stopped', gapLines: string[], fault: string|null, detail: Record<string, any>}>}
133
+ * @returns {Promise<{verdict: 'green'|'red'|'stopped', gapLines: string[], fault: string|null, detail: Record<string, any>,
134
+ * evidence?: any[], evidenceTotal?: number}>} `evidence`/`evidenceTotal` are bareguard's `full.evidence` (what each `cited` check read), carried out
135
+ * for the report-only `rubric-evidence` spine record; no verdict, gap or count above is derived from them
105
136
  */
106
137
  export function gradeStep({ params, bytes, outputBytes, inputs, factQuotes, factMissing }: {
107
138
  params: {
@@ -118,6 +149,8 @@ export function gradeStep({ params, bytes, outputBytes, inputs, factQuotes, fact
118
149
  gapLines: string[];
119
150
  fault: string | null;
120
151
  detail: Record<string, any>;
152
+ evidence?: any[];
153
+ evidenceTotal?: number;
121
154
  }>;
122
155
  /**
123
156
  * The labels of a job's numbered inputs, or null when it carries none (a caller that has no inputs in hand skips the label rule).
@@ -178,6 +211,17 @@ export function settlePersonsWords(declaration: any, jobLines: any[] | null | un
178
211
  * @param {any} declaration @returns {any}
179
212
  */
180
213
  export function withFactBindings(declaration: any): any;
214
+ /**
215
+ * 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.
216
+ * - a dropped FACT that carries a `~` rule no part carries and no `refused` entry names -> `closeDecl.refused` gets {line, rule, reason}
217
+ * (shown under that line in the person's own rule words by {@link ruleNotChecked});
218
+ * - 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
219
+ * narrower check is still said) -> one line in `closeDecl.notes`, shown as Not checked (the AI's own reading).
220
+ * Never a line-level `refused` entry without `rule` (that would silence every other rule red on the line). A rule the drafter already listed
221
+ * itself is never listed twice, and running this twice adds nothing. Pure; the input is untouched.
222
+ * @param {any} declaration @param {import('./pagerules.js').Dropped[]|null|undefined} dropped @param {any[]|null|undefined} jobLines @returns {any}
223
+ */
224
+ export function withDropNotes(declaration: any, dropped: import("./pagerules.js").Dropped[] | null | undefined, jobLines: any[] | null | undefined): any;
181
225
  /**
182
226
  * The ~ rules the drafter listed under Not checked (`refused` entries that name a `rule`), each as one line in the person's own
183
227
  * words: `line N: "<the rule as written>" — <the drafter's reason>` (PART 1b, RF6 / RF16).
@@ -190,6 +234,11 @@ export function ruleNotChecked(closeDecl: any, jobLines: any[] | null | undefine
190
234
  * @param {any} closeDecl @returns {string[]}
191
235
  */
192
236
  export function proseNotChecked(closeDecl: any): string[];
237
+ /** 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}[]} */
238
+ export function proseNotCheckedItems(closeDecl: any): {
239
+ line: number;
240
+ text: string;
241
+ }[];
193
242
  /**
194
243
  * One countable part as a plain sentence, written by code from the part's signed fields (never from the model's words), so the
195
244
  * plan card and the close are one thing seen twice.
@@ -269,6 +318,11 @@ export const STEP_DIR: "output/steps";
269
318
  export function stepFileRel(n: number): string;
270
319
  /** the largest step file the close reads: a bigger one is a stop, never silently truncated (bareguard's own source cap) */
271
320
  export const STEP_MAX_BYTES: number;
321
+ /**
322
+ * What a rubric job's goal text is: the worker brief `rubricGoal` builds. The Job tab's "AI's summary" is only the one
323
+ * sentence the person confirmed, never this brief, so the first line and the confirmed-sentence prefix have ONE spelling.
324
+ */
325
+ export const RUBRIC_GOAL_HEAD: "Do the job below from the input files, then write one JSON file per numbered line.";
272
326
  /** the closed set of classes a part is sorted into; `none` = "I could not sort this one" (a red, in plain words, never a guess) */
273
327
  export const PART_CLASSES: readonly string[];
274
328
  /**
@@ -279,6 +333,8 @@ export const PART_CLASSES: readonly string[];
279
333
  */
280
334
  export const COUNT_CHECKS: Readonly<Record<string, readonly string[]>>;
281
335
  export function sha256Of(b: string | Buffer): string;
336
+ export function boundInWords(b: unknown, said: string): boolean;
337
+ export function wordsSaidOn(jl: any): string;
282
338
  export type ShapeRed = {
283
339
  code: string;
284
340
  path: string;
@@ -48,7 +48,8 @@ export function appendRun(row: RunRow, opts?: {
48
48
  reason?: "duplicate-runid";
49
49
  };
50
50
  /**
51
- * @typedef {{ runid: string, type: 'settled'|'released', by: string, at: string, reason?: string, spentUsd?: number, spendComplete?: boolean, leg?: number }} RunEvent
51
+ * @typedef {{ runid: string, type: 'settled'|'released', by: string, at: string, reason?: string, words?: string, spentUsd?: number, spendComplete?: boolean, leg?: number }} RunEvent
52
+ * `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).
52
53
  * `leg` (P5-R): which leg of the run the write-back is about; absent = leg 1 (every entry written before P5-R).
53
54
  * A resumed run is ONE row, so `settled`/`released` are per leg: a later leg's `released` gives back THAT leg's
54
55
  * claim and never removes the run.
@@ -226,6 +227,7 @@ export type RunRow = {
226
227
  leg?: number;
227
228
  };
228
229
  /**
230
+ * `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).
229
231
  * `leg` (P5-R): which leg of the run the write-back is about; absent = leg 1 (every entry written before P5-R).
230
232
  * A resumed run is ONE row, so `settled`/`released` are per leg: a later leg's `released` gives back THAT leg's
231
233
  * claim and never removes the run.
@@ -241,6 +243,7 @@ export type RunEvent = {
241
243
  by: string;
242
244
  at: string;
243
245
  reason?: string;
246
+ words?: string;
244
247
  spentUsd?: number;
245
248
  spendComplete?: boolean;
246
249
  leg?: number;
@@ -84,14 +84,16 @@ export function jobScheduleWords(f: {
84
84
  export function retryNextWords(ms: number): string;
85
85
  /**
86
86
  * 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
87
- * run of the job is running (as everywhere in the panel), [✗] when the SCHEDULE row is red, else [·] (on, waiting).
88
- * @param {{running: boolean, red: boolean}} f @returns {{sign: string}}
87
+ * 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
88
+ * 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.
89
+ * @param {{running: boolean, red: boolean}} f @returns {{sign: string, word: string}}
89
90
  */
90
91
  export function scheduleSignFor(f: {
91
92
  running: boolean;
92
93
  red: boolean;
93
94
  }): {
94
95
  sign: string;
96
+ word: string;
95
97
  };
96
98
  /** 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` */
97
99
  export function sectionThisRunWords(rec: any): string;
package/types/source.d.ts CHANGED
@@ -421,6 +421,17 @@ export function refreezeUrlInputs({ into, fetchUrl }: {
421
421
  ok: false;
422
422
  error: string;
423
423
  }>;
424
+ /**
425
+ * 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
426
+ * (`inputs[].url`) and when the run-start refetch last finished (`refrozenAt`, written last by {@link refreezeUrlInputs}). The panel decides
427
+ * "fetching the page..." from this: it applies while a web input exists and no refetch has finished at or after `sinceIso`.
428
+ * @param {string} sourceJsonPath @param {string|null|undefined} sinceIso
429
+ * @returns {{ hasWeb: boolean, fetching: boolean }}
430
+ */
431
+ export function webFetchState(sourceJsonPath: string, sinceIso: string | null | undefined): {
432
+ hasWeb: boolean;
433
+ fetching: boolean;
434
+ };
424
435
  /** the per-file ceiling for a rubric input: bareguard's `cited` check searches a quote in a source only up to this size
425
436
  * (its `quoteSourceMaxBytes`); a bigger file could never be cited from, so the door refuses it at $0 */
426
437
  export const RUBRIC_INPUT_MAX_BYTES: number;
@@ -84,6 +84,45 @@ export function thinkingRowText({ mode, support, status }: {
84
84
  support?: true | false | "unverified";
85
85
  status?: string;
86
86
  }): string;
87
+ /**
88
+ * THE one decision for a worker model call (PRD v1.106 Part 1): from the phase label and attempt iteration `mkWorker` already holds,
89
+ * and the job's tick (`job.thinking === true`). Decided in code, never by the model.
90
+ * tick on -> 'on' for every call
91
+ * plan (draft, redraft, replan) -> 'on' (the planner is the hard part of a run)
92
+ * scout -> 'off'
93
+ * step:<id> attempt 1 -> 'off'
94
+ * step:<id> attempt >= 2 -> 'on'
95
+ * fix (close-fix) -> 'on'
96
+ * A step or fix iteration that is not a whole number >= 1 throws (operator error), as does an unknown phase — never a guess.
97
+ * @param {{ phase: unknown, iteration?: unknown, tick?: unknown }} o
98
+ * @returns {'on'|'off'}
99
+ */
100
+ export function workerThinkingMode({ phase, iteration, tick }: {
101
+ phase: unknown;
102
+ iteration?: unknown;
103
+ tick?: unknown;
104
+ }): "on" | "off";
105
+ /**
106
+ * 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
107
+ * `thinking-decision` of `phase` (and `iteration`, when given) whose seq lies strictly between `lo` and `hi`, pairs it with the first
108
+ * `thinking-status` of the same phase and iteration after it (before the next decision), and words both through {@link thinkingRowText}
109
+ * (drafting's own words). `null` when the window holds no decision: a run from before the records existed shows no thinking word.
110
+ * @param {any[]} spine the run's spine records (each with a numeric `seq`)
111
+ * @param {{ phase: string, lo?: number, hi?: number, iteration?: number|null }} q
112
+ * @returns {string|null}
113
+ */
114
+ export function thinkingWordsFrom(spine: any[], { phase, lo, hi, iteration }: {
115
+ phase: string;
116
+ lo?: number;
117
+ hi?: number;
118
+ iteration?: number | null;
119
+ }): string | null;
120
+ /**
121
+ * 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
122
+ * recorded a `thinking-decision`; a run from before the thinking rule reads `—`, never a guess from the job's tick (fwdloop M4d am28).
123
+ * @param {boolean} tick @param {boolean} recorded the run's spine holds at least one `thinking-decision` @returns {string}
124
+ */
125
+ export function thinkingJobText(tick: boolean, recorded: boolean): string;
87
126
  /** The words under a greyed "Thinking on" box (the server's `thinking.canChange` decides; the page decides nothing). */
88
127
  export const THINKING_FIXED_SAY: "can't change this for this model";
89
128
  /** The enumerated operator knob a drafting call is built with: 'on' | 'off' (the tick, spelled as a word). */
@@ -219,6 +219,10 @@ export type RunCtx = {
219
219
  * see `RunOpts.into`
220
220
  */
221
221
  into?: string | undefined;
222
+ /**
223
+ * a FRESH `--spec` run: fetch the web inputs again AFTER the run is listed (never on resume/door/decide)
224
+ */
225
+ refreeze?: boolean | undefined;
222
226
  model: string | null;
223
227
  providerKey: string | null;
224
228
  readShim: string | null;