codehere 1.0.1 → 1.0.3

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 (163) hide show
  1. package/README.md +35 -193
  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 +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,153 +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
- import { VERDICT_WORDS, corroborationClause } from '../../application/services/verdict-service.js';
16
- /** Pull one session's full result from a running daemon's /api/tasks. Matches
17
- * by exact id or short prefix. Returns null if the daemon is unreachable or
18
- * the id isn't found. */
19
- export function fetchSessionResult(base, sessionId) {
20
- return new Promise((resolve) => {
21
- const r = http.get(`${base}/api/tasks`, (res) => {
22
- let buf = '';
23
- res.on('data', (c) => {
24
- buf += c.toString();
25
- });
26
- res.on('end', () => {
27
- try {
28
- const j = JSON.parse(buf);
29
- const all = [...(j.spawned ?? []), ...(j.persisted ?? [])];
30
- const exact = all.find((s) => s.sessionId === sessionId);
31
- resolve(exact ?? all.find((s) => s.sessionId.startsWith(sessionId)) ?? null);
32
- }
33
- catch {
34
- resolve(null);
35
- }
36
- });
37
- });
38
- r.on('error', () => resolve(null));
39
- });
40
- }
41
- /** Poll the daemon until the Final Report lands (or we give up). The report is
42
- * generated asynchronously after the agent exits, so a single fetch right at
43
- * 'exit' often misses it. */
44
- export async function waitForDeliverable(base, sessionId, opts = {}) {
45
- // Real agents take ~30-60s to synthesize the Final Report after the agent
46
- // exits (mock is instant). 90s covers a real claude run without giving up early.
47
- const timeoutMs = opts.timeoutMs ?? 90_000;
48
- const intervalMs = opts.intervalMs ?? 500;
49
- const deadline = Date.now() + timeoutMs;
50
- let last = null;
51
- while (Date.now() < deadline) {
52
- last = await fetchSessionResult(base, sessionId);
53
- if (last && (last.finalReport || last.failedReport || last.status === 'error' || last.status === 'killed')) {
54
- return last;
55
- }
56
- await new Promise((r) => setTimeout(r, intervalMs));
57
- }
58
- return last;
59
- }
60
- /** Plain-English deliverable block. The terminal twin of the dashboard card. */
61
- export function formatDeliverable(r) {
62
- const lines = [];
63
- const bar = colors.dim('─'.repeat(60));
64
- const verb = r.status === 'completed'
65
- ? 'Done'
66
- : r.status === 'error'
67
- ? 'Failed'
68
- : r.status === 'killed'
69
- ? 'Stopped'
70
- : 'Finished';
71
- lines.push('');
72
- lines.push(bar);
73
- lines.push(`${colors.bold(verb)} · ${r.agent} · ${colors.dim(r.sessionId.slice(0, 8))}`);
74
- lines.push(bar);
75
- if (r.stalledReason) {
76
- lines.push(colors.warning(`The agent stalled: ${r.stalledReason}`));
77
- }
78
- // 1 · What the agent did (the Final Report narrative).
79
- if (r.finalReport?.summary) {
80
- lines.push(r.finalReport.summary.trim());
81
- }
82
- else if (r.failedReport) {
83
- lines.push(colors.dim("codehere couldn't write a report for this run."));
84
- }
85
- else if (r.status === 'error') {
86
- lines.push(colors.dim('The agent ran into an error before finishing.'));
87
- }
88
- else {
89
- lines.push(colors.dim('No report yet.'));
90
- }
91
- // 2 · Files changed + trust score (or the honest can't-track note).
92
- const files = r.artifacts?.files ?? [];
93
- if (r.artifacts?.noGitTracking) {
94
- lines.push('');
95
- lines.push(colors.dim("Files: codehere couldn't track changes here (this folder isn't a git repository)."));
96
- }
97
- else if (files.length > 0) {
98
- lines.push('');
99
- lines.push(colors.bold('Files changed:'));
100
- for (const f of files) {
101
- const trust = typeof f.trust === 'number' ? ` · trust ${f.trust}/100` : '';
102
- lines.push(` • ${f.path} ${colors.success('+' + f.added)}/${colors.error('-' + f.removed)}${colors.dim(trust)}`);
103
- }
104
- }
105
- else {
106
- lines.push('');
107
- lines.push(colors.dim('Files: none changed.'));
108
- }
109
- // 3 · Did the report's claims check out? (verifier cross-check)
110
- if (r.verdict?.claim) {
111
- const v = r.verdict.claim;
112
- // Read the one reconciled verdict (the composite grade owns it), falling back
113
- // to the raw claim status only for legacy/seeded tasks with no grade. The
114
- // words come from the shared VERDICT_WORDS so the CLI says exactly what the
115
- // web app says for the same state — a green composite grade reads "Verified"
116
- // (a single cross-vendor review CORROBORATES; the CLI never says "Proven"
117
- // for one reviewer, matching the web seal's "Corroborated by <vendor>").
118
- const byHeadline = {
119
- proven: 'verified',
120
- broken: 'caught',
121
- reported: 'reported',
122
- 'nothing-to-verify': 'unverified',
123
- 'no-report': 'unverified',
124
- };
125
- const byStatus = {
126
- verified: 'verified',
127
- broken: 'caught',
128
- partial: 'partial',
129
- };
130
- const grade = r.verdict.grade;
131
- const key = (grade && byHeadline[grade.headline]) || byStatus[v.status] || 'unverified';
132
- const tone = key === 'proven' || key === 'verified'
133
- ? colors.success
134
- : key === 'caught'
135
- ? colors.error
136
- : key === 'reported' || key === 'partial'
137
- ? colors.warning
138
- : colors.dim;
139
- // For a green composite grade the sub-clause names the ACTUAL independent
140
- // signal that earned it (matching the web seal) — not the claim summary,
141
- // which for a nothing-to-verify claim would contradict the green ("Verified
142
- // · the report makes no checkable claim").
143
- const sub = grade && grade.green ? corroborationClause(grade) : v.summary;
144
- lines.push('');
145
- lines.push(`${colors.bold('Check:')} ${tone(VERDICT_WORDS[key])} · ${sub}`);
146
- for (const f of v.findings.filter((x) => x.severity !== 'info')) {
147
- lines.push(colors.dim(` • ${f.message}`));
148
- }
149
- }
150
- lines.push(bar);
151
- return lines.join('\n');
152
- }
153
- //# 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,147 +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
- // ── Active cassette holder (single-cassette-per-test pattern) ───────────────
35
- let _activeCassette;
36
- export function setActiveCassette(c) {
37
- _activeCassette = c;
38
- }
39
- export function getActiveCassette() {
40
- return _activeCassette;
41
- }
42
- let _fakePidCounter = 1_000_000;
43
- /** Drop-in replacement for `child_process.spawn`. Plays the cassette through
44
- * a synthetic ChildProcess; ignores cmd/args/options because the cassette
45
- * describes the stream end-to-end. */
46
- export function createReplaySpawn(cassette) {
47
- const replaySpawn = ((_cmd, _args, _opts) => {
48
- return makeReplayChild(cassette);
49
- });
50
- return replaySpawn;
51
- }
52
- function makeReplayChild(cassette) {
53
- const child = new EventEmitter();
54
- const timers = [];
55
- let killed = false;
56
- // Readable streams. We push() bytes manually as timers fire; no _read backpressure
57
- // because the adapter parsers consume eagerly via 'data' events.
58
- const stdout = new Readable({ read() { } });
59
- const stderr = new Readable({ read() { } });
60
- const stdin = new Writable({
61
- write(_chunk, _enc, cb) {
62
- cb();
63
- },
64
- final(cb) {
65
- cb();
66
- },
67
- });
68
- Object.defineProperty(child, 'stdout', { value: stdout, configurable: true });
69
- Object.defineProperty(child, 'stderr', { value: stderr, configurable: true });
70
- Object.defineProperty(child, 'stdin', { value: stdin, configurable: true });
71
- Object.defineProperty(child, 'pid', { value: ++_fakePidCounter, configurable: true });
72
- Object.defineProperty(child, 'killed', {
73
- get: () => killed,
74
- configurable: true,
75
- });
76
- // child_process API surface adapters touch (per /tmp/l3b-spawn-boundary-map.json).
77
- child.kill = ((signal) => {
78
- if (killed)
79
- return false;
80
- killed = true;
81
- for (const t of timers)
82
- clearTimeout(t);
83
- timers.length = 0;
84
- const sig = typeof signal === 'string' ? signal : 'SIGTERM';
85
- queueMicrotask(() => {
86
- try {
87
- stdout.push(null);
88
- }
89
- catch {
90
- /* readable already ended */
91
- }
92
- try {
93
- stderr.push(null);
94
- }
95
- catch {
96
- /* readable already ended */
97
- }
98
- child.emit('exit', null, sig);
99
- child.emit('close', null, sig);
100
- });
101
- return true;
102
- });
103
- child.unref = (() => child);
104
- child.ref = (() => child);
105
- child.disconnect = (() => {
106
- /* noop for replay */
107
- });
108
- // Schedule cassette events. setTimeout is fine for replay — Vitest doesn't
109
- // need fake timers; the test waits on the session reaching an exited status.
110
- for (const ev of cassette.events) {
111
- const handle = setTimeout(() => {
112
- if (killed)
113
- return;
114
- const stream = ev.stream === 'stdout' ? stdout : stderr;
115
- try {
116
- stream.push(Buffer.from(ev.data, 'utf-8'));
117
- }
118
- catch {
119
- /* stream ended early — ignore */
120
- }
121
- }, ev.t);
122
- timers.push(handle);
123
- }
124
- // Terminal.
125
- const termHandle = setTimeout(() => {
126
- if (killed)
127
- return;
128
- try {
129
- stdout.push(null);
130
- }
131
- catch {
132
- /* ignore */
133
- }
134
- try {
135
- stderr.push(null);
136
- }
137
- catch {
138
- /* ignore */
139
- }
140
- const code = cassette.terminal.signal ? null : cassette.terminal.exit_code;
141
- child.emit('exit', code, cassette.terminal.signal);
142
- child.emit('close', code, cassette.terminal.signal);
143
- }, cassette.terminal.t);
144
- timers.push(termHandle);
145
- return child;
146
- }
147
- //# sourceMappingURL=replay-adapter.js.map