From 32e6f72eeeddda2a70f041568159ec77ab7e1e6d Mon Sep 17 00:00:00 2001 From: GRayHook Date: Thu, 18 Jun 2026 23:02:17 +0700 Subject: [PATCH] Phase 15: add B-003 regression script with unit gate and artifact audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run compaction fallback, kept inject, and mid-turn steer tests; document lost-reinject.jsonl auto→null source signature from the original bug. Co-authored-by: Cursor --- scripts/b003-repro.mjs | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 scripts/b003-repro.mjs diff --git a/scripts/b003-repro.mjs b/scripts/b003-repro.mjs new file mode 100755 index 0000000..ae73a23 --- /dev/null +++ b/scripts/b003-repro.mjs @@ -0,0 +1,82 @@ +#!/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)");