bareloop 0.45.0 → 0.46.1

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 (48) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/bareloop.context.md +34 -7
  3. package/package.json +1 -1
  4. package/src/attemptdiff.js +116 -0
  5. package/src/authorreadout.js +55 -14
  6. package/src/authorscout.js +1 -1
  7. package/src/bundle.js +90 -6
  8. package/src/close-integrity.js +39 -5
  9. package/src/config.js +5 -2
  10. package/src/inputlook.js +1 -47
  11. package/src/judged.js +3 -3
  12. package/src/labelscan.js +13 -4
  13. package/src/ladder.js +2 -2
  14. package/src/learn.js +943 -0
  15. package/src/learnstore.js +154 -0
  16. package/src/modellist.js +1 -1
  17. package/src/pagedry.js +2 -3
  18. package/src/pagefetch.js +2 -1
  19. package/src/pagerules.js +27 -4
  20. package/src/panel/authorroutes.js +4 -3
  21. package/src/panel/authorsession.js +5 -7
  22. package/src/panel/index.html +108 -0
  23. package/src/panel/schedulestart.js +5 -0
  24. package/src/panel/server.js +16 -134
  25. package/src/panel/status.js +11 -0
  26. package/src/planrun.js +40 -6
  27. package/src/providerrows.js +3 -3
  28. package/src/run.js +4 -2
  29. package/src/schedulewords.js +1 -1
  30. package/src/source.js +1 -1
  31. package/src/stagewords.js +145 -0
  32. package/src/text.js +4 -2
  33. package/src/userrun.js +28 -0
  34. package/src/validate.js +10 -5
  35. package/src/worktree.js +8 -2
  36. package/types/attemptdiff.d.ts +38 -0
  37. package/types/authorreadout.d.ts +7 -0
  38. package/types/config.d.ts +1 -1
  39. package/types/inputlook.d.ts +0 -23
  40. package/types/labelscan.d.ts +6 -0
  41. package/types/learn.d.ts +575 -0
  42. package/types/learnstore.d.ts +107 -0
  43. package/types/pagerules.d.ts +7 -1
  44. package/types/panel/status.d.ts +8 -0
  45. package/types/planrun.d.ts +13 -3
  46. package/types/reuse.d.ts +1 -1
  47. package/types/run.d.ts +8 -2
  48. package/types/stagewords.d.ts +91 -0
