#!/usr/bin/env node /** * Phase 15 regression gate for B-003 (mid-turn / missed reinject). * * 1. Runs focused unit tests for source fallback, kept inject entries, mid-turn steer. * 2. Audits lost-reinject.jsonl (if present) for the pre-fix failure signature. * * Full two-compact RPC E2E needs a long session; use manual checklist in TODO.md §15. */ import { spawnSync } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { resolve, dirname } from "node:path"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const artifactPath = resolve(repoRoot, "lost-reinject.jsonl"); const unitPatterns = [ "test/compaction-source-fallback.test.ts", "test/kept-window.test.ts", "test/reinject-mid-turn.test.ts", "test/reinject-double-compact.test.ts", ]; function runUnitGate() { const result = spawnSync("npm", ["test", "--", ...unitPatterns], { cwd: repoRoot, stdio: "inherit", }); if (result.status !== 0) { process.exit(result.status ?? 1); } } function auditLostReinjectArtifact() { if (!existsSync(artifactPath)) { console.log("b003-repro: lost-reinject.jsonl not found — skip artifact audit"); return; } const lines = readFileSync(artifactPath, "utf8").split("\n").filter(Boolean); const stateEntries = []; const injectEntries = []; for (const line of lines) { try { const entry = JSON.parse(line); if (entry.customType === "skill-reinject:state" && entry.data) { stateEntries.push({ id: entry.id, ts: entry.timestamp, lastCompactionSource: entry.data.lastCompactionSource, pendingReinject: entry.data.pendingReinject ?? [], }); } if (entry.type === "custom_message" && entry.customType === "skill-reinject:inject") { injectEntries.push({ id: entry.id, ts: entry.timestamp }); } } catch { // skip malformed lines } } const nullSourceAfterAuto = stateEntries.filter( (entry, index) => index > 0 && entry.lastCompactionSource === null && stateEntries[index - 1]?.lastCompactionSource === "auto", ); console.log("b003-repro artifact audit:"); console.log(` state snapshots: ${stateEntries.length}`); console.log(` skill-reinject:inject messages: ${injectEntries.length}`); console.log(` auto→null source transitions (pre-fix bug): ${nullSourceAfterAuto.length}`); if (nullSourceAfterAuto.length > 0) { console.log(" sample null-source entry:", JSON.stringify(nullSourceAfterAuto[0])); console.log( " post-fix expectation: ensureCompactionSourceMarked on session_compact → lastCompactionSource: auto", ); } } console.log("b003-repro: running unit regression gate…"); runUnitGate(); auditLostReinjectArtifact(); console.log("b003-repro: PASS (unit gate)");