bareloop 0.43.0 → 0.45.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 (64) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/bareloop.context.md +53 -13
  3. package/package.json +2 -2
  4. package/src/authorflow.js +27 -12
  5. package/src/authoring.js +1 -1
  6. package/src/authorjob.js +52 -16
  7. package/src/authorreadout.js +12 -6
  8. package/src/authorrun.js +8 -2
  9. package/src/declaredclose.js +4 -1
  10. package/src/draftspend.js +20 -2
  11. package/src/facts.js +54 -9
  12. package/src/factwords.js +58 -6
  13. package/src/fitcheck.js +166 -0
  14. package/src/inputlook.js +38 -3
  15. package/src/job.js +1 -1
  16. package/src/kinds.js +5 -0
  17. package/src/pagedropwords.js +110 -0
  18. package/src/pagedry.js +125 -0
  19. package/src/pagemap.js +269 -0
  20. package/src/pagerules.js +318 -0
  21. package/src/panel/authorroutes.js +34 -30
  22. package/src/panel/authorsession.js +66 -11
  23. package/src/panel/index.html +469 -65
  24. package/src/panel/jobplan.js +14 -7
  25. package/src/panel/runroutes.js +46 -22
  26. package/src/panel/server.js +792 -28
  27. package/src/panel/status.js +33 -0
  28. package/src/panelmoney.js +29 -0
  29. package/src/planrun.js +53 -4
  30. package/src/promptregisters.js +8 -0
  31. package/src/replay.js +118 -7
  32. package/src/rubricstep.js +157 -15
  33. package/src/runlist.js +2 -1
  34. package/src/schedulewords.js +6 -3
  35. package/src/source.js +18 -0
  36. package/src/thinking.js +63 -2
  37. package/src/userrun.js +28 -14
  38. package/types/authorflow.d.ts +42 -6
  39. package/types/authorjob.d.ts +20 -4
  40. package/types/declaredclose.d.ts +2 -0
  41. package/types/draftspend.d.ts +27 -1
  42. package/types/facts.d.ts +15 -0
  43. package/types/factwords.d.ts +26 -3
  44. package/types/fitcheck.d.ts +113 -0
  45. package/types/inputlook.d.ts +15 -0
  46. package/types/kinds.d.ts +5 -0
  47. package/types/pagedropwords.d.ts +39 -0
  48. package/types/pagedry.d.ts +37 -0
  49. package/types/pagemap.d.ts +154 -0
  50. package/types/pagerules.d.ts +86 -0
  51. package/types/panel/authorroutes.d.ts +17 -14
  52. package/types/panel/authorsession.d.ts +4 -2
  53. package/types/panel/jobplan.d.ts +5 -2
  54. package/types/panel/runroutes.d.ts +2 -2
  55. package/types/panel/server.d.ts +156 -2
  56. package/types/panel/status.d.ts +51 -0
  57. package/types/panelmoney.d.ts +5 -0
  58. package/types/replay.d.ts +21 -1
  59. package/types/rubricstep.d.ts +62 -1
  60. package/types/runlist.d.ts +4 -1
  61. package/types/schedulewords.d.ts +4 -2
  62. package/types/source.d.ts +11 -0
  63. package/types/thinking.d.ts +39 -0
  64. package/types/userrun.d.ts +4 -0
@@ -87,19 +87,20 @@ export function refuseLockedKind(kind: string): Refusal;
87
87
  * rates?: {in: number, out: number}|null, draftThinking?: 'on'|'off',
88
88
  * onPhase?: (phase: string, data?: any) => void,
89
89
  * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string}) => void,
90
- * seedFn?: Function, scoutFn?: Function, listingFn?: Function, lookFn?: Function|null,
90
+ * seedFn?: Function, scoutFn?: Function, listingFn?: Function, lookFn?: Function|null, fitFn?: Function|null,
91
+ * fitAsk?: ((question: string) => Promise<string|null>)|null, onFit?: (fit: {state: string, asks: any[], notFound: any[]}) => void,
91
92
  * authorFn?: Function, authorOpts?: object, writeScope?: string[]|null,
92
93
  * signerFix?: Function|any, proposeFn?: Function, compileOpts?: object,
93
94
  * 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>}|null>)|null,
95
+ * answerQuestions?: ((o: {questions: {line: number, question: string, rule?: number, source?: string, ask?: string, reading?: string}[], dropped: {reason: string, raw: any}[], confirmed: any}) =>
96
+ * Promise<{jobLines: {n: number, text: string, rule?: string}[], answers: Record<string|number, any>, left?: {line: number, rule: number}[]}|null>)|null,
96
97
  * confirmGenerate?: Function|null, isRepo?: boolean,
