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/src/behaviour.js
CHANGED
|
@@ -126,11 +126,14 @@ export function runBehaviour(events, { runId } = {}) {
|
|
|
126
126
|
* Render a {@link runBehaviour} summary as the agreed printable block
|
|
127
127
|
* (2–4 short lines). A zero-call run formats plainly ("0 tool calls") rather
|
|
128
128
|
* than a NaN-laced percentage — an empty run is a complete, reportable
|
|
129
|
-
* answer, not a division error.
|
|
130
|
-
*
|
|
129
|
+
* answer, not a division error. `summary === null` means the caller never
|
|
130
|
+
* had a gate-audit sidecar to read at all (see `replayRun`'s `auditAvailable`
|
|
131
|
+
* option) — that reads as "unknown", never coerced to a 0-call summary.
|
|
132
|
+
* @param {ReturnType<typeof runBehaviour>|null} summary
|
|
131
133
|
* @returns {string}
|
|
132
134
|
*/
|
|
133
135
|
export function formatBehaviour(summary) {
|
|
136
|
+
if (summary === null) return 'unknown (no gate-audit sidecar found for this run)';
|
|
134
137
|
const { totalCalls, byTool, denied, repeats, repeatPct } = summary; // uniquePaths: data-only, not in the agreed printed shape
|
|
135
138
|
const lines = [];
|
|
136
139
|
|
package/src/cli.js
CHANGED
|
@@ -34,6 +34,41 @@ import {
|
|
|
34
34
|
runJob, makeSpine, loadRegistry, listingRow, jobSpecHash, resolveWorkerModel, resolveJudge,
|
|
35
35
|
} from './index.js';
|
|
36
36
|
import { resolveProvider, buildRunnerProviders, apiKeyProblem } from './providers.js';
|
|
37
|
+
// PANEL-BUILD.md P0 task 2/4 — `bareloop run-u` wraps the person-path run
|
|
38
|
+
// flow's own argv-parsing entry (`src/userrun.js`'s `main(argv, deps)`,
|
|
39
|
+
// itself the lift target of P0 task 1). This is the SAME function
|
|
40
|
+
// `scripts/run-u.mjs` calls directly — routing this command through it
|
|
41
|
+
// rather than re-parsing argv a second time here keeps exactly one code
|
|
42
|
+
// path for the flag grammar; `src/cli.js` only supplies the deps shape
|
|
43
|
+
// (`out`/`err`/`env`) this command's own callers already use.
|
|
44
|
+
import { main as runUMain } from './userrun.js';
|
|
45
|
+
// PANEL-BUILD.md P0 task 3/4 — `bareloop interview` and `bareloop author`
|
|
46
|
+
// wrap the close-authoring interview and authoring-pipeline flows' own
|
|
47
|
+
// argv-parsing entries (`src/interviewrun.js`/`src/authorrun.js`, lifted out
|
|
48
|
+
// of `scripts/run-interview.mjs`/`scripts/run-author.mjs` the same way task
|
|
49
|
+
// 1/2 lifted `scripts/run-u.mjs`). `scripts/run-interview.mjs`/`scripts/
|
|
50
|
+
// run-author.mjs` call these SAME functions directly, so there is exactly
|
|
51
|
+
// one place each flag grammar is parsed.
|
|
52
|
+
import { main as interviewMain } from './interviewrun.js';
|
|
53
|
+
import { main as authorMain } from './authorrun.js';
|
|
54
|
+
// PANEL-BUILD.md P0 — the spine/gate-audit read side (`bareloop replay`,
|
|
55
|
+
// and `doHistory`'s reader below), lifted out of `scripts/run-replay.mjs`
|
|
56
|
+
// into `src/replayio.js` the same way tasks 1-3 lifted their own scripts.
|
|
57
|
+
import {
|
|
58
|
+
parseJsonl, looksLikeSpine, replayOne, listSpines, readHistoryLog,
|
|
59
|
+
} from './replayio.js';
|
|
60
|
+
import { formatReplay, formatAllLines } from './replay.js';
|
|
61
|
+
// PANEL-BUILD.md P1 — the read-only panel HTTP server (`bareloop panel`).
|
|
62
|
+
// One more caller of the same `src/` library functions this file already
|
|
63
|
+
// calls (the layering law, PANEL-BUILD.md §2) — `src/panel/server.js` never
|
|
64
|
+
// re-implements the spine/run-list read side, it calls `src/replayio.js`/
|
|
65
|
+
// `src/runlist.js` directly, in-process.
|
|
66
|
+
import { panelMain } from './panel/server.js';
|
|
67
|
+
// PANEL-BUILD.md P1 (2026-09-24 rulings) — the one run list
|
|
68
|
+
// (`~/.config/bareloop/runs.jsonl`) and its backfill scan. `bareloop runs`
|
|
69
|
+
// (list) and `bareloop runs backfill <dir>` (reconstruct rows from archived
|
|
70
|
+
// spines already on disk) are this rung's only new commands.
|
|
71
|
+
import { appendRun, readRunList, backfillRuns, formatRunRow } from './runlist.js';
|
|
37
72
|
|
|
38
73
|
// The tier->model tables live in `src/providers.js` now (PRD item 28's
|
|
39
74
|
// factory) — one seam instead of a copy hardcoded in each runner. A
|
|
@@ -369,6 +404,23 @@ async function doRun(args, { out, err, cwd, env, now, deps }) {
|
|
|
369
404
|
const closeDir = join(runsDir, 'close');
|
|
370
405
|
mkdirSync(closeDir, { recursive: true });
|
|
371
406
|
|
|
407
|
+
// PANEL-BUILD.md P1 — one row in the run list, BEFORE the first paid call
|
|
408
|
+
// (runJob, right below). A list-append failure must never block a real,
|
|
409
|
+
// already-signed run: caught and named loudly, never rethrown.
|
|
410
|
+
// `deps.runlistHome` — the SAME test seam shape `deps.provider`/`deps.now`
|
|
411
|
+
// already are on this ctx (never a new env var, see src/runlist.js's own
|
|
412
|
+
// header comment): a caller that injects it (every test in
|
|
413
|
+
// tests/cli.test.js that reaches this line) writes its row under a temp
|
|
414
|
+
// home instead of the real `~/.config/bareloop`; production never sets it,
|
|
415
|
+
// so `appendRun` falls through to `runlistHome()`'s own `os.homedir()`.
|
|
416
|
+
try {
|
|
417
|
+
appendRun({
|
|
418
|
+
at: new Date(now()).toISOString(), runid, job: runSpec.job, spine: spineFile, patient: worktree, via: 'bundle',
|
|
419
|
+
}, { home: deps.runlistHome });
|
|
420
|
+
} catch (e) {
|
|
421
|
+
err(`WARNING: could not add this run to ~/.config/bareloop/runs.jsonl (${/** @type {Error} */ (e).message}) — the run continues; the panel's list will be missing this row.`);
|
|
422
|
+
}
|
|
423
|
+
|
|
372
424
|
let outcome;
|
|
373
425
|
try {
|
|
374
426
|
outcome = await runJob(runSpec, {
|
|
@@ -397,8 +449,12 @@ async function doRun(args, { out, err, cwd, env, now, deps }) {
|
|
|
397
449
|
if (existsSync(auditSrc)) renameSync(auditSrc, join(runsDir, 'gate-audit.jsonl'));
|
|
398
450
|
|
|
399
451
|
// the job-end record off THIS run's own spine — never fabricate a 0 for an
|
|
400
|
-
// unknown spend (F6/F12's class).
|
|
401
|
-
|
|
452
|
+
// unknown spend (F6/F12's class). PANEL-BUILD.md P0: read via the same
|
|
453
|
+
// named library reader `doHistory`/`doReplay` already use (`parseJsonl`,
|
|
454
|
+
// `src/replayio.js`) instead of hand-rolling the parse again here — it
|
|
455
|
+
// tolerates a malformed/truncated line (a process killed mid-append)
|
|
456
|
+
// instead of throwing uncaught and crashing this run's exit tail.
|
|
457
|
+
const { records: events } = parseJsonl(spineFile);
|
|
402
458
|
const je = events.findLast((/** @type {any} */ e) => e.type === 'job-end');
|
|
403
459
|
const spentUsd = je?.spentUsd ?? null;
|
|
404
460
|
const spendComplete = je?.spendComplete ?? null;
|
|
@@ -450,10 +506,17 @@ function doHistory(args, { out, err, cwd }) {
|
|
|
450
506
|
const dir = resolve(cwd, bundleDirArg);
|
|
451
507
|
const historyFile = join(dir, 'history.jsonl');
|
|
452
508
|
if (existsSync(historyFile)) {
|
|
509
|
+
// PANEL-BUILD.md P0 — the one reader for `history.jsonl` (`src/replayio.js`'s
|
|
510
|
+
// `readHistoryLog`), replacing this command's own hand-rolled
|
|
511
|
+
// `readFileSync`/split. Each row is re-printed with `JSON.stringify`
|
|
512
|
+
// (byte-identical to the original trimmed line for every well-formed
|
|
513
|
+
// row, since `appendHistory` itself writes `JSON.stringify(row)` —
|
|
514
|
+
// src/bundle.js:712); a malformed line is counted, never silently
|
|
515
|
+
// dropped or reprinted corrupt.
|
|
516
|
+
const { rows, skipped } = readHistoryLog(historyFile);
|
|
453
517
|
out('history:');
|
|
454
|
-
for (const
|
|
455
|
-
|
|
456
|
-
}
|
|
518
|
+
for (const row of rows) out(` ${JSON.stringify(row)}`);
|
|
519
|
+
if (skipped > 0) out(` (${skipped} malformed line${skipped === 1 ? '' : 's'} skipped)`);
|
|
457
520
|
} else out('history: (no runs yet)');
|
|
458
521
|
const reg = loadRegistry(join(dir, 'bridges'));
|
|
459
522
|
if (reg.bridges.length > 0) {
|
|
@@ -463,9 +526,88 @@ function doHistory(args, { out, err, cwd }) {
|
|
|
463
526
|
return 0;
|
|
464
527
|
}
|
|
465
528
|
|
|
529
|
+
/**
|
|
530
|
+
* `bareloop replay <spine.jsonl>` — one run's whole story, reconstructed
|
|
531
|
+
* from its spine + gate-audit sidecar, printed as one page
|
|
532
|
+
* ({@link formatReplay}). `bareloop replay --all <dir>` — every spine found
|
|
533
|
+
* directly in `dir`, one aligned table row each ({@link formatAllLines}).
|
|
534
|
+
* Read-only, $0, mints no verdict, writes nothing (PANEL-BUILD.md P0: this
|
|
535
|
+
* is the CLI the gap table marked "No" for "Replay an archived run at $0").
|
|
536
|
+
* Logic lifted verbatim from `scripts/run-replay.mjs` into
|
|
537
|
+
* `src/replayio.js`; this command is that script's one caller now, the same
|
|
538
|
+
* shape `bareloop history`/`bareloop run` already are over their own
|
|
539
|
+
* library functions.
|
|
540
|
+
* @param {string[]} args @param {{ out: (s: string) => void, err: (s: string) => void, cwd: string }} ctx
|
|
541
|
+
*/
|
|
542
|
+
function doReplay(args, { out, err, cwd }) {
|
|
543
|
+
if (args[0] === '--all') {
|
|
544
|
+
const dirArg = args[1];
|
|
545
|
+
if (!dirArg) { err('usage: bareloop replay --all <dir>'); return 1; }
|
|
546
|
+
const dir = resolve(cwd, dirArg);
|
|
547
|
+
if (!existsSync(dir)) { err(`no such directory: ${dir}`); return 1; }
|
|
548
|
+
const entries = listSpines(dir);
|
|
549
|
+
if (entries.length === 0) { out(`no .jsonl files found in ${dir}`); return 0; }
|
|
550
|
+
out(formatAllLines(entries));
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
553
|
+
const spinePathArg = args[0];
|
|
554
|
+
if (!spinePathArg) {
|
|
555
|
+
err('usage: bareloop replay <spine.jsonl>');
|
|
556
|
+
err(' bareloop replay --all <dir>');
|
|
557
|
+
return 1;
|
|
558
|
+
}
|
|
559
|
+
const spinePath = resolve(cwd, spinePathArg);
|
|
560
|
+
if (!existsSync(spinePath)) { err(`no such file: ${spinePath}`); return 1; }
|
|
561
|
+
const spine = parseJsonl(spinePath);
|
|
562
|
+
if (!looksLikeSpine(spine.records)) {
|
|
563
|
+
err(`${spinePath} does not look like a spine (no job-start or run-start record found)`);
|
|
564
|
+
return 1;
|
|
565
|
+
}
|
|
566
|
+
const summary = replayOne(spinePath, { preParsedSpine: spine });
|
|
567
|
+
out(formatReplay(summary));
|
|
568
|
+
return 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* `bareloop runs` — print the one run list, one line per row
|
|
573
|
+
* ({@link formatRunRow}). `bareloop runs backfill <dir>` — scan `dir` for
|
|
574
|
+
* archived spines and add a row per one not already listed
|
|
575
|
+
* ({@link backfillRuns}), printing the count added/already-listed/skipped
|
|
576
|
+
* (never silent). Read-only, $0: no interview, no author, no run trigger, no
|
|
577
|
+
* key ever read.
|
|
578
|
+
* @param {string[]} args @param {{ out: (s: string) => void, err: (s: string) => void, cwd: string }} ctx
|
|
579
|
+
*/
|
|
580
|
+
function doRuns(args, { out, err, cwd }) {
|
|
581
|
+
if (args[0] === 'backfill') {
|
|
582
|
+
const dirArg = args[1];
|
|
583
|
+
if (!dirArg) { err('usage: bareloop runs backfill <dir>'); return 1; }
|
|
584
|
+
const dir = resolve(cwd, dirArg);
|
|
585
|
+
if (!existsSync(dir)) { err(`no such directory: ${dir}`); return 1; }
|
|
586
|
+
let result;
|
|
587
|
+
try {
|
|
588
|
+
result = backfillRuns(dir);
|
|
589
|
+
} catch (e) {
|
|
590
|
+
err(`backfill failed: ${/** @type {Error} */ (e).message}`);
|
|
591
|
+
return 1;
|
|
592
|
+
}
|
|
593
|
+
out(`backfill ${dir}: added ${result.added}, already listed ${result.alreadyListed}, skipped ${result.skipped} (not a spine or unreadable)`);
|
|
594
|
+
return 0;
|
|
595
|
+
}
|
|
596
|
+
const { rows, skipped } = readRunList();
|
|
597
|
+
if (rows.length === 0) { out('runs: (none listed yet — see `bareloop runs backfill <dir>`)'); }
|
|
598
|
+
else for (const row of rows) out(formatRunRow(row));
|
|
599
|
+
if (skipped > 0) out(`(${skipped} malformed line${skipped === 1 ? '' : 's'} skipped)`);
|
|
600
|
+
return 0;
|
|
601
|
+
}
|
|
602
|
+
|
|
466
603
|
/**
|
|
467
604
|
* The bare `bareloop` menu: `1 export 2 run 3 history q quit`, then the
|
|
468
|
-
* SAME code path as the sub-command, asking its args line by line.
|
|
605
|
+
* SAME code path as the sub-command, asking its args line by line. `run-u`,
|
|
606
|
+
* `interview`, `author` and `replay` are NOT wizarded here — a line-at-a-time
|
|
607
|
+
* prompt for `run-u`'s 13 flags (or the others' own argv shapes) would be
|
|
608
|
+
* inventing new interactive UX, not wiring, and that judgement stands. This
|
|
609
|
+
* menu is fixed instead: it now says those four commands exist and how to
|
|
610
|
+
* reach them, rather than implying only three commands do at all.
|
|
469
611
|
* @param {any} deps @param {{ out: (s: string) => void, err: (s: string) => void, cwd: string, env: any, now: () => number }} ctx
|
|
470
612
|
*/
|
|
471
613
|
async function runMenu(deps, ctx) {
|
|
@@ -474,6 +616,7 @@ async function runMenu(deps, ctx) {
|
|
|
474
616
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
475
617
|
try {
|
|
476
618
|
ctx.out('1 export 2 run 3 history q quit');
|
|
619
|
+
ctx.out('(also on the command line, not wizarded here: run-u, interview, author, replay, runs, panel — run `bareloop <name>` with its own flags)');
|
|
477
620
|
const choice = (await rl.question('> ')).trim().toLowerCase();
|
|
478
621
|
if (choice === '' || choice === 'q' || choice === 'quit') return 0;
|
|
479
622
|
if (choice === '1') {
|
|
@@ -510,7 +653,7 @@ async function runMenu(deps, ctx) {
|
|
|
510
653
|
* code, so `bin/bareloop.mjs` can set `process.exitCode` and let node flush
|
|
511
654
|
* queued stdout on its own (F-something: `process.exit()` can discard it).
|
|
512
655
|
* @param {string[]} argv
|
|
513
|
-
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, judgeModel?: string|null, now?: () => number, stdin?: any }} deps
|
|
656
|
+
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, judgeModel?: string|null, now?: () => number, stdin?: any, runlistHome?: string }} deps
|
|
514
657
|
* @returns {Promise<number>}
|
|
515
658
|
*/
|
|
516
659
|
export async function main(argv, deps = {}) {
|
|
@@ -528,6 +671,45 @@ export async function main(argv, deps = {}) {
|
|
|
528
671
|
if (cmd === 'export') return doExport(rest, ctx);
|
|
529
672
|
if (cmd === 'run') return doRun(rest, ctx);
|
|
530
673
|
if (cmd === 'history') return doHistory(rest, ctx);
|
|
531
|
-
|
|
674
|
+
// `bareloop run-u` — the person-path run flow (PANEL-BUILD.md P0 task
|
|
675
|
+
// 2/4). `rest` is handed straight to `src/userrun.js`'s own `main(argv,
|
|
676
|
+
// deps)`, unparsed: that function is this flag grammar's one owner
|
|
677
|
+
// (`--job`/`--spec`/`--resume`/`--door`/`--decide`/`--text`/
|
|
678
|
+
// `--review-door`/`--approve`/`--registry`/`--workflow`/`--model`/
|
|
679
|
+
// `--read-shim`/`--scout`). `deps` is spread first so an injected test
|
|
680
|
+
// seam (`deps.provider`/`providerFor`/`judgeProvider`) still reaches it,
|
|
681
|
+
// then `env`/`out`/`err` are overridden to the SAME resolved values every
|
|
682
|
+
// other command here prints through, so `run-u`'s output lands on the
|
|
683
|
+
// `stdout`/`stderr` a caller of `main` actually passed.
|
|
684
|
+
if (cmd === 'run-u') return runUMain(rest, { ...deps, env, out, err });
|
|
685
|
+
// `bareloop interview` — the close-authoring interview (PANEL-BUILD.md P0
|
|
686
|
+
// task 3/4). This flow reads a TTY (or a piped stdin) directly rather than
|
|
687
|
+
// through the `out`/`err` line-functions every other command here uses, so
|
|
688
|
+
// it needs the raw streams, not the wrapped ones — `stdout`/`stderr` (both
|
|
689
|
+
// already resolved above) are handed through instead.
|
|
690
|
+
// `invokedAs: 'bareloop interview'` — the usage message names the command a
|
|
691
|
+
// person actually typed, never a script path they never invoked and may
|
|
692
|
+
// not have on disk (a branch-review nit; `scripts/run-interview.mjs` still
|
|
693
|
+
// supplies none, so it keeps naming itself when run directly).
|
|
694
|
+
if (cmd === 'interview') return interviewMain(rest, { ...deps, env, stdin: deps.stdin ?? process.stdin, stdout, stderr, invokedAs: 'bareloop interview' });
|
|
695
|
+
// `bareloop author` — the authoring pipeline (scout, declaration, D9's
|
|
696
|
+
// gates), same task. Same raw-stream reasoning: its one interactive seam
|
|
697
|
+
// (the confirm turn) reads stdin directly. Same `invokedAs` reasoning too.
|
|
698
|
+
if (cmd === 'author') return authorMain(rest, { ...deps, env, stdin: deps.stdin ?? process.stdin, stdout, stderr, invokedAs: 'bareloop author' });
|
|
699
|
+
// `bareloop replay` — the spine/gate-audit read side (PANEL-BUILD.md P0,
|
|
700
|
+
// last of the rung's four tasks). Synchronous, file-based, no interactive
|
|
701
|
+
// seam — same shape as `doHistory`/`doExport`, not the argv-owning
|
|
702
|
+
// `main(argv, deps)` modules the flows above delegate to.
|
|
703
|
+
if (cmd === 'replay') return doReplay(rest, ctx);
|
|
704
|
+
// `bareloop runs` / `bareloop runs backfill <dir>` — PANEL-BUILD.md P1's
|
|
705
|
+
// one run list. Read-only ($0): no interview, no author, no run trigger,
|
|
706
|
+
// no key ever read.
|
|
707
|
+
if (cmd === 'runs') return doRuns(rest, ctx);
|
|
708
|
+
// `bareloop panel [--port N]` — PANEL-BUILD.md P1's read-only HTTP panel.
|
|
709
|
+
// Never runs a job, spends money, or reads a key: it serves the run list
|
|
710
|
+
// and spine/gate-audit reads over `127.0.0.1` only. `deps.runlistHome`
|
|
711
|
+
// rides through the same injectable seam `run`/`run-u` already use.
|
|
712
|
+
if (cmd === 'panel') return panelMain(rest, { out, err, runlistHome: deps.runlistHome });
|
|
713
|
+
err(`unknown command ${JSON.stringify(cmd)} — one of: export, run, history, run-u, interview, author, replay, runs, panel`);
|
|
532
714
|
return 1;
|
|
533
715
|
}
|
package/src/index.js
CHANGED
|
@@ -237,7 +237,19 @@ export { runBehaviour, formatBehaviour } from './behaviour.js';
|
|
|
237
237
|
// (`replayRun`) or one printable page (`formatReplay`). Reads only, mints no
|
|
238
238
|
// verdict, writes nothing to the spine — reuses `runBehaviour` rather than
|
|
239
239
|
// re-implementing the tool-call count.
|
|
240
|
-
export { replayRun, formatReplay } from './replay.js';
|
|
240
|
+
export { replayRun, formatReplay, summarizeForAllLine, formatAllLines } from './replay.js';
|
|
241
|
+
// PANEL-BUILD.md P0 — the IO layer over `replayRun`: read one run's spine
|
|
242
|
+
// (+ its gate-audit sibling) off disk, or list every spine directly in a
|
|
243
|
+
// directory (content-detected, never by filename pattern — see the module
|
|
244
|
+
// header) as `--all`-style rows. `readHistoryLog` is the one reader for a
|
|
245
|
+
// bundle's `history.jsonl`, replacing `src/cli.js`'s own former hand-rolled
|
|
246
|
+
// read. `src/replayio.js` also exports lower-level plumbing this package
|
|
247
|
+
// root deliberately does NOT re-export (`parseJsonl`, `isSidecarByName`,
|
|
248
|
+
// `looksLikeSpine`, `resolveSiblings`): a raw JSONL parser, a filename
|
|
249
|
+
// predicate, and a sibling-path resolver are internal steps these three
|
|
250
|
+
// functions already compose for a caller — not their own adopter-facing
|
|
251
|
+
// promise, and an export here is a promise kept forever.
|
|
252
|
+
export { replayOne, listSpines, readHistoryLog } from './replayio.js';
|
|
241
253
|
// M4b — the interview, the refusal, the composition, and D9's three gates.
|
|
242
254
|
// `prepareSigning` returns the resolved spec's HASH and the seed evidence; it
|
|
243
255
|
// never signs — the approvals array and the human's word are unchanged.
|