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.
- package/CHANGELOG.md +48 -0
- package/bareloop.context.md +34 -7
- package/package.json +1 -1
- package/src/attemptdiff.js +116 -0
- package/src/authorreadout.js +55 -14
- package/src/authorscout.js +1 -1
- package/src/bundle.js +90 -6
- package/src/close-integrity.js +39 -5
- package/src/config.js +5 -2
- package/src/inputlook.js +1 -47
- package/src/judged.js +3 -3
- package/src/labelscan.js +13 -4
- package/src/ladder.js +2 -2
- package/src/learn.js +943 -0
- package/src/learnstore.js +154 -0
- package/src/modellist.js +1 -1
- package/src/pagedry.js +2 -3
- package/src/pagefetch.js +2 -1
- package/src/pagerules.js +27 -4
- package/src/panel/authorroutes.js +4 -3
- package/src/panel/authorsession.js +5 -7
- package/src/panel/index.html +108 -0
- package/src/panel/schedulestart.js +5 -0
- package/src/panel/server.js +16 -134
- package/src/panel/status.js +11 -0
- package/src/planrun.js +40 -6
- package/src/providerrows.js +3 -3
- package/src/run.js +4 -2
- package/src/schedulewords.js +1 -1
- package/src/source.js +1 -1
- package/src/stagewords.js +145 -0
- package/src/text.js +4 -2
- package/src/userrun.js +28 -0
- package/src/validate.js +10 -5
- package/src/worktree.js +8 -2
- package/types/attemptdiff.d.ts +38 -0
- package/types/authorreadout.d.ts +7 -0
- package/types/config.d.ts +1 -1
- package/types/inputlook.d.ts +0 -23
- package/types/labelscan.d.ts +6 -0
- package/types/learn.d.ts +575 -0
- package/types/learnstore.d.ts +107 -0
- package/types/pagerules.d.ts +7 -1
- package/types/panel/status.d.ts +8 -0
- package/types/planrun.d.ts +13 -3
- package/types/reuse.d.ts +1 -1
- package/types/run.d.ts +8 -2
- package/types/stagewords.d.ts +91 -0
package/types/learn.d.ts
ADDED
|
@@ -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;
|