97
98
  * langResult?: {kind: string, candidates?: string[], [k: string]: any}|null}} o
98
99
  * @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
99
100
  * closeDecl: any, seedRef: string|null, authoring: any, judged: any, reds: Red[],
100
101
  * stop: string|null, cost: any, interview: any, confirmed: any}>}
101
102
  */
102
- export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, jobLines, inputs, generate, provider, seedRef, scout, listing, ceilingUsd, rates, draftThinking, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, lookFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, answerQuestions, }: {
103
+ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, jobLines, inputs, generate, provider, seedRef, scout, listing, ceilingUsd, rates, draftThinking, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, lookFn, fitFn, fitAsk, onFit, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, answerQuestions, }: {
103
104
  answers: Record<string | number, any>;
104
105
  repoPath?: string | null;
105
106
  lang: string;
@@ -137,6 +138,13 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
137
138
  scoutFn?: Function;
138
139
  listingFn?: Function;
139
140
  lookFn?: Function | null;
141
+ fitFn?: Function | null;
142
+ fitAsk?: ((question: string) => Promise<string | null>) | null;
143
+ onFit?: (fit: {
144
+ state: string;
145
+ asks: any[];
146
+ notFound: any[];
147
+ }) => void;
140
148
  authorFn?: Function;
141
149
  authorOpts?: object;
142
150
  writeScope?: string[] | null;
@@ -151,6 +159,10 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
151
159
  questions: {
152
160
  line: number;
153
161
  question: string;
162
+ rule?: number;
163
+ source?: string;
164
+ ask?: string;
165
+ reading?: string;
154
166
  }[];
155
167
  dropped: {
156
168
  reason: string;
@@ -164,6 +176,10 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
164
176
  rule?: string;
165
177
  }[];
166
178
  answers: Record<string | number, any>;
179
+ left?: {
180
+ line: number;
181
+ rule: number;
182
+ }[];
167
183
  } | null>) | null;
168
184
  confirmGenerate?: Function | null;
169
185
  isRepo?: boolean;
@@ -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;
@@ -3,6 +3,8 @@
3
3
  * - `step-start` {no, id, label, at}
4
4
  * - `step-end` {no, status: 'done'|'failed', label, at} (a re-opened step writes `step-reopen` {no, at} first)
