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.
- package/README.md +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- 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
|
package/dist/testing/cassette.js
DELETED
|
@@ -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
|