codehere 0.8.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,127 +0,0 @@
1
- /**
2
- * Task deliverable · the CLI counterpart to the web UI session card.
3
- *
4
- * After a task finishes, the non-technical user needs the SAME thing they get
5
- * in the dashboard, just in the terminal: what the agent did (Final Report),
6
- * which files changed + the trust score, whether the report's claims check out
7
- * (verifier), and one obvious next step. No raw event types, no jargon.
8
- *
9
- * Shared by `task --watch` (print on completion) and the daemon-fetch path so
10
- * both surfaces say the same honest thing. Per feedback_no_screen_lies: every
11
- * line here traces to recorded state — we never print a success we can't prove.
12
- */
13
- import http from 'http';
14
- import { colors } from '../../ui.js';
15
- /** Pull one session's full result from a running daemon's /api/tasks. Matches
16
- * by exact id or short prefix. Returns null if the daemon is unreachable or
17
- * the id isn't found. */
18
- export function fetchSessionResult(base, sessionId) {
19
- return new Promise((resolve) => {
20
- const r = http.get(`${base}/api/tasks`, (res) => {
21
- let buf = '';
22
- res.on('data', (c) => {
23
- buf += c.toString();
24
- });
25
- res.on('end', () => {
26
- try {
27
- const j = JSON.parse(buf);
28
- const all = [...(j.spawned ?? []), ...(j.persisted ?? [])];
29
- const exact = all.find((s) => s.sessionId === sessionId);
30
- resolve(exact ?? all.find((s) => s.sessionId.startsWith(sessionId)) ?? null);
31
- }
32
- catch {
33
- resolve(null);
34
- }
35
- });
36
- });
37
- r.on('error', () => resolve(null));
38
- });
39
- }
40
- /** Poll the daemon until the Final Report lands (or we give up). The report is
41
- * generated asynchronously after the agent exits, so a single fetch right at
42
- * 'exit' often misses it. */
43
- export async function waitForDeliverable(base, sessionId, opts = {}) {
44
- // Real agents take ~30-60s to synthesize the Final Report after the agent
45
- // exits (mock is instant). 90s covers a real claude run without giving up early.
46
- const timeoutMs = opts.timeoutMs ?? 90_000;
47
- const intervalMs = opts.intervalMs ?? 500;
48
- const deadline = Date.now() + timeoutMs;
49
- let last = null;
50
- while (Date.now() < deadline) {
51
- last = await fetchSessionResult(base, sessionId);
52
- if (last && (last.finalReport || last.failedReport || last.status === 'error' || last.status === 'killed')) {
53
- return last;
54
- }
55
- await new Promise((r) => setTimeout(r, intervalMs));
56
- }
57
- return last;
58
- }
59
- /** Plain-English deliverable block. The terminal twin of the dashboard card. */
60
- export function formatDeliverable(r) {
61
- const lines = [];
62
- const bar = colors.dim('─'.repeat(60));
63
- const verb = r.status === 'completed'
64
- ? 'Done'
65
- : r.status === 'error'
66
- ? 'Failed'
67
- : r.status === 'killed'
68
- ? 'Stopped'
69
- : 'Finished';
70
- lines.push('');
71
- lines.push(bar);
72
- lines.push(`${colors.bold(verb)} · ${r.agent} · ${colors.dim(r.sessionId.slice(0, 8))}`);
73
- lines.push(bar);
74
- if (r.stalledReason) {
75
- lines.push(colors.warning(`The agent stalled: ${r.stalledReason}`));
76
- }
77
- // 1 · What the agent did (the Final Report narrative).
78
- if (r.finalReport?.summary) {
79
- lines.push(r.finalReport.summary.trim());
80
- }
81
- else if (r.failedReport) {
82
- lines.push(colors.dim("codehere couldn't write a report for this run."));
83
- }
84
- else if (r.status === 'error') {
85
- lines.push(colors.dim('The agent ran into an error before finishing.'));
86
- }
87
- else {
88
- lines.push(colors.dim('No report yet.'));
89
- }
90
- // 2 · Files changed + trust score (or the honest can't-track note).
91
- const files = r.artifacts?.files ?? [];
92
- if (r.artifacts?.noGitTracking) {
93
- lines.push('');
94
- lines.push(colors.dim("Files: codehere couldn't track changes here (this folder isn't a git repository)."));
95
- }
96
- else if (files.length > 0) {
97
- lines.push('');
98
- lines.push(colors.bold('Files changed:'));
99
- for (const f of files) {
100
- const trust = typeof f.trust === 'number' ? ` · trust ${f.trust}/100` : '';
101
- lines.push(` • ${f.path} ${colors.success('+' + f.added)}/${colors.error('-' + f.removed)}${colors.dim(trust)}`);
102
- }
103
- }
104
- else {
105
- lines.push('');
106
- lines.push(colors.dim('Files: none changed.'));
107
- }
108
- // 3 · Did the report's claims check out? (verifier cross-check)
109
- if (r.verifyResult) {
110
- const v = r.verifyResult;
111
- const label = v.status === 'verified'
112
- ? colors.success('verified')
113
- : v.status === 'broken'
114
- ? colors.error('does not match reality')
115
- : v.status === 'partial'
116
- ? colors.warning('partly unverifiable')
117
- : colors.dim('nothing to check');
118
- lines.push('');
119
- lines.push(`${colors.bold('Check:')} ${label} — ${v.summary}`);
120
- for (const f of v.findings.filter((x) => x.severity !== 'info')) {
121
- lines.push(colors.dim(` • ${f.message}`));
122
- }
123
- }
124
- lines.push(bar);
125
- return lines.join('\n');
126
- }
127
- //# sourceMappingURL=task-deliverable.js.map
@@ -1,125 +0,0 @@
1
- /**
2
- * Cassette schema (LOCKED — do not modify; see docs/TEST_PLAN_v0.7.md §3.L3b).
3
- *
4
- * JSONL at `agent/test/cassettes/<adapter>/<scenario>-YYYY-MM-DD.jsonl`. One line per chunk.
5
- *
6
- * Line 1 (header):
7
- * {"t":0,"meta":{"adapter":"<id>","scenario":"<id>","captured_at":"<ISO>","binary_version":"<x.y.z|synthetic>","origin":"real"|"synthetic","prompt":"<original>","cwd_hint":"<relpath>","schema":1}}
8
- * Line 2..N (events):
9
- * {"t":<ms-from-start>,"stream":"stdout"|"stderr","data":"<raw bytes, newlines preserved>"}
10
- * Line N+1 (term):
11
- * {"t":<ms-from-start>,"exit_code":<int>,"signal":<string|null>}
12
- *
13
- * Loaders validate shape and reject malformed cassettes loudly — a silently-
14
- * misparsed cassette would produce a false-green test, defeating L3b's point.
15
- */
16
- import { readFileSync } from 'fs';
17
- function isObject(x) {
18
- return typeof x === 'object' && x !== null && !Array.isArray(x);
19
- }
20
- function parseLine(line, lineNo, path) {
21
- try {
22
- return JSON.parse(line);
23
- }
24
- catch (err) {
25
- const msg = err instanceof Error ? err.message : String(err);
26
- throw new Error(`Cassette ${path} line ${lineNo}: invalid JSON (${msg})`, { cause: err });
27
- }
28
- }
29
- /** Read a JSONL cassette from disk, validate shape, return structured form. */
30
- export function loadCassette(path) {
31
- const raw = readFileSync(path, 'utf-8');
32
- const rawLines = raw.split('\n');
33
- const lines = [];
34
- for (let i = 0; i < rawLines.length; i++) {
35
- const line = rawLines[i].trim();
36
- if (line)
37
- lines.push({ line, no: i + 1 });
38
- }
39
- if (lines.length < 2) {
40
- throw new Error(`Cassette ${path}: needs at least header + terminal lines (got ${lines.length})`);
41
- }
42
- // Header
43
- const headerRaw = parseLine(lines[0].line, lines[0].no, path);
44
- if (!isObject(headerRaw) || headerRaw.t !== 0 || !isObject(headerRaw.meta)) {
45
- throw new Error(`Cassette ${path} line ${lines[0].no}: header must be {"t":0,"meta":{...}}`);
46
- }
47
- const meta = headerRaw.meta;
48
- const requiredMeta = [
49
- 'adapter',
50
- 'scenario',
51
- 'captured_at',
52
- 'binary_version',
53
- 'origin',
54
- 'prompt',
55
- 'cwd_hint',
56
- 'schema',
57
- ];
58
- for (const k of requiredMeta) {
59
- if (!(k in meta)) {
60
- throw new Error(`Cassette ${path}: header meta missing required field "${k}"`);
61
- }
62
- }
63
- if (meta.schema !== 1) {
64
- throw new Error(`Cassette ${path}: schema must be 1, got ${String(meta.schema)}`);
65
- }
66
- if (meta.origin !== 'real' && meta.origin !== 'synthetic') {
67
- throw new Error(`Cassette ${path}: meta.origin must be "real" or "synthetic"`);
68
- }
69
- // Terminal (last line)
70
- const termEntry = lines[lines.length - 1];
71
- const termRaw = parseLine(termEntry.line, termEntry.no, path);
72
- if (!isObject(termRaw) || typeof termRaw.t !== 'number' || !('exit_code' in termRaw) || !('signal' in termRaw)) {
73
- throw new Error(`Cassette ${path} line ${termEntry.no}: terminal must be {"t":...,"exit_code":...,"signal":...}`);
74
- }
75
- if (typeof termRaw.exit_code !== 'number') {
76
- throw new Error(`Cassette ${path} line ${termEntry.no}: terminal.exit_code must be a number`);
77
- }
78
- if (termRaw.signal !== null && typeof termRaw.signal !== 'string') {
79
- throw new Error(`Cassette ${path} line ${termEntry.no}: terminal.signal must be string or null`);
80
- }
81
- // Middle: events
82
- const events = [];
83
- for (let i = 1; i < lines.length - 1; i++) {
84
- const { line, no } = lines[i];
85
- const ev = parseLine(line, no, path);
86
- if (!isObject(ev) || typeof ev.t !== 'number') {
87
- throw new Error(`Cassette ${path} line ${no}: event must have numeric "t"`);
88
- }
89
- if (ev.stream !== 'stdout' && ev.stream !== 'stderr') {
90
- throw new Error(`Cassette ${path} line ${no}: event.stream must be "stdout" or "stderr"`);
91
- }
92
- if (typeof ev.data !== 'string') {
93
- throw new Error(`Cassette ${path} line ${no}: event.data must be a string`);
94
- }
95
- events.push({ t: ev.t, stream: ev.stream, data: ev.data });
96
- }
97
- const cassette = {
98
- header: { t: 0, meta: meta },
99
- events,
100
- terminal: {
101
- t: termRaw.t,
102
- exit_code: termRaw.exit_code,
103
- signal: termRaw.signal,
104
- },
105
- };
106
- validateCassette(cassette);
107
- return cassette;
108
- }
109
- /** Sanity-check ordering invariants. Throws on violation. */
110
- export function validateCassette(c) {
111
- if (c.header.t !== 0)
112
- throw new Error('Cassette: header.t must be 0');
113
- let prev = 0;
114
- for (let i = 0; i < c.events.length; i++) {
115
- const t = c.events[i].t;
116
- if (t < prev) {
117
- throw new Error(`Cassette: events out of order at index ${i} (t=${t} < prev=${prev})`);
118
- }
119
- prev = t;
120
- }
121
- if (c.terminal.t < prev) {
122
- throw new Error(`Cassette: terminal.t (${c.terminal.t}) must be >= last event.t (${prev})`);
123
- }
124
- }
125
- //# sourceMappingURL=cassette.js.map
@@ -1,166 +0,0 @@
1
- /**
2
- * L3b cassette byte-replay shim — see docs/TEST_PLAN_v0.7.md §3.L3b.
3
- *
4
- * This module swaps `child_process.spawn` so a real adapter's stdout/stderr/
5
- * exit handlers receive bytes from a cassette instead of a live subprocess.
6
- * The adapter under test is the production code path — only its boundary with
7
- * the OS process layer is substituted. That keeps L3b honest about the parser
8
- * + state-machine behavior the adapter exhibits on real captured bytes.
9
- *
10
- * Why a module-level "active cassette" instead of dependency injection:
11
- * `vi.mock('child_process', factory)` hoists to module load and runs once.
12
- * The factory has no way to receive a per-test cassette argument. The factory
13
- * therefore reads `getActiveCassette()` lazily at every `spawn()` call. Tests
14
- * call `setActiveCassette(loadCassette(path))` in `beforeEach`, then call
15
- * `SessionManager.getInstance().spawn(...)` to drive the adapter.
16
- *
17
- * Honest call-out: this approach mocks the entire `child_process` import in
18
- * the test file. The same factory wraps `execFileSync` so adapter `which X`
19
- * probes succeed even when the binary is absent on the test host. Anything
20
- * else (git, ps) passes through to the real implementation.
21
- *
22
- * The synthetic `ChildProcess` honors what the per-adapter spawn boundary
23
- * actually touches per `/tmp/l3b-spawn-boundary-map.json`:
24
- * - `stdout` / `stderr` Readable that emit `data` events at the cassette `t`s
25
- * - `stdin` writable that swallows writes (codex's prompt write is recorded
26
- * by the capture-cassette script on the spawn side; stdin replay deferred)
27
- * - `exit` then `close` at the terminal `t`
28
- * - `kill(signal)` cancels pending timers and emits exit with that signal
29
- * - `pid` set to a fake non-zero integer
30
- * - `unref()` / `ref()` no-ops (gemini detached path)
31
- */
32
- import { EventEmitter } from 'events';
33
- import { Readable, Writable } from 'stream';
34
- import { loadCassette } from './cassette.js';
35
- // ── Active cassette holder (single-cassette-per-test pattern) ───────────────
36
- let _activeCassette;
37
- export function setActiveCassette(c) {
38
- _activeCassette = c;
39
- }
40
- export function getActiveCassette() {
41
- return _activeCassette;
42
- }
43
- let _fakePidCounter = 1_000_000;
44
- /** Drop-in replacement for `child_process.spawn`. Plays the cassette through
45
- * a synthetic ChildProcess; ignores cmd/args/options because the cassette
46
- * describes the stream end-to-end. */
47
- export function createReplaySpawn(cassette) {
48
- const replaySpawn = ((_cmd, _args, _opts) => {
49
- return makeReplayChild(cassette);
50
- });
51
- return replaySpawn;
52
- }
53
- function makeReplayChild(cassette) {
54
- const child = new EventEmitter();
55
- const timers = [];
56
- let killed = false;
57
- // Readable streams. We push() bytes manually as timers fire; no _read backpressure
58
- // because the adapter parsers consume eagerly via 'data' events.
59
- const stdout = new Readable({ read() { } });
60
- const stderr = new Readable({ read() { } });
61
- const stdin = new Writable({
62
- write(_chunk, _enc, cb) {
63
- cb();
64
- },
65
- final(cb) {
66
- cb();
67
- },
68
- });
69
- Object.defineProperty(child, 'stdout', { value: stdout, configurable: true });
70
- Object.defineProperty(child, 'stderr', { value: stderr, configurable: true });
71
- Object.defineProperty(child, 'stdin', { value: stdin, configurable: true });
72
- Object.defineProperty(child, 'pid', { value: ++_fakePidCounter, configurable: true });
73
- Object.defineProperty(child, 'killed', {
74
- get: () => killed,
75
- configurable: true,
76
- });
77
- // child_process API surface adapters touch (per /tmp/l3b-spawn-boundary-map.json).
78
- child.kill = ((signal) => {
79
- if (killed)
80
- return false;
81
- killed = true;
82
- for (const t of timers)
83
- clearTimeout(t);
84
- timers.length = 0;
85
- const sig = typeof signal === 'string' ? signal : 'SIGTERM';
86
- queueMicrotask(() => {
87
- try {
88
- stdout.push(null);
89
- }
90
- catch {
91
- /* readable already ended */
92
- }
93
- try {
94
- stderr.push(null);
95
- }
96
- catch {
97
- /* readable already ended */
98
- }
99
- child.emit('exit', null, sig);
100
- child.emit('close', null, sig);
101
- });
102
- return true;
103
- });
104
- child.unref = (() => child);
105
- child.ref = (() => child);
106
- child.disconnect = (() => {
107
- /* noop for replay */
108
- });
109
- // Schedule cassette events. setTimeout is fine for replay — Vitest doesn't
110
- // need fake timers; the test waits on the session reaching an exited status.
111
- for (const ev of cassette.events) {
112
- const handle = setTimeout(() => {
113
- if (killed)
114
- return;
115
- const stream = ev.stream === 'stdout' ? stdout : stderr;
116
- try {
117
- stream.push(Buffer.from(ev.data, 'utf-8'));
118
- }
119
- catch {
120
- /* stream ended early — ignore */
121
- }
122
- }, ev.t);
123
- timers.push(handle);
124
- }
125
- // Terminal.
126
- const termHandle = setTimeout(() => {
127
- if (killed)
128
- return;
129
- try {
130
- stdout.push(null);
131
- }
132
- catch {
133
- /* ignore */
134
- }
135
- try {
136
- stderr.push(null);
137
- }
138
- catch {
139
- /* ignore */
140
- }
141
- const code = cassette.terminal.signal ? null : cassette.terminal.exit_code;
142
- child.emit('exit', code, cassette.terminal.signal);
143
- child.emit('close', code, cassette.terminal.signal);
144
- }, cassette.terminal.t);
145
- timers.push(termHandle);
146
- return child;
147
- }
148
- /** Test convenience: load cassette, set it active, spawn via SessionManager.
149
- *
150
- * Callers MUST install `vi.mock('child_process', factory)` at the top of
151
- * their test file with a factory that delegates `spawn` to
152
- * `createReplaySpawn(getActiveCassette())`. See cassette-replay.test.ts
153
- * for the canonical setup.
154
- *
155
- * Returns the spawned TaskSession (typed loosely to avoid pulling the
156
- * whole adapter type tree into src/testing/).
157
- */
158
- export async function replayThroughAdapter(cassettePath, sessionManager) {
159
- const cassette = loadCassette(cassettePath);
160
- setActiveCassette(cassette);
161
- const session = sessionManager.spawn(cassette.header.meta.prompt, cassette.header.meta.adapter, {
162
- cwd: cassette.header.meta.cwd_hint,
163
- });
164
- return session;
165
- }
166
- //# sourceMappingURL=replay-adapter.js.map