5
5
  * - `step-label` {no, label, at} (the step reads `label` from now on, status and times untouched: the thinking row's "the model thought anyway" / "can't change it" / "not confirmed")
6
+ * - `human` {no, source: 'confirm'|'try-3'|'look', question, answer, line?, at}: one question the person was asked and what they answered (the answer is secret-masked
7
+ * before it is written; the row is written only when BOTH exist, so a question nobody answered leaves no row)
6
8
  * - `call` {no, step, label, model, thinking, thinkingSupport, thinkingStatus?, costUsd (null stays null), unpricedRounds, at}: one METERED model call
7
9
  * (`thinking` = the tick's mode: off | on; `thinkingSupport` = bare-agent's lookup for the model: true | false | 'unverified';
8
10
  * `thinkingStatus` = bare-agent's own verdict for the call's run: applied | unsupported | ignored | unknown, present only when thinkingMode was sent)
@@ -58,12 +60,33 @@ export function sessionDirForRow(row: {
58
60
  home?: string;
59
61
  sessionsRoot?: string;
60
62
  }): string | null;
63
+ /**
64
+ * PRD v1.106 Part 5 Q1: one answered question as the two lines the Audit's drafting table shows under its step. ONE place owns the words.
65
+ * @param {{question: string, answer: string, source?: string, line?: number, at?: string}} h
66
+ * @returns {{question: string, answer: string, source: string, line?: number, at?: string, askedText: string, saidText: string}}
67
+ */
68
+ export function humanRowWords(h: {
69
+ question: string;
70
+ answer: string;
71
+ source?: string;
72
+ line?: number;
73
+ at?: string;
74
+ }): {
75
+ question: string;
76
+ answer: string;
77
+ source: string;
78
+ line?: number;
79
+ at?: string;
80
+ askedText: string;
81
+ saidText: string;
82
+ };
61
83
  /**
62
84
  * Fold `<dir>/draft-log.jsonl` into one record per drafting step, each with its calls. A line that cannot be parsed is
63
85
  * skipped (a torn last line must not hide the rest).
64
86
  * @param {string} dir
65
87
  * @returns {{no: number, id: string, label: string, status: 'running'|'done'|'failed', startedAt: string, endedAt: string|null, wallMs: number|null,
66
- * calls: {label: string, model: string|null, costUsd: number|null, unpricedRounds: number, at: string}[]}[]}
88
+ * calls: {label: string, model: string|null, costUsd: number|null, unpricedRounds: number, at: string}[],
89
+ * humans: ReturnType<typeof humanRowWords>[]}[]}
67
90
  */
68
91
  export function readDraftSteps(dir: string): {
69
92
  no: number;
@@ -80,6 +103,7 @@ export function readDraftSteps(dir: string): {
80
103
  unpricedRounds: number;
81
104
  at: string;
82
105
  }[];
106
+ humans: ReturnType<typeof humanRowWords>[];
83
107
  }[];
84
108
  /**
85
109
  * The DRAFTING PART of a run (hamr 2026-10-06, option A): the first part of the run's views, built from the session's
@@ -109,6 +133,8 @@ export const DRAFT_LOG_FILE: "draft-log.jsonl";
109
133
  * - `step-start` {no, id, label, at}
110
134
  * - `step-end` {no, status: 'done'|'failed', label, at} (a re-opened step writes `step-reopen` {no, at} first)
111
135
  * - `step-label` {no, label, at} (the step reads `label` from now on, status and times untouched: the thinking row's "the model thought anyway" / "can't change it" / "not confirmed")
136
+ * - `human` {no, source: 'confirm'|'try-3'|'look', question, answer, line?, at}: one question the person was asked and what they answered (the answer is secret-masked
137
+ * before it is written; the row is written only when BOTH exist, so a question nobody answered leaves no row)
112
138
  * - `call` {no, step, label, model, thinking, thinkingSupport, thinkingStatus?, costUsd (null stays null), unpricedRounds, at}: one METERED model call
113
139
  * (`thinking` = the tick's mode: off | on; `thinkingSupport` = bare-agent's lookup for the model: true | false | 'unverified';
114
140
  * `thinkingStatus` = bare-agent's own verdict for the call's run: applied | unsupported | ignored | unknown, present only when thinkingMode was sent)
package/types/facts.d.ts CHANGED
@@ -23,6 +23,14 @@ export function factClaims(part: any, results: unknown): {
23
23
  quote: string;
24
24
  values?: Record<string, string>;
25
25
  }[];
26
+ /**
27
+ * One amount of a money fact in the fact's OWN signed forms, never a hardcoded sign: `symbolFirst` puts the currency sign before the amount
28
+ * ("$144") when the fact has one, else the unit after it ("144 US dollars"); not `symbolFirst` (a rule's bound) prefers the unit.
29
+ * @param {any} f a money fact @param {string|number} n @param {boolean} symbolFirst @returns {string}
30
+ */
31
+ export function moneyAs(f: any, n: string | number, symbolFirst: boolean): string;
32
+ /** @param {any} f a fact with a rule @returns {string} the rule in words, e.g. "at most 450 US dollars" */
33
+ export function ruleWords(f: any): string;
26
34
  /**
27
35
  * One fact as a plain row, written by code from the signed fields:
28
36
  * `fact · price — money, at most 450 US dollars, after "Price", quoted from "Flights"`.
@@ -69,3 +77,10 @@ export const FACT_OPS: readonly string[];
69
77
  export const MAX_FACTS: 10;
70
78
  /** every field a part of class `fact` may carry (the shared ones are checked by rubricStepShapeReds) */
71
79
  export const FACT_PART_FIELDS: readonly string[];
80
+ /** the limits bareguard 0.24 puts on a money fact's lists (ruling #65), enforced here first so the red is ours and plain */
81
+ export const MAX_CURRENCIES: 3;
82
+ export const MAX_UNITS: 10;
83
+ export const MAX_UNIT_CHARS: 100;
84
+ export function asList(v: unknown): string[];
85
+ export function moneyForms(f: any): string[];
86
+ export function boundOk(shape: string, v: unknown): boolean;
@@ -9,16 +9,35 @@ export function countExact(source: string, words: string): number;
9
9
  * @param {string} source @param {string} words @returns {number}
10
10
  */
11
11
  export function countPhrase(source: string, words: string): number;
12
+ /**
13
+ * What the page-word map (src/pagemap.js) KEPT and DROPPED for one input: the sets the second proof reads. A look from the old search-and-read
14
+ * path has no `dropped` list, so it gives null and the label is proved against the code-made list as before.
15
+ * @param {{facts?: any[], dropped?: (import('./pagerules.js').Dropped & {source?: string})[]}|null|undefined} look @param {string} source
16
+ * @returns {{labels: Set<string>, units: Set<string>, currencies: Set<string>, words: Set<string>, dropped: Map<string, import('./pagerules.js').Dropped>}|null}
17
+ */
18
+ export function keptSetOf(look: {
19
+ facts?: any[];
20
+ dropped?: (import("./pagerules.js").Dropped & {
21
+ source?: string;
22
+ })[];
23
+ } | null | undefined, source: string): {
24
+ labels: Set<string>;
25
+ units: Set<string>;
26
+ currencies: Set<string>;
27
+ words: Set<string>;
28
+ dropped: Map<string, import("./pagerules.js").Dropped>;
29
+ } | null;
12
30
  /**
13
31
  * The reds for ONE fact part against its frozen source text.
14
- * @param {{part: any, text: string, line: number|string, scan?: import('./labelscan.js').LabelScan}} o
32
+ * @param {{part: any, text: string, line: number|string, scan?: import('./labelscan.js').LabelScan, kept?: ReturnType<typeof keptSetOf>}} o
15
33
  * @returns {{code: string, detail: string}[]}
16
34
  */
17
- export function checkFactWords({ part, text, line, scan }: {
35
+ export function checkFactWords({ part, text, line, scan, kept }: {
18
36
  part: any;
19
37
  text: string;
20
38
  line: number | string;
21
39
  scan?: import("./labelscan.js").LabelScan;
40
+ kept?: ReturnType<typeof keptSetOf>;
22
41
  }): {
23
42
  code: string;
24
43
  detail: string;
@@ -27,9 +46,13 @@ export function checkFactWords({ part, text, line, scan }: {
27
46
  * The reds for every fact part of a declaration. A fact part whose input text is not at hand is a red: a check whose words were never
28
47
  * proved against the source is never signed.
29
48
  * @param {any} declaration @param {Record<string, string>|null} texts label -> frozen text
49
+ * @param {{facts?: any[], dropped?: any[]}|null} [look] the page-word map's report: what it kept and dropped (null = no map, the code-made list is the proof)
30
50
  * @returns {{code: string, path: string, detail: string}[]}
31
51
  */
32
- export function checkDeclarationWords(declaration: any, texts: Record<string, string> | null): {
52
+ export function checkDeclarationWords(declaration: any, texts: Record<string, string> | null, look?: {
53
+ facts?: any[];
54
+ dropped?: any[];
55
+ } | null): {
33
56
  code: string;
34
57
  path: string;
35
58
  detail: string;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * What the AI is told: the job lines (the numbered text only, no `~` rules) and the WHOLE text of every input under its label. Adapted from the POC
3
+ * call (.claude/scratch/fit-poc6/run.mjs): the asks are copied from each job line instead of hand-written.
4
+ * @param {{jobLines: {n: number, text: string}[], texts: Record<string, string>}} o
5
+ * @returns {string}
6
+ */
7
+ export function fitPrompt({ jobLines, texts }: {
8
+ jobLines: {
9
+ n: number;
10
+ text: string;
11
+ }[];
12
+ texts: Record<string, string>;
13
+ }): string;
14
+ /**
15
+ * Read the AI's reply and decide, all in code. Malformed (no repair): not JSON, another form, a line that is not a job line, an ask not copied from its own
16
+ * line, found not a boolean, a quote neither text nor null, or a job line with no ask. An ask is found only if `found` is true AND its quote is on a page;
17
+ * `found` with a quote that is not on any page (or no quote) is made up and counts as not found.
18
+ * @param {unknown} text the reply
19
+ * @param {{n: number, text: string}[]} jobLines
20
+ * @param {Record<string, string>} texts the frozen input texts by label
21
+ * @returns {{status: 'malformed'}|{status: 'ok', state: 'fits'|'ask', asks: FitAsk[], notFound: {line: number, ask: string}[]}}
22
+ */
23
+ export function decideFit(text: unknown, jobLines: {
24
+ n: number;
25
+ text: string;
26
+ }[], texts: Record<string, string>): {
27
+ status: "malformed";
28
+ } | {
29
+ status: "ok";
30
+ state: "fits" | "ask";
31
+ asks: FitAsk[];
32
+ notFound: {
33
+ line: number;
34
+ ask: string;
35
+ }[];
36
+ };
37
+ /**
38
+ * Run the fit check: one call (one retry on a malformed reply or a transport failure or a 429). Never throws on a call casualty; every failure is `absent`.
39
+ * @param {{jobLines: {n: number, text: string}[], texts: Record<string, string>, provider?: any, maxTokens?: number, ceilingUsd?: number|null,
40
+ * callTimeoutMs?: number, onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string, raw?: string}) => void,
41
+ * createLoop?: Function, rates?: {in: number, out: number}|null, callOptions?: Record<string, any>, sleep?: (ms: number) => Promise<void>}} o
42
+ * @returns {Promise<{state: 'fits'|'ask'|'absent', asks: FitAsk[], notFound: {line: number, ask: string}[], reason: string|null, cause: string|null,
43
+ * calls: {label: string, costUsd: number|null, unpricedRounds: number}[], budgetStop: 'cap-halt'|'pricing-red'|null}>}
44
+ */
45
+ export function runFitCheck({ jobLines, texts, provider, maxTokens, ceilingUsd, callTimeoutMs, onCall, createLoop, rates, callOptions, sleep, }: {
46
+ jobLines: {
47
+ n: number;
48
+ text: string;
49
+ }[];
50
+ texts: Record<string, string>;
51
+ provider?: any;
52
+ maxTokens?: number;
53
+ ceilingUsd?: number | null;
54
+ callTimeoutMs?: number;
55
+ onCall?: (call: {
56
+ label: string;
57
+ costUsd: number | null;
58
+ unpricedRounds: number;
59
+ thinkingStatus?: string;
60
+ raw?: string;
61
+ }) => void;
62
+ createLoop?: Function;
63
+ rates?: {
64
+ in: number;
65
+ out: number;
66
+ } | null;
67
+ callOptions?: Record<string, any>;
68
+ sleep?: (ms: number) => Promise<void>;
69
+ }): Promise<{
70
+ state: "fits" | "ask" | "absent";
71
+ asks: FitAsk[];
72
+ notFound: {
73
+ line: number;
74
+ ask: string;
75
+ }[];
76
+ reason: string | null;
77
+ cause: string | null;
78
+ calls: {
79
+ label: string;
80
+ costUsd: number | null;
81
+ unpricedRounds: number;
82
+ }[];
83
+ budgetStop: "cap-halt" | "pricing-red" | null;
84
+ }>;
85
+ /** the label of the one paid call, as the drafting book and the progress line see it */
86
+ export const FIT_CALL_LABEL: "fit-check";
87
+ /** what the chat says when the check did not finish (drafting goes on) */
88
+ export const FIT_ABSENT_NOTE: "The fit check did not finish; drafting goes on.";
89
+ /** the answer that keeps drafting after the fit question (any case, a closing full stop allowed) */
90
+ export const FIT_GO_ON: "go on";
91
+ export function isGoOn(text: unknown): boolean;
92
+ export const FIT_SYSTEM: "You check whether saved web pages show what a job asks for. You never invent words: every ask must be copied from its job line and every quote must be copied from the page, character for character.";
93
+ export function norm(s: unknown): string;
94
+ export function notFoundList(notFound: {
95
+ line: number;
96
+ ask: string;
97
+ }[]): string;
98
+ export function fitQuestionText({ found, total, notFound }: {
99
+ found: number;
100
+ total: number;
101
+ notFound: {
102
+ line: number;
103
+ ask: string;
104
+ }[];
105
+ }): string;
106
+ export type FitAsk = {
107
+ line: number;
108
+ ask: string;
109
+ found: boolean;
110
+ quote: string | null;
111
+ madeUp: boolean;
112
+ ignored?: "number";
113
+ };
@@ -148,6 +148,12 @@ export function lookPrompt({ jobLines, labels, lists }: {
148
148
  labels: string[];
149
149
  lists: Record<string, string>;
150
150
  }): string;
151
+ /**
152
+ * A fact the page-word map kept (src/pagerules.js `KeptFact`), as the look report carries it: a phrase fact holds its alternative
153
+ * phrases; any other holds its labels (the first is the primary), and a money fact its units and currencies.
154
+ * @param {import('./pagerules.js').KeptFact} k @param {string} source @returns {LookFact}
155
+ */
156
+ export function lookFactOf(k: import("./pagerules.js").KeptFact, source: string): LookFact;
151
157
  /**
152
158
  * Turn the AI's raw report into a VERIFIED one, by code, at $0. A fact is carried only when: its shape is a real shape, its source is
153
159
  * a real input, and its label is ON THE LIST for that input and shape (an id is resolved to its exact words). Anything else is moved
@@ -207,6 +213,9 @@ export const LOOK_REPORT_TOOL: "report_look";
207
213
  /** how many hits one search returns at most, and how long a snippet or a read line is */
208
214
  export const LOOK_MAX_HITS: 40;
209
215
  export { LABEL_SHAPES };
216
+ /**
217
+ * `words` / `labels` / `unit` / `currency`: what the page-word map kept (idea A core)
218
+ */
210
219
  export type LookFact = {
211
220
  line: number;
212
221
  name: string;
@@ -220,6 +229,11 @@ export type LookFact = {
220
229
  rule?: any;
221
230
  aiProposal?: boolean;
222
231
  forRule?: number;
232
+ reading?: import("./pagerules.js").Reading;
233
+ words?: string[];
234
+ labels?: string[];
235
+ unit?: string[];
236
+ currency?: string[];
223
237
  };
224
238
  export type LookNotFound = {
225
239
  line: number;
@@ -234,5 +248,6 @@ export type LookReport = {
234
248
  text?: string;
235
249
  }[];
236
250
  notFound: LookNotFound[];
251
+ dropped?: import("./pagerules.js").Dropped[];
237
252
  };
238
253
  import { LABEL_SHAPES } from './labelscan.js';
package/types/kinds.d.ts CHANGED
@@ -645,6 +645,11 @@ export type Ctx = {
645
645
  timeoutMs?: number;
646
646
  deadlineMs?: number;
647
647
  };
648
+ onRubricEvidence?: (e: {
649
+ step: number;
650
+ evidence: any[];
651
+ evidenceTotal?: number;
652
+ }) => void;
648
653
  onStage?: (row: {
649
654
  stage: string | null;
650
655
  kind: string | null;
@@ -0,0 +1,39 @@
1
+ /** @param {Dropped} d @returns {string} the plain sentence for one drop */
2
+ export function dropSentence(d: Dropped): string;
3
+ /**
4
+ * One row per fact that lost something: the sentences (one per dropped word, then the fact's own), and whether the whole fact went.
5
+ * @param {Dropped[]|undefined|null} dropped
6
+ * @returns {{line: number, rule?: number, fact: string, full: boolean, sentences: string[]}[]}
7
+ */
8
+ export function dropRows(dropped: Dropped[] | undefined | null): {
9
+ line: number;
10
+ rule?: number;
11
+ fact: string;
12
+ full: boolean;
13
+ sentences: string[];
14
+ }[];
15
+ /**
16
+ * A row as one Not-checked line, in the person's own words: `line N: "<the ~ rule as written, or the fact's name>" — <sentences>`.
17
+ * @param {{line: number, rule?: number, fact: string, sentences: string[]}} row @param {any[]|null|undefined} jobLines @param {(l: any) => string[]} rulesOf
18
+ * @returns {string}
19
+ */
20
+ export function dropLine(row: {
21
+ line: number;
22
+ rule?: number;
23
+ fact: string;
24
+ sentences: string[];
25
+ }, jobLines: any[] | null | undefined, rulesOf: (l: any) => string[]): string;
26
+ /**
27
+ * An `s1` drop (a second fact that would read the same value as one already kept) is a NOTE when that OTHER fact is still signed: the guardrail
28
+ * is checked by it. With the other fact not signed, or no signed list given, the sentence stays Not checked. Every sentence of a row must qualify.
29
+ * @param {string} text one Not-checked line @param {string[]|null|undefined} [signed] the names of the facts the plan signs
30
+ * @returns {boolean} true when the line only says which written forms of a still-checked fact were not used
31
+ */
32
+ export function isReadNote(text: string, signed?: string[] | null | undefined): boolean;
33
+ /** @param {string[]|null|undefined} items @param {string[]|null|undefined} [signed] names of the signed facts (see isReadNote)
34
+ * @returns {{notChecked: string[], readNotes: string[]}} the lines split: what is left unchecked, and how the page was read */
35
+ export function splitReadNotes(items: string[] | null | undefined, signed?: string[] | null | undefined): {
36
+ notChecked: string[];
37
+ readNotes: string[];
38
+ };
39
+ export type Dropped = import("./pagerules.js").Dropped;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The result lines of a kept money fact: with a label, every page line where a kept label is followed by a number (one sign between allowed);
3
+ * with no label (U1 moved it to a unit, e.g. "€533 total"), every line where a number is followed by a kept unit.
4
+ * @param {string} text @param {KeptFact} k @returns {{quote: string, value: string}[]}
5
+ */
6
+ export function moneyResultLines(text: string, k: KeptFact): {
7
+ quote: string;
8
+ value: string;
9
+ }[];
10
+ /**
11
+ * Dry-read every kept money fact; drop (as `reads-no-result`) the ones whose forms read no result line. Other facts pass through untouched.
12
+ * @param {string} text the frozen page text @param {KeptFact[]} facts what applyPageRules kept
13
+ * @returns {Promise<{facts: KeptFact[], dropped: Dropped[], reads: Record<string, number>, results: Record<string, number>}>}
14
+ * `reads` / `results` = result lines the checker read / lines found, per fact name (for tests and reports; no verdict derives from the numbers)
15
+ */
16
+ export function dryReadMoney(text: string, facts: KeptFact[]): Promise<{
17
+ facts: KeptFact[];
18
+ dropped: Dropped[];
19
+ reads: Record<string, number>;
20
+ results: Record<string, number>;
21
+ }>;
22
+ /**
23
+ * The same "can it read" for PHRASE facts (hamr ruled 3a, run m10-walk-flights-3: "Rome" was on the page only in "Track prices from Madrid to
24
+ * Rome" switches, in none of the 18 result lines, so no result could ever say it). A kept phrase fact is read against the page's RESULT LINES =
25
+ * the lines the job's kept money facts read (`moneyResultLines`), each graded as an honest claim by the real `gradeStep` (quote = that line;
26
+ * a phrase has no value to write). A phrase that reads no result line is dropped whole as `phrase-no-result` and shows under Not checked.
27
+ * With no kept money fact the result lines are unknown, so phrase facts pass through untouched (nothing is guessed about what a result is).
28
+ * @param {string} text the frozen page text @param {KeptFact[]} facts what the money dry read kept
29
+ * @returns {Promise<{facts: KeptFact[], dropped: Dropped[], reads: Record<string, number>}>} `reads` = result lines that said the phrase, per fact name
30
+ */
31
+ export function dryReadPhrases(text: string, facts: KeptFact[]): Promise<{
32
+ facts: KeptFact[];
33
+ dropped: Dropped[];
34
+ reads: Record<string, number>;
35
+ }>;
36
+ export type KeptFact = import("./pagerules.js").KeptFact;
37
+ export type Dropped = import("./pagerules.js").Dropped;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * What the AI is told, for ONE page input. Ported from the POC prompt: the job, the `~` rules, "a result is one item", the answer form
3
+ * and the shape meanings.
4
+ * @param {{jobLines: {n: number, text: string, rule?: string, rules?: string[]}[], label: string, text: string}} o
5
+ * @returns {string}
6
+ */
7
+ export function mapPrompt({ jobLines, label, text }: {
8
+ jobLines: {
9
+ n: number;
10
+ text: string;
11
+ rule?: string;
12
+ rules?: string[];
13
+ }[];
14
+ label: string;
15
+ text: string;
16
+ }): string;
17
+ /**
18
+ * Read the AI's answer: ONE JSON object of the answer form, and nothing else. No repair (hamr's ruling): text that is not JSON, JSON of
19
+ * another form, or a fact whose shape or lists are not of the form is a malformed answer.
20
+ * @param {unknown} text @returns {{ok: true, mapping: import('./pagerules.js').PageMap}|{ok: false}}
21
+ */
22
+ export function parseMapAnswer(text: unknown): {
23
+ ok: true;
24
+ mapping: import("./pagerules.js").PageMap;
25
+ } | {
26
+ ok: false;
27
+ };
28
+ /**
29
+ * Run the map: one call per page input.
30
+ * @param {{texts: Record<string, string>, inputs: {n: number, label: string}[], jobLines: {n: number, text: string, rule?: string, rules?: string[]}[],
31
+ * provider?: any, maxTokens?: number, ceilingUsd?: number|null, callTimeoutMs?: number,
32
+ * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string, raw?: string}) => void,
33
+ * createLoop?: (o: {system: string, onLlmResult: any, provider: any, rates?: {in: number, out: number}}) => any,
34
+ * rates?: {in: number, out: number}|null, callOptions?: Record<string, any>, sleep?: (ms: number) => Promise<void>, prove?: Function}} o
35
+ * @returns {Promise<{state: 'PRESENT'|'ABSENT', facts: any, reason: string|null, cause: string|null, meta: any, raw: string,
36
+ * raws: ReturnType<typeof scrubRaw>[], calls: {label: string, costUsd: number|null, unpricedRounds: number}[],
37
+ * budgetStop: 'cap-halt'|'pricing-red'|null}>}
38
+ */
39
+ export function runPageMap({ texts, inputs, jobLines, provider, maxTokens, ceilingUsd, callTimeoutMs, onCall, createLoop, rates, callOptions, sleep, }: {
40
+ texts: Record<string, string>;
41
+ inputs: {
42
+ n: number;
43
+ label: string;
44
+ }[];
45
+ jobLines: {
46
+ n: number;
47
+ text: string;
48
+ rule?: string;
49
+ rules?: string[];
50
+ }[];
51
+ provider?: any;
52
+ maxTokens?: number;
53
+ ceilingUsd?: number | null;
54
+ callTimeoutMs?: number;
55
+ onCall?: (call: {
56
+ label: string;
57
+ costUsd: number | null;
58
+ unpricedRounds: number;
59
+ thinkingStatus?: string;
60
+ raw?: string;
61
+ }) => void;
62
+ createLoop?: (o: {
63
+ system: string;
64
+ onLlmResult: any;
65
+ provider: any;
66
+ rates?: {
67
+ in: number;
68
+ out: number;
69
+ };
70
+ }) => any;
71
+ rates?: {
72
+ in: number;
73
+ out: number;
74
+ } | null;
75
+ callOptions?: Record<string, any>;
76
+ sleep?: (ms: number) => Promise<void>;
77
+ prove?: Function;
78
+ }): Promise<{
79
+ state: "PRESENT" | "ABSENT";
80
+ facts: any;
81
+ reason: string | null;
82
+ cause: string | null;
83
+ meta: any;
84
+ raw: string;
85
+ raws: ReturnType<typeof scrubRaw>[];
86
+ calls: {
87
+ label: string;
88
+ costUsd: number | null;
89
+ unpricedRounds: number;
90
+ }[];
91
+ budgetStop: "cap-halt" | "pricing-red" | null;
92
+ }>;
93
+ /** @param {unknown} q a recorded question @returns {string|null} the reading a reading ask proposed, or null when it was another kind of question */
94
+ export function readingOfQuestion(q: unknown): string | null;
95
+ /**
96
+ * PRD v1.107 Part 5b ("the look reads, code asks"): the early asks CODE forces before any paid drafting, from the look's own result. Every `~` rule is
97
+ * one of three kinds:
98
+ * - PAGE: a kept page word carries it (a kept fact with `forRule` and no `reading`): no ask.
99
+ * - READING: a kept fact carries it with a `reading` (a limit the page cannot hold, e.g. "not a red-eye" -> departure time between 06:00 and 21:00).
100
+ * Asked only when a bound of the reading is NOT in the words the person wrote (the same test as `rubric-fact-unmarked`, `boundInWords`); a
101
+ * bound they wrote themselves is theirs and nothing is asked. `agree` writes the reading as a `~` rule in plain words (the answer text is
102
+ * `reading`), so the drafter is told it and the fact signs plain.
103
+ * - CAN'T CHECK: no kept fact carries it (PRD v1.106 Part 5 Q3 T2). `drop it` leaves it Not checked.
104
+ * One ask per rule text on a line; readings first, then can't-check asks, each in line order. The caller applies the machine's cap and the answer gate.
105
+ * @param {{facts?: {line: number, forRule?: number, name?: string, shape?: string, unit?: string[], currency?: string[], reading?: import('./pagerules.js').Reading}[]}|null|undefined} look
106
+ * @param {{n: number, text?: string, rule?: string, rules?: string[]}[]|null|undefined} jobLines
107
+ * @returns {LookAsk[]}
108
+ */
109
+ export function askableFromLook(look: {
110
+ facts?: {
111
+ line: number;
112
+ forRule?: number;
113
+ name?: string;
114
+ shape?: string;
115
+ unit?: string[];
116
+ currency?: string[];
117
+ reading?: import("./pagerules.js").Reading;
118
+ }[];
119
+ } | null | undefined, jobLines: {
120
+ n: number;
121
+ text?: string;
122
+ rule?: string;
123
+ rules?: string[];
124
+ }[] | null | undefined): LookAsk[];
125
+ /**
126
+ * PRD v1.107 Part 5b: more asks than the machine's cap means the job needs a rewrite, not a questionnaire. The code-owned stop words list EVERY ask and
127
+ * its reading as the suggestion (no extra paid call). A secret in a rule is masked.
128
+ * @param {LookAsk[]} asks @returns {string}
129
+ */
130
+ export function tooManyAsksText(asks: LookAsk[]): string;
131
+ /** the label of the one paid call, as the drafting book and the progress line see it */
132
+ export const MAP_CALL_LABEL: "page-map";
133
+ /** the shapes the map may name; a `place` is read by code as a phrase */
134
+ export const MAP_SHAPES: readonly string[];
135
+ export const MAP_SYSTEM: "You map a person's job to the exact words of ONE saved web page. You never grade and never invent words: every string you return must be copied from the page text, character for character.";
136
+ /** the plain-word stop when the answer was not readable twice (no Resume: a draft is not a run) */
137
+ export const MAP_UNREADABLE: "The AI's answer about the page was not readable twice. Nothing was signed.";
138
+ /** the answer that drops a `~` rule the page cannot check: the person types it (any case, a closing full stop allowed) */
139
+ export const DROP_IT: "drop it";
140
+ export function isDropIt(text: unknown): boolean;
141
+ export function isAgree(text: unknown): boolean;
142
+ /**
143
+ * `reading` = the plain words an `agree` writes as a `~` rule (a reading ask only)
144
+ */
145
+ export type LookAsk = {
146
+ line: number;
147
+ rule: number;
148
+ source: "look";
149
+ question: string;
150
+ ask: "reading" | "cant-check";
151
+ ruleText: string;
152
+ reading?: string;
153
+ };
154
+ import { scrubRaw } from './text.js';