bareloop 0.27.0 → 0.28.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 +49 -0
- package/bareloop.context.md +188 -33
- package/package.json +1 -1
- package/src/authorreadout.js +323 -0
- package/src/authorrun.js +1337 -0
- package/src/cli.js +120 -7
- package/src/index.js +13 -1
- package/src/interviewrun.js +842 -0
- package/src/planrun.js +1 -1
- package/src/replayio.js +153 -0
- package/src/reuse.js +1 -1
- package/src/run.js +1 -1
- package/src/u-patient.js +73 -0
- package/src/u-readout.js +451 -0
- package/src/userrun.js +2273 -0
- package/types/authorreadout.d.ts +172 -0
- package/types/authorrun.d.ts +37 -0
- package/types/index.d.ts +2 -1
- package/types/interviewrun.d.ts +46 -0
- package/types/planrun.d.ts +2 -2
- package/types/replayio.d.ts +93 -0
- package/types/reuse.d.ts +2 -2
- package/types/run.d.ts +2 -2
- package/types/u-patient.d.ts +39 -0
- package/types/u-readout.d.ts +247 -0
- package/types/userrun.d.ts +134 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{goal?: string|null, closeDecl?: any}} spec the RESOLVED spec — the bytes
|
|
3
|
+
* that get hashed and signed, never the operator's draft and never the authored
|
|
4
|
+
* half on its own.
|
|
5
|
+
* @returns {string[]} one console line each, in print order.
|
|
6
|
+
*/
|
|
7
|
+
export function declarationLines({ goal, closeDecl }: {
|
|
8
|
+
goal?: string | null;
|
|
9
|
+
closeDecl?: any;
|
|
10
|
+
}): string[];
|
|
11
|
+
/**
|
|
12
|
+
* F176'S FALLBACK, SAID OUT LOUD — the revise ladder kept the NEWEST SOUND
|
|
13
|
+
* declaration rather than the last one it happened to try, because "kept the
|
|
14
|
+
* last accepted revision" is not the same claim as "kept the best revision"
|
|
15
|
+
* (docs/logs/FINDINGS.md F176). Absent on every run where the fallback never
|
|
16
|
+
* fired (the overwhelming majority), which is why this returns an EMPTY list
|
|
17
|
+
* rather than a "no fallback" line — a green close has nothing to say here and
|
|
18
|
+
* printing it every run would be noise, not information.
|
|
19
|
+
* @param {{fellBack?: {from: string, to: string, brokenStages: string[]}|null}|null} authoring `authored.authoring`
|
|
20
|
+
* @returns {string[]}
|
|
21
|
+
*/
|
|
22
|
+
export function fellBackLines(authoring: {
|
|
23
|
+
fellBack?: {
|
|
24
|
+
from: string;
|
|
25
|
+
to: string;
|
|
26
|
+
brokenStages: string[];
|
|
27
|
+
} | null;
|
|
28
|
+
} | null): string[];
|
|
29
|
+
/**
|
|
30
|
+
* THE TWO SIGNED JUDGED ARTIFACTS, as a person reads them before signing
|
|
31
|
+
* (softgreen modules 4+5). Absent on a close that judges nothing, which is why
|
|
32
|
+
* this returns an EMPTY list rather than a "no card" line: a green close has no
|
|
33
|
+
* rubric and saying so every run is noise, not information.
|
|
34
|
+
*
|
|
35
|
+
* The CARD is printed in full — it is short, it is the whole standard the judge
|
|
36
|
+
* will hold the work to, and it is the signer's own words. The CASES are printed
|
|
37
|
+
* as a ROSTER (id, verdict, itemized reds) rather than in full: ten real source
|
|
38
|
+
* artifacts is a page of code the terminal cannot usefully show, and what a
|
|
39
|
+
* signer checks at this surface is that both polarities are there and that each
|
|
40
|
+
* red names something they recognise. The artifacts themselves are in the spec
|
|
41
|
+
* file, which is named right above this block.
|
|
42
|
+
* @param {{closeDecl?: any}} spec the RESOLVED spec
|
|
43
|
+
* @returns {string[]}
|
|
44
|
+
*/
|
|
45
|
+
export function rubricLines({ closeDecl }: {
|
|
46
|
+
closeDecl?: any;
|
|
47
|
+
}): string[];
|
|
48
|
+
/**
|
|
49
|
+
* THE CONFIRM TURN'S OPEN QUESTIONS (PRD item 33 M3 piece 4, ruling 5's D3
|
|
50
|
+
* addendum) — shown at the SIGNING readout, from `authored.json`'s own
|
|
51
|
+
* `confirmed.openQuestions`, never added to the signed spec format itself
|
|
52
|
+
* (D4: the closeDecl's `notes` are the model's own record; this is the
|
|
53
|
+
* person's "fix" text from a round that hit the 2-round cap, ruling 5,
|
|
54
|
+
* passed to the composer verbatim rather than spending a 3rd call, D3). A
|
|
55
|
+
* close with none is shown as `(none)` rather than an empty line — the
|
|
56
|
+
* absence is itself part of what the signer reads before signing.
|
|
57
|
+
* @param {{openQuestions?: string[]}|null} confirmed `authored.confirmed`
|
|
58
|
+
* @returns {string[]}
|
|
59
|
+
*/
|
|
60
|
+
export function openQuestionLines(confirmed: {
|
|
61
|
+
openQuestions?: string[];
|
|
62
|
+
} | null): string[];
|
|
63
|
+
/**
|
|
64
|
+
* F175's open half, said at the SIGNING readout too (run mu0voeo4, hamr's
|
|
65
|
+
* 2026-09-16 ruling) — every question the plan raised that "Confirm" or
|
|
66
|
+
* "Type the goal yourself" forced the person to answer inline, from
|
|
67
|
+
* `authored.json`'s own `confirmed.answeredQuestions`. Shown only when
|
|
68
|
+
* non-empty (the overwhelming majority of plans raise no question at all,
|
|
69
|
+
* and printing an empty block every run would be noise, not information) —
|
|
70
|
+
* unlike {@link openQuestionLines}, an empty list here says nothing, because
|
|
71
|
+
* absence is the ordinary case, not a fact the signer needs flagged.
|
|
72
|
+
* @param {{answeredQuestions?: string[]}|null} confirmed `authored.confirmed`
|
|
73
|
+
* @returns {string[]}
|
|
74
|
+
*/
|
|
75
|
+
export function answeredQuestionLines(confirmed: {
|
|
76
|
+
answeredQuestions?: string[];
|
|
77
|
+
} | null): string[];
|
|
78
|
+
/**
|
|
79
|
+
* THE CALIBRATION GATE'S OWN READOUT — the one gate that spends money, and the
|
|
80
|
+
* one whose rows a signer has to read case by case.
|
|
81
|
+
*
|
|
82
|
+
* ITEMIZED, never an aggregate percentage: "9/10" tells a person nothing about
|
|
83
|
+
* which line of their own rubric is wrong, and the fix for a miss is a card line
|
|
84
|
+
* or a corrected case (§4.3's ceiling). A CASUALTY prints under its own name and
|
|
85
|
+
* says so, because a dead judge is not a failed set.
|
|
86
|
+
* @param {any} calibration `signing.gates.calibration`
|
|
87
|
+
* @returns {string[]}
|
|
88
|
+
*/
|
|
89
|
+
export function calibrationLines(calibration: any): string[];
|
|
90
|
+
/**
|
|
91
|
+
* THE AUTHORING CEILING, parsed from `--budget`. Housed here for the same reason
|
|
92
|
+
* the readout above is: the runner is a script, and a rule no test can reach is a
|
|
93
|
+
* rule nothing checks.
|
|
94
|
+
*
|
|
95
|
+
* ABSENT IS UNBOUNDED, and that is the only way to get there. A malformed value
|
|
96
|
+
* is an ERROR rather than a fallback: `--budget banana` silently collapsing to
|
|
97
|
+
* "no ceiling" is precisely the failure this flag exists to prevent, wearing the
|
|
98
|
+
* operator's own typo. Zero and negatives are rejected on the same rule — a
|
|
99
|
+
* ceiling that can fund nothing is a typo for "unbounded" far more often than it
|
|
100
|
+
* is a request, and the honest way to ask for nothing is not to ask.
|
|
101
|
+
*
|
|
102
|
+
* There is NO DEFAULT anywhere in this function, deliberately: a defaulted cap is
|
|
103
|
+
* a silent second ceiling (the `maxWallMs` precedent), and the number is the
|
|
104
|
+
* operator's to set or to decline.
|
|
105
|
+
* @param {string|null} raw the argv value, or null when the flag was not given
|
|
106
|
+
* @returns {{ceilingUsd: number|null, error: string|null}}
|
|
107
|
+
*/
|
|
108
|
+
export function parseCeiling(raw: string | null): {
|
|
109
|
+
ceilingUsd: number | null;
|
|
110
|
+
error: string | null;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* The ceiling's header line. An UNBOUNDED run is ANNOUNCED — printed before the
|
|
114
|
+
* provider is built, so it reaches stdout ahead of the first paid byte rather
|
|
115
|
+
* than being inferred from the total afterwards. An unbounded run is legal; it is
|
|
116
|
+
* never allowed to be an accident.
|
|
117
|
+
* @param {number|null} ceilingUsd
|
|
118
|
+
*/
|
|
119
|
+
export function ceilingLine(ceilingUsd: number | null): string;
|
|
120
|
+
/**
|
|
121
|
+
* ONE PROGRESS PHASE, as a console line.
|
|
122
|
+
*
|
|
123
|
+
* The pipeline used to run up to ~15 minutes saying nothing between
|
|
124
|
+
* `author-start` and its result — a real survey ladder, a real declaration
|
|
125
|
+
* ladder, and a real toolchain per close stage, all inside one await. Silence
|
|
126
|
+
* and a hang are the same bytes on a terminal, and the operator's only lever is
|
|
127
|
+
* to kill a run that may be working.
|
|
128
|
+
*
|
|
129
|
+
* It says WHAT is happening, never how far along it is: there is no honest
|
|
130
|
+
* fraction to print (a survey attempt has no progress, and a suite stage
|
|
131
|
+
* finishes when it finishes), and an invented percentage is a number nobody
|
|
132
|
+
* measured. What it does carry is the two facts a person waiting actually uses —
|
|
133
|
+
* which phase, and how long the last thing took.
|
|
134
|
+
*
|
|
135
|
+
* `durationMs` prints as UNKNOWN when it is absent rather than as 0 (F6's rule,
|
|
136
|
+
* in its time form): a stage whose duration was never measured did not take no
|
|
137
|
+
* time.
|
|
138
|
+
* @param {string} phase @param {any} [data]
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
export function phaseLine(phase: string, data?: any): string;
|
|
142
|
+
/**
|
|
143
|
+
* ONE crashed authoring run, ready for the spine.
|
|
144
|
+
*
|
|
145
|
+
* It exists because a crash used to leave NO body. `run-author.mjs` awaited the
|
|
146
|
+
* paid pipeline at the top level, so a throw anywhere inside it went to the
|
|
147
|
+
* operator's terminal as an unhandled rejection and the spine kept exactly one
|
|
148
|
+
* line — `author-start` — which is byte-for-byte what a run still in flight looks
|
|
149
|
+
* like. The terminal scrolls; the spine is the record.
|
|
150
|
+
*
|
|
151
|
+
* The STACK is the evidence (the incident that minted this had no idea WHERE the
|
|
152
|
+
* throw came from), and a stack is the one field here that can be arbitrarily
|
|
153
|
+
* long and can quote anything the process had open. So it goes through `scrubRaw`
|
|
154
|
+
* — the ONE persist boundary for a raw blob — which redacts it over the same
|
|
155
|
+
* `SECRET_PATTERNS` inventory the validator reds on and bounds it with the bound
|
|
156
|
+
* announcing its own size (F28). Nothing here is hand-rolled: a second spelling
|
|
157
|
+
* of either rule is how the two drift.
|
|
158
|
+
*
|
|
159
|
+
* A non-Error throw (a string, a plain object) is honest rather than coerced into
|
|
160
|
+
* an Error shape: it has no `name`, so `name` is `null`, and `String(err)` is what
|
|
161
|
+
* lands in the raw.
|
|
162
|
+
* @param {unknown} err whatever was thrown
|
|
163
|
+
* @returns {{name: string|null, message: string|null, code: string|null,
|
|
164
|
+
* raw: ReturnType<typeof scrubRaw>}}
|
|
165
|
+
*/
|
|
166
|
+
export function crashRecord(err: unknown): {
|
|
167
|
+
name: string | null;
|
|
168
|
+
message: string | null;
|
|
169
|
+
code: string | null;
|
|
170
|
+
raw: ReturnType<typeof scrubRaw>;
|
|
171
|
+
};
|
|
172
|
+
import { scrubRaw } from './text.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} Deps
|
|
3
|
+
* @property {Record<string,string|undefined>} [env]
|
|
4
|
+
* @property {NodeJS.ReadStream} [stdin]
|
|
5
|
+
* @property {NodeJS.WritableStream} [stdout]
|
|
6
|
+
* @property {NodeJS.WritableStream} [stderr]
|
|
7
|
+
* @property {string} [invokedAs] the command name this run was actually
|
|
8
|
+
* reached through, for the usage message ONLY — `scripts/run-author.mjs`
|
|
9
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
10
|
+
* `'bareloop author'` so a person who typed that is told to re-run THAT,
|
|
11
|
+
* never a script path they never invoked and may not have on disk.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Parses this script's own argv shape and runs the authoring pipeline
|
|
15
|
+
* (scout, declaration loop, D9's gates) against the given (or real)
|
|
16
|
+
* stdin/stdout. `scripts/run-author.mjs` is the thin adapter over this, the
|
|
17
|
+
* same shape `bin/bareloop.mjs` already is over `src/cli.js`'s own
|
|
18
|
+
* `main(argv, deps)`.
|
|
19
|
+
* @param {string[]} argv
|
|
20
|
+
* @param {Deps} [deps]
|
|
21
|
+
* @returns {Promise<number>} an exit code — never calls process.exit
|
|
22
|
+
*/
|
|
23
|
+
export function main(argv: string[], deps?: Deps): Promise<number>;
|
|
24
|
+
export type Deps = {
|
|
25
|
+
env?: Record<string, string | undefined> | undefined;
|
|
26
|
+
stdin?: NodeJS.ReadStream | undefined;
|
|
27
|
+
stdout?: NodeJS.WritableStream | undefined;
|
|
28
|
+
stderr?: NodeJS.WritableStream | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* the command name this run was actually
|
|
31
|
+
* reached through, for the usage message ONLY — `scripts/run-author.mjs`
|
|
32
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
33
|
+
* `'bareloop author'` so a person who typed that is told to re-run THAT,
|
|
34
|
+
* never a script path they never invoked and may not have on disk.
|
|
35
|
+
*/
|
|
36
|
+
invokedAs?: string | undefined;
|
|
37
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -24,7 +24,8 @@ export { scrubRaw, RAW_PERSIST_MAX } from "./text.js";
|
|
|
24
24
|
export { authorClose, MAX_REVISIONS, questionsFor, requiredAnswersFor } from "./authorflow.js";
|
|
25
25
|
export { validateCloseDecl, runDeclaredStages, declaredStages, HUMAN_PAUSE } from "./declaredclose.js";
|
|
26
26
|
export { runBehaviour, formatBehaviour } from "./behaviour.js";
|
|
27
|
-
export { replayRun, formatReplay } from "./replay.js";
|
|
27
|
+
export { replayRun, formatReplay, summarizeForAllLine, formatAllLines } from "./replay.js";
|
|
28
|
+
export { replayOne, listSpines, readHistoryLog } from "./replayio.js";
|
|
28
29
|
export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
|
|
29
30
|
export { PROMPT_REGISTERS, isPromptFile } from "./promptregisters.js";
|
|
30
31
|
export { exportBundle, bundleHash, readBundle, resolveBundleSpec, checkEnvelope, bless, verifyBlessing, appendHistory, checkBundleDeps } from "./bundle.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} Deps
|
|
3
|
+
* @property {Record<string,string|undefined>} [env]
|
|
4
|
+
* @property {NodeJS.ReadStream} [stdin]
|
|
5
|
+
* @property {NodeJS.WritableStream} [stdout]
|
|
6
|
+
* @property {NodeJS.WritableStream} [stderr]
|
|
7
|
+
* @property {typeof realSpawnSync} [spawnSync] the TEST SEAM for the final
|
|
8
|
+
* paid-step offer — a caller that supplies this never actually spawns
|
|
9
|
+
* `scripts/run-author.mjs`.
|
|
10
|
+
* @property {string} [invokedAs] the command name this run was actually
|
|
11
|
+
* reached through, for the usage message ONLY — `scripts/run-interview.mjs`
|
|
12
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
13
|
+
* `'bareloop interview'` so a person who typed that is told to re-run THAT,
|
|
14
|
+
* never a script path they never invoked and may not have on disk.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Parses this script's own argv shape and runs the interview against the
|
|
18
|
+
* given (or real) stdin/stdout. `scripts/run-interview.mjs` is the thin
|
|
19
|
+
* adapter over this, the same shape `bin/bareloop.mjs` already is over
|
|
20
|
+
* `src/cli.js`'s own `main(argv, deps)`.
|
|
21
|
+
* @param {string[]} argv
|
|
22
|
+
* @param {Deps} [deps]
|
|
23
|
+
* @returns {Promise<number>} an exit code — never calls process.exit
|
|
24
|
+
*/
|
|
25
|
+
export function main(argv: string[], deps?: Deps): Promise<number>;
|
|
26
|
+
export type Deps = {
|
|
27
|
+
env?: Record<string, string | undefined> | undefined;
|
|
28
|
+
stdin?: NodeJS.ReadStream | undefined;
|
|
29
|
+
stdout?: NodeJS.WritableStream | undefined;
|
|
30
|
+
stderr?: NodeJS.WritableStream | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* the TEST SEAM for the final
|
|
33
|
+
* paid-step offer — a caller that supplies this never actually spawns
|
|
34
|
+
* `scripts/run-author.mjs`.
|
|
35
|
+
*/
|
|
36
|
+
spawnSync?: typeof realSpawnSync | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* the command name this run was actually
|
|
39
|
+
* reached through, for the usage message ONLY — `scripts/run-interview.mjs`
|
|
40
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
41
|
+
* `'bareloop interview'` so a person who typed that is told to re-run THAT,
|
|
42
|
+
* never a script path they never invoked and may not have on disk.
|
|
43
|
+
*/
|
|
44
|
+
invokedAs?: string | undefined;
|
|
45
|
+
};
|
|
46
|
+
import { spawnSync as realSpawnSync } from 'node:child_process';
|
package/types/planrun.d.ts
CHANGED
|
@@ -365,7 +365,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
365
365
|
* it the deterministic name would collide with that very branch and the collision walk
|
|
366
366
|
* would mint a `-2` beside the progress the resume exists to keep. Absent is the cold
|
|
367
367
|
* path. A recorded branch that no longer exists is a STOP, never a fresh start.
|
|
368
|
-
* @param {{decision: string, text?: string}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
368
|
+
* @param {{decision: string, text?: string|null}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
369
369
|
* answer at a hitl pause (2026-08-12 §1, re-cut 2026-08-17: accept | rerun <text> | pause), carried in
|
|
370
370
|
* by the runner on the leg that RESUMES a paused run. Absent on every ordinary run and
|
|
371
371
|
* on the leg that pauses; it is never authored, never defaulted, and never inferred.
|
|
@@ -456,7 +456,7 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
456
456
|
resumeBranch?: string | null | undefined;
|
|
457
457
|
humanRuling?: {
|
|
458
458
|
decision: string;
|
|
459
|
-
text?: string;
|
|
459
|
+
text?: string | null;
|
|
460
460
|
} | null | undefined;
|
|
461
461
|
heldRuling?: {
|
|
462
462
|
decision: string;
|
|
@@ -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,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;
|