@@ -0,0 +1,575 @@
1
+ /**
2
+ * Mask secrets and strip the working-tree prefix from a text. Pure.
3
+ * @param {string} text @param {string|null} workdir
4
+ * @returns {string}
5
+ */
6
+ export function cleanText(text: string, workdir: string | null): string;
7
+ /**
8
+ * A card/audit path made relative to the run's tree, or null when it is not inside it. The workdir is the
9
+ * authority; the patient-prefix shapes are the fallback for a spine read without one.
10
+ * @param {string} abs @param {string|null} workdir
11
+ * @returns {string|null}
12
+ */
13
+ export function relPath(abs: string, workdir: string | null): string | null;
14
+ /**
15
+ * Cut at a word / sentence edge, never mid-word where avoidable. Whitespace collapsed.
16
+ * @param {unknown} text @param {number} n @returns {string}
17
+ */
18
+ export function cutText(text: unknown, n: number): string;
19
+ /**
20
+ * Deep-redact every string of a plain JSON value through the ONE secret inventory (a secret in a person's repo must
21
+ * not enter an append-only record). Pure; returns a new value.
22
+ * @template T @param {T} v @returns {T}
23
+ */
24
+ export function redactDeep<T>(v: T): T;
25
+ /**
26
+ * The flagged text of ONE quote line (wrapper stripped), or null when the line is not a flagged line.
27
+ * Flagged lines: `... added <id> - <text>` and `close: [<id>] <file>:<line>: <text>` (id in the allowed list), and
28
+ * lines with `error TS<digits>:` or `error:`.
29
+ * @param {string} line @returns {string|null}
30
+ */
31
+ export function flaggedText(line: string): string | null;
32
+ /**
33
+ * The error-kind set of a quote: TS codes; `[id]` of `added <id>` and `close: [id]` lines; a mypy trailing bracket
34
+ * code; empty -> `other`. Also the files named, the first symbol, and whether the card is CHECK-SUSPECT.
35
+ * @param {string} text the (path-stripped) quote @param {string} [fullGap] the whole gap, for the suspect read
36
+ * @returns {{kinds: string[], files: string[], symbol: string|null, suspect: boolean}}
37
+ */
38
+ export function errorKinds(text: string, fullGap?: string): {
39
+ kinds: string[];
40
+ files: string[];
41
+ symbol: string | null;
42
+ suspect: boolean;
43
+ };
44
+ /**
45
+ * The token set of a quote's flagged lines: lowercase runs of `[a-z0-9_]` of length >= 2, union over flagged lines.
46
+ * File paths, line numbers, `added <id> -`, `error TS<n>:` and the trailing mypy code are already stripped.
47
+ * @param {string} text @returns {string[]} sorted
48
+ */
49
+ export function flaggedTokens(text: string): string[];
50
+ /**
51
+ * @typedef {object} Card
52
+ * @property {string} id `<runid>:<check>:<fixed|stuck>`
53
+ * @property {string} runid
54
+ * @property {string} at the run's end (ISO)
55
+ * @property {string} date YYYY-MM-DD of `at`
56
+ * @property {string} job the workflow key
57
+ * @property {string|null} model
58
+ * @property {string} check the check / close stage name
59
+ * @property {'FIXED'|'NEVER_FIXED'} kind
60
+ * @property {string} goal Job (as written), cut
61
+ * @property {string|null} stepId
62
+ * @property {string|null} stepAction Step (as planned), cut
63
+ * @property {string|null} meaning the check's own catalogue sentence (code-owned)
64
+ * @property {string[]} said It said: <= 3 lines, paths and prefixes stripped
65
+ * @property {number} saidMore lines left out
66
+ * @property {string[]} kinds error kinds, sorted; ['other'] when none
67
+ * @property {string[]} files files the quote names, sorted
68
+ * @property {string|null} symbol
69
+ * @property {string[]} tokens the flagged-text token set, sorted
70
+ * @property {string|null} nextCheck the next failing check after this moment, if any
71
+ * @property {string} ended how the run ended (job-end outcome, or its escalation word)
72
+ * @property {string[]} fixTouched files written by the passing try (the try that followed the red, for NEVER FIXED)
73
+ * @property {{files: {path: string, diff: string, truncated: boolean, cutLines?: number}[], binary: string[], truncated: boolean}|null} whatChanged
74
+ * the passing try's attempt-diff (FIXED only), as the spine capped it
75
+ * @property {string} key the one-line machine key
76
+ */
77
+ /**
78
+ * The one-line machine key of a card. Line numbers never appear in it.
79
+ * @param {{id: string, job: string, check: string, kinds: string[], files: string[], model: string|null, runid: string, date: string}} c
80
+ * @returns {string}
81
+ */
82
+ export function cardKey(c: {
83
+ id: string;
84
+ job: string;
85
+ check: string;
86
+ kinds: string[];
87
+ files: string[];
88
+ model: string | null;
89
+ runid: string;
90
+ date: string;
91
+ }): string;
92
+ /**
93
+ * The lines of a gap a card quotes: lines that carry a path:line or an error code come first, accumulated up to
94
+ * ~250 characters (the cut the fold was fitted on). Pure.
95
+ * @param {string} gap @returns {string[]}
96
+ */
97
+ export function quoteLines(gap: string): string[];
98
+ /**
99
+ * @typedef {object} CardBuild
100
+ * @property {Card[]} cards foldable cards (FIXED and NEVER FIXED), suspect and too-thin removed
101
+ * @property {Card[]} suspect CHECK-SUSPECT cards: listed, never folded
102
+ * @property {number} thin TOO-THIN count: no error kind and no fix files
103
+ * @property {string|null} noise why the whole run was set aside (provider-red, close-crashed, worker-crash, no-close-spoke, no-job-end), else null
104
+ */
105
+ /**
106
+ * Turn one run's record into cards. Every field is COPIED from the spine / gate audit; nothing is generated.
107
+ *
108
+ * Candidates: FIXED = a check red, then the SAME check passes later in the same run; NEVER FIXED = a check still red
109
+ * at run end. At most one of each per check per run (the last red before the pass; the last red). Excluded, counted:
110
+ * noise runs, CHECK-SUSPECT, TOO-THIN.
111
+ *
112
+ * Written files come from `attempt-diff` events (rubric `output/steps/<n>.json` included: the diff force-adds the
113
+ * fence) and the REAL gate audit's allow write/edit rows relative to `workdir` — never `git status`, and never a
114
+ * reader that drops a path it cannot place under a known patient prefix (the POC defect: a rubric job's writes read
115
+ * as none).
116
+ * `questions` is the check catalogue (stage name -> plain question, src/stagewords.js) the caller resolved from the signed
117
+ * spec; a check with none reads `(not in the record)`.
118
+ * @param {{runid: string, events: any[], audit?: any[]|null, workdir?: string|null, spec?: any, model?: string|null, questions?: Map<string, string|null>}} o
119
+ * @returns {CardBuild}
120
+ */
121
+ export function buildCards({ runid, events, audit, workdir, spec, model, questions }: {
122
+ runid: string;
123
+ events: any[];
124
+ audit?: any[] | null;
125
+ workdir?: string | null;
126
+ spec?: any;
127
+ model?: string | null;
128
+ questions?: Map<string, string | null>;
129
+ }): CardBuild;
130
+ /** @param {{kinds: string[]}} a @param {{kinds: string[]}} b */
131
+ export function sharedKind(a: {
132
+ kinds: string[];
133
+ }, b: {
134
+ kinds: string[];
135
+ }): boolean;
136
+ /**
137
+ * Do two cards agree on kind? A shared error code / kind, OR (RULED 2026-10-23 R1) both carry NO code (`other`) and both
138
+ * have a non-empty flagged-text token set, so the text rule alone decides. A coded card never matches a code-less one.
139
+ * The 60% was fitted on cards WITH codes; the code-less use is UNTESTED on real data.
140
+ * @param {{kinds: string[], tokens: string[]}} a @param {{kinds: string[], tokens: string[]}} b
141
+ */
142
+ export function kindAgrees(a: {
143
+ kinds: string[];
144
+ tokens: string[];
145
+ }, b: {
146
+ kinds: string[];
147
+ tokens: string[];
148
+ }): boolean;
149
+ /** Jaccard of two token arrays; an empty side scores 0. @param {string[]} a @param {string[]} b */
150
+ export function jaccard(a: string[], b: string[]): number;
151
+ /**
152
+ * Are two cards one lesson? Same check, kinds that agree ({@link kindAgrees}: a shared code, or both code-less with text
153
+ * on both sides) and Jaccard >= 60% on flagged-text tokens. `sameJob: false` drops the job test (RULING 1: the cross-job rule for climbing).
154
+ * @param {Card} a @param {Card} b @param {{sameJob?: boolean}} [o]
155
+ * @returns {{match: boolean, score: number}}
156
+ */
157
+ export function cardsMatch(a: Card, b: Card, { sameJob }?: {
158
+ sameJob?: boolean;
159
+ }): {
160
+ match: boolean;
161
+ score: number;
162
+ };
163
+ /**
164
+ * Group cards into lessons: single-link components over eligible pairs; every pair INSIDE a component is re-checked
165
+ * for a shared kind, and a component with any pair lacking one splits back to singletons.
166
+ * @param {Card[]} cards
167
+ * @returns {Card[][]} groups, in first-card order; each group in input order
168
+ */
169
+ export function foldCards(cards: Card[]): Card[][];
170
+ /**
171
+ * @typedef {object} Row
172
+ * @property {string} id
173
+ * @property {string} lesson ONE sentence, template-only (see {@link lessonOf})
174
+ * @property {number} count runs seen; STARTS AT 1
175
+ * @property {Card} sample the LATEST card
176
+ * @property {Record<string, number>} models per-model counts (read-only information; never a filter)
177
+ * @property {string} workflow
178
+ * @property {string|null} place
179
+ * @property {'workflow'|'place'|'user'} level STORED as 'workflow' only; the level a reader sees is derived by {@link readableRows} from the rows memory still shows (an older file's stored value is ignored and reset on the next write)
180
+ * @property {'deterministic'|'rubric'} trust
181
+ * @property {string[]} runIds
182
+ * @property {string} firstSeen
183
+ * @property {string} lastSeen
184
+ * @property {string|null} specHash the signed hash the lesson was last seen under (a workflow row of another hash is never matched or read)
185
+ * @property {string} key check + sorted kinds (job-independent); information only since R2 removed the check-wide set-aside
186
+ */
187
+ /**
188
+ * @typedef {object} Pending
189
+ * @property {string} runid @property {string} at @property {string} workflow @property {string|null} place
190
+ * @property {string|null} specHash @property {Card[]} cards
191
+ */
192
+ /**
193
+ * @typedef {object} Ledger
194
+ * @property {1} version
195
+ * @property {Row[]} rows
196
+ * @property {Pending[]} pending rubric cards held until a human Accept (NOT counted, NOT readable)
197
+ * @property {Card[]} suspect CHECK-SUSPECT cards, listed, newest last
198
+ * @property {Record<string, {at: string, noise: string|null, cards: number, suspect: number, thin: number}>} health per run (idempotent by run id)
199
+ */
200
+ /** @returns {Ledger} */
201
+ export function emptyLedger(): Ledger;
202
+ /**
203
+ * ONE sentence, rendered by code from the card's own fields (RULING 5, template-only).
204
+ * @param {Card} c @returns {string}
205
+ */
206
+ export function lessonOf(c: Card): string;
207
+ /**
208
+ * Fold ONE run's cards into the ledger. Pure: returns a new ledger.
209
+ *
210
+ * +1 per RUN per row: the run's own cards are folded into lessons first, and a row whose `runIds` already holds the
211
+ * run id is never counted twice (a resumed run, one id, never double counts). A matching lesson replaces the row's
212
+ * sample, bumps the model count and appends the run id (a row last seen 56+ days ago counts +1 and is read again);
213
+ * otherwise it opens a new row at count 1. NEVER FIXED cards count like FIXED ones (RULING 3). A workflow row of
214
+ * another signed hash is never matched here: the new version opens its own rows. Nothing is ever deleted or stamped.
215
+ * @param {Ledger} ledger
216
+ * @param {{runid: string, cards: Card[], workflow: string, place: string|null, specHash: string|null, trust: 'deterministic'|'rubric', now: Date}} o
217
+ * @returns {Ledger}
218
+ */
219
+ export function countLedger(ledger: Ledger, { runid, cards, workflow, place, specHash, trust, now }: {
220
+ runid: string;
221
+ cards: Card[];
222
+ workflow: string;
223
+ place: string | null;
224
+ specHash: string | null;
225
+ trust: "deterministic" | "rubric";
226
+ now: Date;
227
+ }): Ledger;
228
+ /**
229
+ * Climbing (RULING 1): the same lesson across workflows — the section-3 rule with "same job" dropped — seen in 3
230
+ * distinct workflows on one place climbs to `place`; in 3 distinct places, to `user`. Derived from the rows handed in
231
+ * alone (every row starts at `workflow`, a stored level is ignored): {@link readableRows} hands it only the rows memory
232
+ * still shows, so a level drops back when siblings age out. Returns copies; keeps every run id.
233
+ * @param {Row[]} rows @returns {Row[]}
234
+ */
235
+ export function climb(rows: Row[]): Row[];
236
+ /**
237
+ * A rubric run's cards are HELD, not counted, until a human Accept (the existing quarantine is the rule). Replaces
238
+ * an earlier hold for the same run id (a resumed run re-derives its cards). Pure.
239
+ * @param {Ledger} ledger
240
+ * @param {{runid: string, cards: Card[], workflow: string, place: string|null, specHash: string|null, at: string}} o
241
+ * @param {Date} now @returns {Ledger}
242
+ */
243
+ export function holdPending(ledger: Ledger, { runid, cards, workflow, place, specHash, at }: {
244
+ runid: string;
245
+ cards: Card[];
246
+ workflow: string;
247
+ place: string | null;
248
+ specHash: string | null;
249
+ at: string;
250
+ }, now: Date): Ledger;
251
+ /**
252
+ * The human Accept releases a held rubric run: its cards count now (trust `rubric`). Only forward, only here. A hold
253
+ * older than 60 days is dropped uncounted. Idempotent (no hold = nothing to do; run ids stop a double count).
254
+ * @param {Ledger} ledger @param {string} runid @param {Date} now
255
+ * @returns {{ledger: Ledger, released: number}}
256
+ */
257
+ export function releasePending(ledger: Ledger, runid: string, now: Date): {
258
+ ledger: Ledger;
259
+ released: number;
260
+ };
261
+ /**
262
+ * Record the run's health line and CHECK-SUSPECT list (idempotent by run id). Pure.
263
+ * @param {Ledger} ledger @param {string} runid @param {CardBuild} build @param {string} at @param {Date} now
264
+ * @returns {Ledger}
265
+ */
266
+ export function recordHealth(ledger: Ledger, runid: string, build: CardBuild, at: string, now: Date): Ledger;
267
+ /**
268
+ * The +/- content lines of a card's stored attempt-diff, in file order: only lines INSIDE a hunk that start with `+` or
269
+ * `-` (so `diff --git`, `index`, `---`/`+++` headers, `@@` lines, context and `\ No newline` lines never appear; a removed
270
+ * line that itself starts with `--` is still content once a hunk has opened). Leading indent after the sign is dropped.
271
+ * @param {Card['whatChanged']} wc @returns {string[]}
272
+ */
273
+ export function changedLines(wc: Card["whatChanged"]): string[];
274
+ /**
275
+ * The line "It said:" shows (R5): the first REAL flagged source/error line of the stored quote (the same lines the fold's
276
+ * flagged-text rule reads, with or without the `close:` wrapper the card already stripped), else the first line when no
277
+ * line is flagged (a summary such as `SPAWNER red: 3 suppression(s) added ...` is only the fallback).
278
+ * @param {string[]} said @returns {string|undefined}
279
+ */
280
+ export function saidLine(said: string[]): string | undefined;
281
+ /**
282
+ * Which rows may be read for this job, in reading order (THE memory filter; the ledger itself keeps every row).
283
+ * Excluded: pending rows, any row last seen more than 56 days ago, any row with no run id, a workflow row of another
284
+ * job or of another signed hash, a place row of another place. The level (this job / this place / all jobs) is
285
+ * derived HERE by {@link climb} over the rows not decayed at `now` only; the stored level is never read.
286
+ * Order: this job's, then this place's, then all-jobs LAST (R2: climbing wins, so an all-jobs hint only uses leftover
287
+ * room); inside each level highest count first (ties: most recent lastSeen, then id), so the order is total and stable.
288
+ * @param {Ledger} ledger @param {{workflow: string, place: string|null, specHash?: string|null, now: Date}} o
289
+ * @returns {Row[]}
290
+ */
291
+ export function readableRows(ledger: Ledger, { workflow, place, specHash, now }: {
292
+ workflow: string;
293
+ place: string | null;
294
+ specHash?: string | null;
295
+ now: Date;
296
+ }): Row[];
297
+ /**
298
+ * THE one render: the block the PLANNER reads at draft and at the first replan. Byte-for-byte reproducible from the
299
+ * ledger as it stood. Stops at 2,000 characters; a hint is added whole or not at all; nothing readable -> empty text.
300
+ * @param {Ledger} ledger @param {{workflow: string, place: string|null, specHash?: string|null, now: Date}} o
301
+ * @returns {{text: string, ids: string[]}}
302
+ */
303
+ export function renderHints(ledger: Ledger, o: {
304
+ workflow: string;
305
+ place: string | null;
306
+ specHash?: string | null;
307
+ now: Date;
308
+ }): {
309
+ text: string;
310
+ ids: string[];
311
+ };
312
+ /**
313
+ * Split a block {@link renderHints} wrote back into its hints. The ONLY reader of that text: it lives beside the
314
+ * writer ({@link hintText}, {@link lessonOf}) and a test renders real blocks and parses them back. A hint it cannot
315
+ * read is returned with what it could read (never thrown, never invented). Pure.
316
+ * @param {string} text @returns {HintParts[]}
317
+ */
318
+ export function parseHintBlock(text: string): HintParts[];
319
+ /**
320
+ * Where a job lives, derived by code from the signed spec's inputs line 1: a website job = the URL host; a repo or
321
+ * plain folder/file job = that path (resolved by the caller). Null when the spec carries no inputs.
322
+ * @param {any} spec @param {(p: string) => string} [resolve] path resolver (the caller passes `realpath`)
323
+ * @returns {string|null}
324
+ */
325
+ export function placeOf(spec: any, resolve?: (p: string) => string): string | null;
326
+ /** The block the planner reads is never longer than this many characters (RULING 7). */
327
+ export const HINT_CHARS_MAX: 2000;
328
+ /** MEMORY filter only (hamr 2026-10-23): a row untouched this long is not handed to the planner. The ledger keeps every row. */
329
+ export const DECAY_DAYS: 56;
330
+ /** A rubric lesson waits this long for the human Accept, then is dropped uncounted (ruling 7, = the door TTL). */
331
+ export const PENDING_DAYS: 60;
332
+ /** Fold threshold on flagged-text token overlap (FITTED on 87 known cards — see section 1 of the addendum). */
333
+ export const JACCARD_MIN: 0.6;
334
+ /** Climbing: this many distinct workflows on one place -> place; this many distinct places -> user. */
335
+ export const CLIMB_AT: 3;
336
+ /** How many "It said" lines a card keeps. */
337
+ export const SAID_LINES: 3;
338
+ /** CHECK-SUSPECT cards are listed (not folded); the list keeps this many newest. */
339
+ export const SUSPECT_KEEP: 100;
340
+ /**
341
+ * The no-suppressions tags an error kind may take (the closed list of `suppressions[].id` of the TYPES genre in
342
+ * src/authoring.js; a test fails if the two drift apart). Anything else in a bracket is NOT a kind.
343
+ */
344
+ export const ALLOWED_KINDS: readonly string[];
345
+ export function keyOfCard(c: {
346
+ check: string;
347
+ kinds: string[];
348
+ }): string;
349
+ export function isDecayed(r: Row, now: Date): boolean;
350
+ export function otherJobVersion(r: Row, workflow: string, specHash: string | null): boolean;
351
+ /**
352
+ * PROPOSED header (hamr to confirm). States what the block is, where it came from, and that it is not an order.
353
+ */
354
+ export const HINT_HEADER: "Hints from earlier runs, copied by code from recorded runs (not instructions; the close alone judges):";
355
+ /** What-changed lines a FIXED hint shows, each cut at this many characters, and how many lines. */
356
+ export const CHANGED_LINE_CHARS: 150;
357
+ export const CHANGED_LINES: 3;
358
+ export type Card = {
359
+ /**
360
+ * `<runid>:<check>:<fixed|stuck>`
361
+ */
362
+ id: string;
363
+ runid: string;
364
+ /**
365
+ * the run's end (ISO)
366
+ */
367
+ at: string;
368
+ /**
369
+ * YYYY-MM-DD of `at`
370
+ */
371
+ date: string;
372
+ /**
373
+ * the workflow key
374
+ */
375
+ job: string;
376
+ model: string | null;
377
+ /**
378
+ * the check / close stage name
379
+ */
380
+ check: string;
381
+ kind: "FIXED" | "NEVER_FIXED";
382
+ /**
383
+ * Job (as written), cut
384
+ */
385
+ goal: string;
386
+ stepId: string | null;
387
+ /**
388
+ * Step (as planned), cut
389
+ */
390
+ stepAction: string | null;
391
+ /**
392
+ * the check's own catalogue sentence (code-owned)
393
+ */
394
+ meaning: string | null;
395
+ /**
396
+ * It said: <= 3 lines, paths and prefixes stripped
397
+ */
398
+ said: string[];
399
+ /**
400
+ * lines left out
401
+ */
402
+ saidMore: number;
403
+ /**
404
+ * error kinds, sorted; ['other'] when none
405
+ */
406
+ kinds: string[];
407
+ /**
408
+ * files the quote names, sorted
409
+ */
410
+ files: string[];
411
+ symbol: string | null;
412
+ /**
413
+ * the flagged-text token set, sorted
414
+ */
415
+ tokens: string[];
416
+ /**
417
+ * the next failing check after this moment, if any
418
+ */
419
+ nextCheck: string | null;
420
+ /**
421
+ * how the run ended (job-end outcome, or its escalation word)
422
+ */
423
+ ended: string;
424
+ /**
425
+ * files written by the passing try (the try that followed the red, for NEVER FIXED)
426
+ */
427
+ fixTouched: string[];
428
+ /**
429
+ * the passing try's attempt-diff (FIXED only), as the spine capped it
430
+ */
431
+ whatChanged: {
432
+ files: {
433
+ path: string;
434
+ diff: string;
435
+ truncated: boolean;
436
+ cutLines?: number;
437
+ }[];
438
+ binary: string[];
439
+ truncated: boolean;
440
+ } | null;
441
+ /**
442
+ * the one-line machine key
443
+ */
444
+ key: string;
445
+ };
446
+ export type CardBuild = {
447
+ /**
448
+ * foldable cards (FIXED and NEVER FIXED), suspect and too-thin removed
449
+ */
450
+ cards: Card[];
451
+ /**
452
+ * CHECK-SUSPECT cards: listed, never folded
453
+ */
454
+ suspect: Card[];
455
+ /**
456
+ * TOO-THIN count: no error kind and no fix files
457
+ */
458
+ thin: number;
459
+ /**
460
+ * why the whole run was set aside (provider-red, close-crashed, worker-crash, no-close-spoke, no-job-end), else null
461
+ */
462
+ noise: string | null;
463
+ };
464
+ export type Row = {
465
+ id: string;
466
+ /**
467
+ * ONE sentence, template-only (see {@link lessonOf})
468
+ */
469
+ lesson: string;
470
+ /**
471
+ * runs seen; STARTS AT 1
472
+ */
473
+ count: number;
474
+ /**
475
+ * the LATEST card
476
+ */
477
+ sample: Card;
478
+ /**
479
+ * per-model counts (read-only information; never a filter)
480
+ */
481
+ models: Record<string, number>;
482
+ workflow: string;
483
+ place: string | null;
484
+ /**
485
+ * STORED as 'workflow' only; the level a reader sees is derived by {@link readableRows} from the rows memory still shows (an older file's stored value is ignored and reset on the next write)
486
+ */
487
+ level: "workflow" | "place" | "user";
488
+ trust: "deterministic" | "rubric";
489
+ runIds: string[];
490
+ firstSeen: string;
491
+ lastSeen: string;
492
+ /**
493
+ * the signed hash the lesson was last seen under (a workflow row of another hash is never matched or read)
494
+ */
495
+ specHash: string | null;
496
+ /**
497
+ * check + sorted kinds (job-independent); information only since R2 removed the check-wide set-aside
498
+ */
499
+ key: string;
500
+ };
501
+ export type Pending = {
502
+ runid: string;
503
+ at: string;
504
+ workflow: string;
505
+ place: string | null;
506
+ specHash: string | null;
507
+ cards: Card[];
508
+ };
509
+ export type Ledger = {
510
+ version: 1;
511
+ rows: Row[];
512
+ /**
513
+ * rubric cards held until a human Accept (NOT counted, NOT readable)
514
+ */
515
+ pending: Pending[];
516
+ /**
517
+ * CHECK-SUSPECT cards, listed, newest last
518
+ */
519
+ suspect: Card[];
520
+ /**
521
+ * per run (idempotent by run id)
522
+ */
523
+ health: Record<string, {
524
+ at: string;
525
+ noise: string | null;
526
+ cards: number;
527
+ suspect: number;
528
+ thin: number;
529
+ }>;
530
+ };
531
+ /**
532
+ * one hint of a rendered block, split back into its fields (for the panel's Audit view)
533
+ */
534
+ export type HintParts = {
535
+ /**
536
+ * the hint's number in the block
537
+ */
538
+ n: number;
539
+ /**
540
+ * 'this job' | 'this place' | 'all jobs' (the words {@link renderHints} wrote)
541
+ */
542
+ level: string;
543
+ /**
544
+ * how many runs saw it
545
+ */
546
+ count: number;
547
+ /**
548
+ * the check's name
549
+ */
550
+ check: string;
551
+ /**
552
+ * the error codes (empty when the hint names none)
553
+ */
554
+ kinds: string[];
555
+ /**
556
+ * true = fixed later in the same run, false = never fixed
557
+ */
558
+ fixed: boolean;
559
+ /**
560
+ * the "It said" line
561
+ */
562
+ said: string | null;
563
+ /**
564
+ * the "Fix touched" text
565
+ */
566
+ touched: string | null;
567
+ /**
568
+ * the shown What-changed lines (+/- lines), without the "(+N more)" line
569
+ */
570
+ changed: string[];
571
+ /**
572
+ * N of "(+N more)", 0 when absent
573
+ */
574
+ moreChanged: number;
575
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * `<home>/learn/ledger.json`.
3
+ * @param {string} [home] test seam; production never passes it
4
+ * @returns {string}
5
+ */
6
+ export function ledgerPath(home?: string): string;
7
+ /**
8
+ * Read the ledger. A missing file is an empty ledger; an unreadable or wrong-shaped one is reported as `problem` (and the
9
+ * writer then refuses to replace it: an append-only record is never silently reset).
10
+ * @param {string} [home]
11
+ * @returns {{ledger: import('./learn.js').Ledger, exists: boolean, problem: string|null}}
12
+ */
13
+ export function readLedger(home?: string): {
14
+ ledger: import("./learn.js").Ledger;
15
+ exists: boolean;
16
+ problem: string | null;
17
+ };
18
+ /**
19
+ * Write the ledger atomically (temp + rename, mode 600), every string masked through the one secret inventory.
20
+ * @param {import('./learn.js').Ledger} ledger @param {string} [home]
21
+ * @returns {string} the file written
22
+ */
23
+ export function writeLedger(ledger: import("./learn.js").Ledger, home?: string): string;
24
+ /**
25
+ * Fold ONE finished run into the ledger on disk: a deterministic job's cards COUNT now; a rubric job's cards are HELD
26
+ * until a human Accept (they are written to `pending`, never counted, never readable). The run's health line is recorded
27
+ * either way. Never throws on a bad ledger file: it reports `problem` and writes nothing.
28
+ * @param {{home?: string, runid: string, build: import('./learn.js').CardBuild, workflow: string, place: string|null, specHash: string|null, trust: 'deterministic'|'rubric', at: string, now?: Date}} o
29
+ * @returns {{wrote: boolean, counted: number, held: number, problem: string|null}}
30
+ */
31
+ export function foldRunIntoLedger({ home, runid, build, workflow, place, specHash, trust, at, now }: {
32
+ home?: string;
33
+ runid: string;
34
+ build: import("./learn.js").CardBuild;
35
+ workflow: string;
36
+ place: string | null;
37
+ specHash: string | null;
38
+ trust: "deterministic" | "rubric";
39
+ at: string;
40
+ now?: Date;
41
+ }): {
42
+ wrote: boolean;
43
+ counted: number;
44
+ held: number;
45
+ problem: string | null;
46
+ };
47
+ /**
48
+ * The human Accept at the review door: release the run's held rubric cards so they count (only forward, only here).
49
+ * @param {{home?: string, runid: string, now?: Date}} o
50
+ * @returns {{released: number, problem: string|null}}
51
+ */
52
+ export function releaseRun({ home, runid, now }: {
53
+ home?: string;
54
+ runid: string;
55
+ now?: Date;
56
+ }): {
57
+ released: number;
58
+ problem: string | null;
59
+ };
60
+ /**
61
+ * The ONE setting: `learningHints` in config.json. Ships OFF (absent = off). OFF still writes the tally ($0) and the planner
62
+ * is simply handed nothing, so an ON/OFF contrast has data at once. An unreadable config reads as OFF.
63
+ * @param {string} [home]
64
+ * @returns {boolean}
65
+ */
66
+ export function learningHintsOn(home?: string): boolean;
67
+ /**
68
+ * THE RUN-END TALLY ($0, code only, no model): turn the finished run's whole record into cards and fold them into the
69
+ * ledger (a rubric job's are held until Accept). Called once from the one engine's run-end block (src/userrun.js
70
+ * `execute`, shared by the panel, the CLI and the bundle runner), inside its own try/catch: it is a record, never a verdict,
71
+ * and can never change a run's outcome, exit code or spend. It runs whether the hints setting is on or off.
72
+ * @param {{home?: string, runid: string, events: any[], auditFiles?: string[], spec: any, specHash: string|null, workdir: string|null, trust: 'deterministic'|'rubric', now?: Date}} o
73
+ * @returns {{wrote: boolean, counted: number, held: number, problem: string|null, noise: string|null, cards: number}}
74
+ */
75
+ export function learnAtRunEnd({ home, runid, events, auditFiles, spec, specHash, workdir, trust, now }: {
76
+ home?: string;
77
+ runid: string;
78
+ events: any[];
79
+ auditFiles?: string[];
80
+ spec: any;
81
+ specHash: string | null;
82
+ workdir: string | null;
83
+ trust: "deterministic" | "rubric";
84
+ now?: Date;
85
+ }): {
86
+ wrote: boolean;
87
+ counted: number;
88
+ held: number;
89
+ problem: string | null;
90
+ noise: string | null;
91
+ cards: number;
92
+ };
93
+ /**
94
+ * The block the planner is handed for this job, made ONCE at run start from the ledger as it stood — empty when the
95
+ * setting is off, when there is no ledger, or when nothing is readable. The one `renderHints` render, never a second.
96
+ * @param {{home?: string, spec: any, specHash: string|null, now?: Date}} o
97
+ * @returns {{text: string, ids: string[]}|null}
98
+ */
99
+ export function hintsForRun({ home, spec, specHash, now }: {
100
+ home?: string;
101
+ spec: any;
102
+ specHash: string | null;
103
+ now?: Date;
104
+ }): {
105
+ text: string;
106
+ ids: string[];
107
+ } | null;