bareloop 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/bareloop.context.md +245 -33
- package/package.json +1 -1
- package/src/authorreadout.js +323 -0
- package/src/authorrun.js +1345 -0
- package/src/behaviour.js +5 -2
- package/src/cli.js +190 -8
- package/src/index.js +13 -1
- package/src/interviewrun.js +842 -0
- package/src/panel/index.html +1910 -0
- package/src/panel/server.js +1527 -0
- package/src/planrun.js +1 -1
- package/src/replay.js +632 -13
- package/src/replayio.js +159 -0
- package/src/reuse.js +1 -1
- package/src/run.js +1 -1
- package/src/runlist.js +281 -0
- package/src/u-patient.js +73 -0
- package/src/u-readout.js +451 -0
- package/src/userrun.js +2299 -0
- package/types/authorreadout.d.ts +172 -0
- package/types/authorrun.d.ts +37 -0
- package/types/behaviour.d.ts +5 -3
- package/types/cli.d.ts +2 -1
- package/types/index.d.ts +2 -1
- package/types/interviewrun.d.ts +46 -0
- package/types/panel/server.d.ts +265 -0
- package/types/planrun.d.ts +2 -2
- package/types/replay.d.ts +104 -6
- package/types/replayio.d.ts +93 -0
- package/types/reuse.d.ts +2 -2
- package/types/run.d.ts +2 -2
- package/types/runlist.d.ts +112 -0
- package/types/u-patient.d.ts +39 -0
- package/types/u-readout.d.ts +247 -0
- package/types/userrun.d.ts +141 -0
package/types/replay.d.ts
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `[startTs, endTs]` (epoch ms; `endTs` possibly `Infinity`) bounding the
|
|
3
|
+
* gate-audit rows that actually belong to ONE run's own spine. A sidecar
|
|
4
|
+
* file can be SHARED across several runs whose spines happen to reuse the
|
|
5
|
+
* same filename (measured directly on a real archived patient —
|
|
6
|
+
* pulselog-person-live-2, run mu2p83go: its sidecar carries rows from 7
|
|
7
|
+
* distinct `run_id`s spanning 06:56Z..13:25Z on one day, but this run's own
|
|
8
|
+
* `job-start`..`job-end` window is 13:19:40Z..13:25:48Z; every row before
|
|
9
|
+
* that window belongs to an earlier, unrelated run sharing the sidecar
|
|
10
|
+
* path). No `step`/`run_id` field on a gate-audit row is a reliable enough
|
|
11
|
+
* key on its own (see this file's header) — bounding by `ts` against this
|
|
12
|
+
* run's own `job-start`/`job-end` is the only honest scope.
|
|
13
|
+
*
|
|
14
|
+
* `startTs` is this run's own `job-start.ts` (`-Infinity` when absent —
|
|
15
|
+
* never a guessed lower bound). `endTs` is `job-end.ts` when a `job-end`
|
|
16
|
+
* was actually reached, else `Infinity` (a died/still-running run — never
|
|
17
|
+
* clamped to the spine's own last record's `ts`, which would wrongly
|
|
18
|
+
* exclude a real tool call the gate logged just AFTER the spine's own last
|
|
19
|
+
* record landed: spine and gate-audit are written by different seams and
|
|
20
|
+
* are not guaranteed to interleave exactly).
|
|
21
|
+
*
|
|
22
|
+
* ONE shared owner for this rule (F195): `replayRun` (this file) uses it to
|
|
23
|
+
* scope every audit row it hands to `runBehaviour`, so `bareloop replay`
|
|
24
|
+
* and every caller of `replayRun`/`replayOne` (including the panel,
|
|
25
|
+
* `src/panel/server.js`) read the same, honest figure — never two places
|
|
26
|
+
* independently re-deriving (and risking drifting from) the same rule.
|
|
27
|
+
* @param {{ts?: string}|null} [jobStart]
|
|
28
|
+
* @param {{ts?: string}|null} [jobEnd]
|
|
29
|
+
* @returns {{startTs: number, endTs: number}}
|
|
30
|
+
*/
|
|
31
|
+
export function auditWindow(jobStart?: {
|
|
32
|
+
ts?: string;
|
|
33
|
+
} | null, jobEnd?: {
|
|
34
|
+
ts?: string;
|
|
35
|
+
} | null): {
|
|
36
|
+
startTs: number;
|
|
37
|
+
endTs: number;
|
|
38
|
+
};
|
|
1
39
|
/**
|
|
2
40
|
* Reconstruct one run's whole story from its spine + gate-audit. Pure: no
|
|
3
41
|
* IO, no spine writes, no verdict. Malformed/non-object entries in either
|
|
@@ -18,16 +56,26 @@
|
|
|
18
56
|
*
|
|
19
57
|
* @param {any[]} spineEvents parsed records from `u-<id>.jsonl`
|
|
20
58
|
* @param {any[]} [auditEvents] parsed records from the sibling `-gate-audit.jsonl`
|
|
21
|
-
* @param {{runId?: string|null}} [opts] `runId`:
|
|
22
|
-
* carries no run-id field of its own — see the
|
|
59
|
+
* @param {{runId?: string|null, auditAvailable?: boolean}} [opts] `runId`:
|
|
60
|
+
* caller-supplied (the spine carries no run-id field of its own — see the
|
|
61
|
+
* file header). `auditAvailable` (default `true`, back-compat: every
|
|
62
|
+
* direct caller in this codebase that hands a literal `[]` here means a
|
|
63
|
+
* REAL, known-empty audit, not "unknown") — set `false` only when the
|
|
64
|
+
* caller could not even determine whether a gate-audit sidecar exists (no
|
|
65
|
+
* file found on disk, or deliberately not read). When `false`, every
|
|
66
|
+
* tool-call figure this function derives (`behaviour`, each occurrence's
|
|
67
|
+
* `toolCalls`) reads `null` (unknown), never `0` — doctrine: unknown is
|
|
68
|
+
* reported as unknown, never rendered as zero.
|
|
23
69
|
*/
|
|
24
|
-
export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
70
|
+
export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, auditAvailable }?: {
|
|
25
71
|
runId?: string | null;
|
|
72
|
+
auditAvailable?: boolean;
|
|
26
73
|
}): {
|
|
27
74
|
runId: string | null;
|
|
28
75
|
job: any;
|
|
29
76
|
verdictType: any;
|
|
30
77
|
model: any;
|
|
78
|
+
provider: any;
|
|
31
79
|
code: {
|
|
32
80
|
version: any;
|
|
33
81
|
sha: any;
|
|
@@ -71,14 +119,51 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
|
71
119
|
diffUsd: number;
|
|
72
120
|
} | null;
|
|
73
121
|
timelineKind: "steps" | "iterations";
|
|
122
|
+
scoutPlan: {
|
|
123
|
+
rounds: number;
|
|
124
|
+
toolCalls: number | null;
|
|
125
|
+
wallMs: number | null;
|
|
126
|
+
spentUsd: number | null;
|
|
127
|
+
unpricedRounds: number;
|
|
128
|
+
tripped: {
|
|
129
|
+
category: any;
|
|
130
|
+
detail: string | null;
|
|
131
|
+
} | null;
|
|
132
|
+
transportRetries: {
|
|
133
|
+
count: number;
|
|
134
|
+
label: string;
|
|
135
|
+
error: string | null;
|
|
136
|
+
} | null;
|
|
137
|
+
scoutRounds: number;
|
|
138
|
+
planRounds: number;
|
|
139
|
+
} | null;
|
|
74
140
|
steps: {
|
|
75
141
|
checks: {
|
|
76
142
|
passed: number;
|
|
77
143
|
failed: number;
|
|
78
144
|
};
|
|
79
145
|
treeChanged: boolean;
|
|
146
|
+
attempts: {
|
|
147
|
+
n: number;
|
|
148
|
+
iteration: number | null;
|
|
149
|
+
outcome: "green" | "red";
|
|
150
|
+
}[];
|
|
151
|
+
attemptWindows: {
|
|
152
|
+
n: number;
|
|
153
|
+
iteration: number | null;
|
|
154
|
+
outcome: "green" | "red";
|
|
155
|
+
startSeq: number;
|
|
156
|
+
endSeq: number;
|
|
157
|
+
startTs: number | null;
|
|
158
|
+
endTs: number | null;
|
|
159
|
+
exitEvalDetail: string | null;
|
|
160
|
+
}[];
|
|
161
|
+
stepStartSeq: any;
|
|
162
|
+
stepEndSeq: any;
|
|
163
|
+
stepStartTs: number | null;
|
|
164
|
+
stepEndTs: number | null;
|
|
80
165
|
rounds: number;
|
|
81
|
-
toolCalls: number;
|
|
166
|
+
toolCalls: number | null;
|
|
82
167
|
wallMs: number | null;
|
|
83
168
|
spentUsd: number | null;
|
|
84
169
|
unpricedRounds: number;
|
|
@@ -96,8 +181,12 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
|
96
181
|
outcome: any;
|
|
97
182
|
}[];
|
|
98
183
|
iterations: {
|
|
184
|
+
windowStartSeq: any;
|
|
185
|
+
windowEndSeq: any;
|
|
186
|
+
windowStartTs: number | null;
|
|
187
|
+
windowEndTs: number | null;
|
|
99
188
|
rounds: number;
|
|
100
|
-
toolCalls: number;
|
|
189
|
+
toolCalls: number | null;
|
|
101
190
|
wallMs: number | null;
|
|
102
191
|
spentUsd: number | null;
|
|
103
192
|
unpricedRounds: number;
|
|
@@ -118,6 +207,15 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
|
118
207
|
stages: any;
|
|
119
208
|
} | null;
|
|
120
209
|
}[];
|
|
210
|
+
fixLoop: {
|
|
211
|
+
attempts: any[];
|
|
212
|
+
rounds: any;
|
|
213
|
+
toolCalls: any;
|
|
214
|
+
wallMs: any;
|
|
215
|
+
spentUsd: any;
|
|
216
|
+
unpricedRounds: any;
|
|
217
|
+
} | null;
|
|
218
|
+
parts: any[];
|
|
121
219
|
replans: any;
|
|
122
220
|
close: {
|
|
123
221
|
source: "close-verdict" | "outer-close";
|
|
@@ -137,7 +235,7 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
|
137
235
|
uniquePaths: number;
|
|
138
236
|
repeats: number;
|
|
139
237
|
repeatPct: number | null;
|
|
140
|
-
};
|
|
238
|
+
} | null;
|
|
141
239
|
memoryCache: any;
|
|
142
240
|
skipped: number;
|
|
143
241
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse one JSONL file into records, tolerant of a malformed line (counted
|
|
3
|
+
* in `skipped`, never thrown on). Mirrors `scripts/behaviour-readout.mjs`'s
|
|
4
|
+
* own parse.
|
|
5
|
+
* @param {string} file
|
|
6
|
+
* @returns {{records: any[], skipped: number}}
|
|
7
|
+
*/
|
|
8
|
+
export function parseJsonl(file: string): {
|
|
9
|
+
records: any[];
|
|
10
|
+
skipped: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* `true` when `name` (a bare filename, not a path) is a gate-audit or lag
|
|
14
|
+
* sidecar by NAME convention — the one filename rule that holds across the
|
|
15
|
+
* whole archive.
|
|
16
|
+
* @param {string} name
|
|
17
|
+
*/
|
|
18
|
+
export function isSidecarByName(name: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* `true` when `records` carries at least one `job-start` (present on every
|
|
21
|
+
* real spine checked, first record or not — a reuse/battery run nests one or
|
|
22
|
+
* more ordinary `runJob` calls inside it) or `run-start` (ralph's own inner
|
|
23
|
+
* loop marker, present on most but not all real spines).
|
|
24
|
+
* @param {any[]} records
|
|
25
|
+
*/
|
|
26
|
+
export function looksLikeSpine(records: any[]): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* `<name>.jsonl` → `<name>-gate-audit.jsonl` in the same directory, and the
|
|
29
|
+
* bare run id for display — the run id lives only in the filename (no spine
|
|
30
|
+
* record carries it; see `src/replay.js`'s header). A leading `u-` (the
|
|
31
|
+
* run-u convention) is stripped; every other prefix (`battery-A1-`,
|
|
32
|
+
* `reuse-`, …) is kept verbatim, since it is the only thing distinguishing
|
|
33
|
+
* that run from its siblings in the same directory.
|
|
34
|
+
* @param {string} spinePath
|
|
35
|
+
* @returns {{runId: string, auditPath: string|null}}
|
|
36
|
+
*/
|
|
37
|
+
export function resolveSiblings(spinePath: string): {
|
|
38
|
+
runId: string;
|
|
39
|
+
auditPath: string | null;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Read one run's spine (+ its gate-audit sidecar, when present) off disk and
|
|
43
|
+
* hand both to {@link replayRun}. Read-only, $0, mints no verdict.
|
|
44
|
+
* @param {string} spinePath absolute or cwd-relative path to a `.jsonl` spine
|
|
45
|
+
* @param {{preParsedSpine?: {records: any[], skipped: number}, skipAudit?: boolean}} [opts]
|
|
46
|
+
* `preParsedSpine`: avoids a second parse of the same file when the caller
|
|
47
|
+
* ({@link listSpines}) already read it once to run {@link looksLikeSpine}.
|
|
48
|
+
* `skipAudit` (PR #23 review item 5, 2026-08-26, carried over): a
|
|
49
|
+
* directory listing never opens the gate-audit sidecar at all —
|
|
50
|
+
* `summarizeForAllLine` reads none of the fields that sidecar feeds inside
|
|
51
|
+
* `replayRun`, so reading every run's sidecar in a directory listing was
|
|
52
|
+
* pure unused I/O, multiplied by every spine found.
|
|
53
|
+
* @returns {ReturnType<typeof replayRun>}
|
|
54
|
+
*/
|
|
55
|
+
export function replayOne(spinePath: string, opts?: {
|
|
56
|
+
preParsedSpine?: {
|
|
57
|
+
records: any[];
|
|
58
|
+
skipped: number;
|
|
59
|
+
};
|
|
60
|
+
skipAudit?: boolean;
|
|
61
|
+
}): ReturnType<typeof replayRun>;
|
|
62
|
+
/**
|
|
63
|
+
* List every spine found directly in `dir` (not recursive — same as the
|
|
64
|
+
* script this replaces): one entry per `.jsonl` file that is not a sidecar
|
|
65
|
+
* by name, each either `{kind:'spine', row}` ({@link summarizeForAllLine}'s
|
|
66
|
+
* row) or `{kind:'not-a-spine', name}` when the file's content doesn't look
|
|
67
|
+
* like a spine. Feeds `formatAllLines` (`src/replay.js`) for the printed
|
|
68
|
+
* table — this function does no formatting of its own, only IO + detection.
|
|
69
|
+
* @param {string} dir
|
|
70
|
+
* @returns {Array<{kind: 'spine', row: ReturnType<typeof summarizeForAllLine>}|{kind: 'not-a-spine', name: string}>}
|
|
71
|
+
*/
|
|
72
|
+
export function listSpines(dir: string): Array<{
|
|
73
|
+
kind: "spine";
|
|
74
|
+
row: ReturnType<typeof summarizeForAllLine>;
|
|
75
|
+
} | {
|
|
76
|
+
kind: "not-a-spine";
|
|
77
|
+
name: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Read a bundle's `history.jsonl` (one `bareloop run` row per line, written
|
|
81
|
+
* by `appendHistory` in `src/bundle.js`) — the single reader for this file,
|
|
82
|
+
* replacing `src/cli.js`'s own hand-rolled `readFileSync`/split. Tolerant of
|
|
83
|
+
* a malformed line the same way {@link parseJsonl} is (counted in
|
|
84
|
+
* `skipped`, never thrown on).
|
|
85
|
+
* @param {string} file absolute path to `history.jsonl`
|
|
86
|
+
* @returns {{rows: any[], skipped: number}}
|
|
87
|
+
*/
|
|
88
|
+
export function readHistoryLog(file: string): {
|
|
89
|
+
rows: any[];
|
|
90
|
+
skipped: number;
|
|
91
|
+
};
|
|
92
|
+
import { replayRun } from './replay.js';
|
|
93
|
+
import { summarizeForAllLine } from './replay.js';
|
package/types/reuse.d.ts
CHANGED
|
@@ -234,7 +234,7 @@ export function selectBridge({ registry, job, ask, provider, pinned, shortlist,
|
|
|
234
234
|
* abandoned attempt twice in a chain of resumes.
|
|
235
235
|
*
|
|
236
236
|
* @param {any[]} events the dead spine's events, parsed, in file order
|
|
237
|
-
* @param {{deathAt?: number|null, direct?: boolean, resumableOutcomes?: string[]}} [opts]
|
|
237
|
+
* @param {{deathAt?: number|null, direct?: boolean, resumableOutcomes?: readonly string[]}} [opts]
|
|
238
238
|
* `deathAt`: when the process is judged to have died (the watchdog's kill record,
|
|
239
239
|
* which is later and better evidence than the last event). Defaults to the last
|
|
240
240
|
* event's own timestamp — the last sign of life there is.
|
|
@@ -246,7 +246,7 @@ export function selectBridge({ registry, job, ask, provider, pinned, shortlist,
|
|
|
246
246
|
export function readResume(events: any[], { deathAt, direct, resumableOutcomes }?: {
|
|
247
247
|
deathAt?: number | null;
|
|
248
248
|
direct?: boolean;
|
|
249
|
-
resumableOutcomes?: string[];
|
|
249
|
+
resumableOutcomes?: readonly string[];
|
|
250
250
|
}): any;
|
|
251
251
|
/**
|
|
252
252
|
* The AGE gate for a paused checkpoint — the sibling of `resumeTreeGate`, and it
|
package/types/run.d.ts
CHANGED
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
* a manufactured-fixation probe). `true` is the ON/experimental arm; the
|
|
144
144
|
* default is the OFF arm. (Shell-owned seam, same doctrine as the provider
|
|
145
145
|
* binding.)
|
|
146
|
-
* @param {{decision: string, text?: string}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
146
|
+
* @param {{decision: string, text?: string|null}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
147
147
|
* answer at a hitl pause (accept | rerun <text> | pause), carried by the leg that
|
|
148
148
|
* RESUMES a paused run and forwarded verbatim to the plan flow. Absent on every
|
|
149
149
|
* ordinary run; never authored and never defaulted (a defaulted answer to "is this
|
|
@@ -219,7 +219,7 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
219
219
|
layerRoot?: boolean | undefined;
|
|
220
220
|
humanRuling?: {
|
|
221
221
|
decision: string;
|
|
222
|
-
text?: string;
|
|
222
|
+
text?: string | null;
|
|
223
223
|
} | null | undefined;
|
|
224
224
|
heldRuling?: {
|
|
225
225
|
decision: string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `~/.config/bareloop` (or the injected override) — the one home directory
|
|
3
|
+
* for both the keys file (PRD §7d, not this module's concern) and the run
|
|
4
|
+
* list.
|
|
5
|
+
* @param {string} [override] test seam; a caller in production never passes it
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function runlistHome(override?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* `<home>/runs.jsonl`.
|
|
11
|
+
* @param {string} [home]
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function runlistPath(home?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {{ at: string, runid: string, job: string, spine: string, patient: string|null, via: 'run-u'|'bundle'|'backfill' }} RunRow
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Append one row to the run list, mkdir -p'ing the home directory first.
|
|
20
|
+
* IDEMPOTENT BY RUNID: a row already carrying `row.runid` is never
|
|
21
|
+
* duplicated (a resume, or a re-run of `backfill`, must not double-list one
|
|
22
|
+
* run) — the file is read once first to check.
|
|
23
|
+
*
|
|
24
|
+
* Perms: the home directory is created `0700` and the file kept `0600`
|
|
25
|
+
* (chmod'd after every append, in case it pre-existed with wider perms) —
|
|
26
|
+
* the same "outside any repo, keys-file-adjacent" posture PRD §7d states for
|
|
27
|
+
* `.env` in this same directory, even though this file carries no secret
|
|
28
|
+
* itself.
|
|
29
|
+
*
|
|
30
|
+
* THROWS on any IO failure (permission denied, disk full, …) — the caller
|
|
31
|
+
* (a live run) is required to catch this and continue the run rather than
|
|
32
|
+
* let a list-append failure abort paid work (hamr's rule: a panel list must
|
|
33
|
+
* never block real work). This function itself does not swallow, so a
|
|
34
|
+
* direct caller (e.g. `backfillRuns`, a $0 path) sees the real error.
|
|
35
|
+
* @param {RunRow} row
|
|
36
|
+
* @param {{ home?: string }} [opts]
|
|
37
|
+
* @returns {{ appended: boolean, reason?: 'duplicate-runid' }}
|
|
38
|
+
*/
|
|
39
|
+
export function appendRun(row: RunRow, opts?: {
|
|
40
|
+
home?: string;
|
|
41
|
+
}): {
|
|
42
|
+
appended: boolean;
|
|
43
|
+
reason?: "duplicate-runid";
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Tolerant reader — reuses {@link parseJsonl} (never a second hand-rolled
|
|
47
|
+
* parser). An absent file reads as an empty list, not an error: a fresh
|
|
48
|
+
* install has never run `appendRun` yet.
|
|
49
|
+
* @param {{ home?: string }} [opts]
|
|
50
|
+
* @returns {{ rows: RunRow[], skipped: number }}
|
|
51
|
+
*/
|
|
52
|
+
export function readRunList(opts?: {
|
|
53
|
+
home?: string;
|
|
54
|
+
}): {
|
|
55
|
+
rows: RunRow[];
|
|
56
|
+
skipped: number;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* `bareloop runs backfill <dir>` — recursively scan `dir` (bounded to
|
|
60
|
+
* {@link MAX_BACKFILL_DEPTH} levels, never into `node_modules`/`.git`,
|
|
61
|
+
* never following a symlinked directory) for archived spine files — both
|
|
62
|
+
* the free-standing layout (e.g. `<dir>/<x>/u-<id>.jsonl`, at any depth) and
|
|
63
|
+
* the bundle layout (`<dir>/.../runs/<runid>/spine.jsonl`) — and add one row
|
|
64
|
+
* per spine whose RESOLVED ABSOLUTE PATH is not already in the list.
|
|
65
|
+
*
|
|
66
|
+
* F197: dedup is by spine path, never by `runidForSpine`'s own derived
|
|
67
|
+
* runid. A spine carrying no `runid` field falls back to a filename-derived
|
|
68
|
+
* id (e.g. `run` for a `run.jsonl` with no runid inside) — a SECOND such
|
|
69
|
+
* file elsewhere (same basename, different directory: not a rare shape,
|
|
70
|
+
* `bareloop-patients/quickstart-proof/run.jsonl` is a real example) is a
|
|
71
|
+
* DIFFERENT run, not a duplicate, and dedup-by-runid would have silently
|
|
72
|
+
* dropped it. When the derived runid collides with one already in the list
|
|
73
|
+
* (pre-existing or added earlier in this same pass), the row's OWN runid is
|
|
74
|
+
* disambiguated (`<base>~2`, `<base>~3`, …) so every row keeps a runid the
|
|
75
|
+
* panel's `/api/runs/:runid` lookup can resolve unambiguously — see
|
|
76
|
+
* {@link RUNID_RE} in `src/panel/server.js`, updated to allow `~`.
|
|
77
|
+
*
|
|
78
|
+
* IDEMPOTENT: running twice adds nothing the second time (the resolved-path
|
|
79
|
+
* set already contains every spine this pass would otherwise re-add). Never
|
|
80
|
+
* silent about what it found: every candidate `.jsonl` is bucketed into
|
|
81
|
+
* exactly one of added / already listed / skipped (not a spine, or
|
|
82
|
+
* unreadable).
|
|
83
|
+
* @param {string} dir
|
|
84
|
+
* @param {{ home?: string }} [opts]
|
|
85
|
+
* @returns {{ added: number, alreadyListed: number, skipped: number, addedRows: RunRow[] }}
|
|
86
|
+
*/
|
|
87
|
+
export function backfillRuns(dir: string, opts?: {
|
|
88
|
+
home?: string;
|
|
89
|
+
}): {
|
|
90
|
+
added: number;
|
|
91
|
+
alreadyListed: number;
|
|
92
|
+
skipped: number;
|
|
93
|
+
addedRows: RunRow[];
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* One printable line per row for `bareloop runs` (no subcommand): `job
|
|
97
|
+
* (runid) · date · spine path`, with `file missing` appended when the row's
|
|
98
|
+
* `spine` no longer exists on disk (an archive moved/pruned since the row
|
|
99
|
+
* was written — never silently listed as if it were still there).
|
|
100
|
+
* @param {RunRow} row
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
export function formatRunRow(row: RunRow): string;
|
|
104
|
+
export const MAX_BACKFILL_DEPTH: 6;
|
|
105
|
+
export type RunRow = {
|
|
106
|
+
at: string;
|
|
107
|
+
runid: string;
|
|
108
|
+
job: string;
|
|
109
|
+
spine: string;
|
|
110
|
+
patient: string | null;
|
|
111
|
+
via: "run-u" | "bundle" | "backfill";
|
|
112
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reset a patient to its frozen seed and drop its `.litectx` store.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} wd absolute path to the patient's tree (a git repo with >=1 commit)
|
|
5
|
+
* @param {string} seed the frozen seed commit
|
|
6
|
+
* @returns {{ head: string, storeRemoved: boolean }} `head` is the short sha the tree
|
|
7
|
+
* now stands at; `storeRemoved` says whether a store was actually there to remove
|
|
8
|
+
* (so a caller can print the truth rather than a claim).
|
|
9
|
+
*/
|
|
10
|
+
export function coldReset(wd: string, seed: string): {
|
|
11
|
+
head: string;
|
|
12
|
+
storeRemoved: boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* F186 — the patient's own `.gitignore` denies `*.jsonl`, so `coldReset`'s
|
|
16
|
+
* `git clean -fd` (no `-x`) never removes a `gate-audit.jsonl` already
|
|
17
|
+
* sitting at the tree root — it can only be there from an EARLIER run
|
|
18
|
+
* against this same patient (an authoring scout, or a prior worker run)
|
|
19
|
+
* that never got archived out. Left in place, THIS run's own Gate (which
|
|
20
|
+
* writes to that same path) would append to it, and the end-of-run rename
|
|
21
|
+
* that claims the whole file as "this run's own audit" would silently
|
|
22
|
+
* carry every earlier run's rows too — a harness slicing a shared append-
|
|
23
|
+
* only log has to account for every writer inside its window.
|
|
24
|
+
*
|
|
25
|
+
* Call this AFTER `coldReset` and BEFORE this run's own Gate ever opens: at
|
|
26
|
+
* that point a `gate-audit.jsonl` sitting in the tree is provably not this
|
|
27
|
+
* run's, because coldReset just proved the tree is unable to have produced
|
|
28
|
+
* one since the seed reset. NOT for a resume — a halted run's own gate
|
|
29
|
+
* audit already in the tree it resumes into is that SAME run's prior leg,
|
|
30
|
+
* not a stranger's; callers on the resume path must not call this.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} wd absolute path to the patient's tree, just cold-reset
|
|
33
|
+
* @param {string} spineDir absolute path to this run's spine directory
|
|
34
|
+
* @param {string} runid this run's own id (the same one every other spine
|
|
35
|
+
* file for this run is named after)
|
|
36
|
+
* @returns {string|null} the path the stale file was moved to, or `null`
|
|
37
|
+
* when there was nothing to move
|
|
38
|
+
*/
|
|
39
|
+
export function moveStaleGateAudit(wd: string, spineDir: string, runid: string): string | null;
|