/* - Date: 2026-06-29 Spec: plans/2026-06-29-claude-code-raw-replay-harness-plan.md Relationship: Local replay entrypoint for raw agent fixtures. It loads one or more NDJSON fixtures, validates coverage, and writes static playground and JSON summary artifacts without starting live providers. */ import { mkdir, writeFile } from 'node:fs/promises'; import { basename, join, resolve } from 'node:path'; import { commanderReplayCoverageRequirements, commanderReplayRealCoverageRequirements, extractAgentReplayCoverage, missingAgentReplayCoverage, } from '../src/agentReplayCoverage'; import { loadAgentReplayFixture, type AgentReplayFixture, } from '../src/agentReplayFixture'; import { renderAgentReplayPlaygroundHtml } from '../src/agentReplayPlayground'; import { collectAgentReplayDiagnostics } from '../src/agentReplayDiagnostics'; import { evaluateAgentReplayFeatureReadiness, loadAgentReplayManifestForFixture, summarizeAgentReplayManifest, type AgentReplayFeatureReadiness, type AgentReplayManifestSummary, } from '../src/agentReplayManifest'; type ReplayCliOptions = { readonly fixtures: readonly string[]; readonly outDir: string; readonly coverageMode: 'none' | 'real' | 'full'; readonly html: boolean; readonly summary: boolean; readonly requiredFeatureId: string | undefined; }; async function main(): Promise { const args = process.argv.slice(2); if (args.includes('--help')) { printHelp(); return; } const options = parseArgs(args); const fixtures = await Promise.all(options.fixtures.map(loadFixture)); const manifestSummaries = await loadManifestSummaries(options.fixtures); const featureReadiness = await loadManifestReadiness(options.fixtures); await mkdir(options.outDir, { recursive: true }); if (options.html) { for (const fixture of fixtures) { const name = fixtureOutputName(fixture.path); await writeFile( join(options.outDir, `${name}.html`), renderAgentReplayPlaygroundHtml(fixture), 'utf8' ); } } const coverage = extractAgentReplayCoverage(fixtures); const diagnostics = combineDiagnostics( fixtures.map(collectAgentReplayDiagnostics) ); const requirements = options.coverageMode === 'real' ? commanderReplayRealCoverageRequirements : commanderReplayCoverageRequirements; const gaps = options.coverageMode === 'none' ? [] : missingAgentReplayCoverage(coverage, requirements); const requiredFeatureGate = evaluateRequiredFeatureGate( options.requiredFeatureId, featureReadiness ); const summary = { fixtures: fixtures.map((fixture) => ({ path: fixture.path, records: fixture.records.length, })), coverageMode: options.coverageMode, coverage: coverageSummary(coverage), featureCoverage: manifestSummaries, featureReadiness, diagnostics, gaps, requiredFeature: requiredFeatureGate, }; if (options.summary) { await writeFile( join(options.outDir, 'summary.json'), `${JSON.stringify(summary, null, 2)}\n`, 'utf8' ); } process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`); if ( gaps.length > 0 || diagnostics.droppedEvents.length > 0 || requiredFeatureGate.status === 'failed' ) { process.exitCode = 1; } } function combineDiagnostics( diagnostics: readonly ReturnType[] ): ReturnType { return { droppedEvents: diagnostics.flatMap((entry) => entry.droppedEvents), debugOnlyEvents: diagnostics.flatMap((entry) => entry.debugOnlyEvents), }; } function parseArgs(args: readonly string[]): ReplayCliOptions { const fixtures = valuesFor(args, '--fixture').map((path) => resolve(path)); if (fixtures.length === 0) { throw new Error('provide at least one --fixture '); } return { fixtures, outDir: resolve(argValue(args, '--out-dir') ?? 'agent-replay-output'), coverageMode: coverageModeValue(argValue(args, '--coverage') ?? 'full'), html: !args.includes('--no-html'), summary: !args.includes('--no-summary'), requiredFeatureId: argValue(args, '--require-feature'), }; } function coverageModeValue(value: string): ReplayCliOptions['coverageMode'] { switch (value) { case 'none': case 'real': case 'full': return value; default: throw new Error(`unsupported --coverage value: ${value}`); } } async function loadManifestSummaries( fixturePaths: readonly string[] ): Promise { const manifests: AgentReplayManifestSummary[] = []; for (const fixturePath of fixturePaths) { const loaded = await loadAgentReplayManifestForFixture(fixturePath); if (loaded === undefined) { continue; } if (loaded.type === 'error') { throw new Error(JSON.stringify(loaded.error)); } manifests.push(summarizeAgentReplayManifest(loaded.manifest)); } return manifests; } async function loadManifestReadiness( fixturePaths: readonly string[] ): Promise { const readiness: AgentReplayFeatureReadiness[] = []; for (const fixturePath of fixturePaths) { const loaded = await loadAgentReplayManifestForFixture(fixturePath); if (loaded === undefined) { continue; } if (loaded.type === 'error') { throw new Error(JSON.stringify(loaded.error)); } readiness.push(...evaluateAgentReplayFeatureReadiness(loaded.manifest)); } return readiness; } type RequiredFeatureGate = | { readonly status: 'not-required' } | { readonly status: 'passed'; readonly featureId: string; readonly readiness: AgentReplayFeatureReadiness; } | { readonly status: 'failed'; readonly featureId: string; readonly reason: string; readonly readiness?: AgentReplayFeatureReadiness | undefined; }; function evaluateRequiredFeatureGate( featureId: string | undefined, readiness: readonly AgentReplayFeatureReadiness[] ): RequiredFeatureGate { if (featureId === undefined) { return { status: 'not-required' }; } if (readiness.length === 0) { return { status: 'failed', featureId, reason: '--require-feature needs a neighboring fixture manifest', }; } const feature = readiness.find((row) => row.featureId === featureId); if (feature === undefined) { return { status: 'failed', featureId, reason: `feature ${featureId} is not present in the fixture manifests`, }; } switch (feature.status) { case 'implemented': case 'ready-to-implement': return { status: 'passed', featureId, readiness: feature }; case 'blocked': case 'missing-proof': case 'not-started': return { status: 'failed', featureId, readiness: feature, reason: feature.reason, }; } } async function loadFixture(path: string): Promise { const loaded = await loadAgentReplayFixture(path); if (loaded.type === 'error') { throw new Error(JSON.stringify(loaded.error)); } return loaded.fixture; } function valuesFor(args: readonly string[], name: string): readonly string[] { const values: string[] = []; for (let index = 0; index < args.length; index += 1) { if (args[index] === name) { const value = args[index + 1]; if (value === undefined) { throw new Error(`${name} requires a value`); } values.push(value); } } return values; } function argValue(args: readonly string[], name: string): string | undefined { const index = args.indexOf(name); if (index === -1) { return undefined; } const value = args[index + 1]; if (value === undefined) { throw new Error(`${name} requires a value`); } return value; } function fixtureOutputName(path: string): string { return basename(path).replace(/\.ndjson$/u, ''); } function coverageSummary( coverage: ReturnType ): Record { return { codexNotificationMethods: sorted(coverage.codexNotificationMethods), codexRequestMethods: sorted(coverage.codexRequestMethods), codexThreadItemTypes: sorted(coverage.codexThreadItemTypes), claudeSdkMessageTags: sorted(coverage.claudeSdkMessageTags), claudeContentBlockTypes: sorted(coverage.claudeContentBlockTypes), claudeToolNames: sorted(coverage.claudeToolNames), }; } function sorted(values: ReadonlySet): readonly string[] { return Array.from(values).sort(); } function printHelp(): void { process.stdout.write( `Usage: pnpm run replay:agent-fixture -- --fixture [--fixture ...] [options]\n\nOptions:\n --out-dir Artifact directory. Default: agent-replay-output\n --coverage none|real|full Coverage gate. Default: full\n --no-html Do not write per-fixture playground HTML\n --no-summary Do not write summary.json\n\nExamples:\n pnpm --filter @linzumi/cli run replay:agent-fixture -- \\\n --fixture test/fixtures/agent-raw-replay/real-probe/codex.ndjson \\\n --fixture test/fixtures/agent-raw-replay/real-probe/claude.ndjson \\\n --coverage real \\\n --out-dir /tmp/agent-replay-real\n` ); } main().catch((error) => { process.stderr.write( `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n` ); process.exitCode = 1; });