feat(cli): Restore-Packet vertical M2c-CLI — packet-writer atomic emission + write action impl

Per orch decision (Path B sub-chunked M2c; M2c-CLI chunk first per
qitem-20260502013530-1fad3a3d acknowledgment): land packet-writer +
restore-packet write action full impl + CLI defaults + 12 new round-trip
tests. M2c-Daemon (full-read transcript route + auth + redaction +
daemon route tests) ships separately.

Files (3 changed, 1 new, 1095+ insertions):

NEW packages/cli/src/restore-packet/packet-writer.ts (~280 lines):
- writePacket(opts) → atomically writes the v0 packet directory.
- Atomic emission: writes to mkdtemp sibling tempdir, schema-validates
  the resulting restore-summary.json against RESTORE_SUMMARY_SCHEMA
  (the M2a R2 const), then fs.renameSync tempdir → targetDir. On any
  validation failure (in-memory pre-check OR on-disk recheck after
  write) OR write error: rmSync the tempdir; targetDir is NEVER created;
  caller sees the error.
- Emits 4 required files (restore-instructions.md, transcript-latest.md,
  touched-files.md, restore-summary.json) per contract § 1; emits
  optional transcript.md when includeFullTranscript=true and adds the
  full_transcript summary key in lockstep (parity rule).
- Bounded transcript-latest at 120 messages (TRANSCRIPT_BOUND const,
  matching contract § 7's v0 fixed bound).
- restore-summary.json carries all 21 contract § 2.1 required fields:
  source_session_id / source_rig (derived from session_name@rig
  splitting) / source_runtime / source_cwd / target_rig / target_runtime
  / target_workspace_root / default_target_repo / role_pointer /
  bounded_latest_transcript / touched_files / durable_pointers (4
  required arrays, all empty by default — operator-supplied via flags
  in v1+) / current_work_summary / next_owner / caveats /
  authority_boundaries / omitted_classes / redaction_policy_id
  ("openrig-v0" const) / source_trust_ranking / generator_version /
  generated_at (ISO-8601 now).

EDIT packages/cli/src/commands/restore-packet.ts:
- Replaced the M2a-deferred error stub in the write action with full
  implementation:
  - Mutual-exclusion + missing-source checks preserved from M2a.
  - --source-jsonl branch: readFileSync → runtime-detect (or explicit
    --source-runtime override) → parseCodexJsonl/parseClaudeTranscript →
    writePacket.
  - --source-session branch: getDaemonStatus + getDaemonUrl + DaemonClient
    GET to /api/transcripts/<session>/full (the route M2c-Daemon will
    land); same parser dispatch + writePacket pipeline.
  - --source-runtime explicit value validated against codex/claude-code
    enum; auto-detect when omitted; explicit error when ambiguous.
  - 7 new CLI flags for the contract-required fields not derivable from
    parser output: --target-rig, --target-runtime, --target-workspace-root,
    --default-target-repo, --role-pointer, --current-work-summary,
    --next-owner, --caveat (repeatable), --authority-boundaries,
    --source-trust-ranking, --generator-version. Sensible defaults for
    each so the minimum command shape is `rig restore-packet write
    --source-jsonl <path> --target <dir>`.
  - Per Quality Lesson v9: log only metadata (target dir, file list,
    message count, omitted-class counts). Transcript content NEVER
    written to stdout/stderr.
- read + validate subcommand stubs unchanged (M3-pending).

EXTEND packages/cli/test/restore-packet.test.ts (+12 tests; 63 → 75):

- 6 packet-writer atomic emission tests:
  - 4 required files + summary schema-valid
  - transcript.md present when includeFullTranscript=true; full_transcript
    key in summary
  - all 21 contract § 2.1 required fields populated from operator opts
  - reject when target dir already exists (preserves marker.txt)
  - atomic emission: schema-validation failure mid-write → no target dir
    created AND tempdir cleaned up
  - transcript-latest bounded at 120; message_count reflects actual
    count (150-message input → message_count=120)
- 4 Velocity-shape round-trips (driver/guard/planner/tester roles):
  synthetic Codex JSONL fixtures emulating Velocity shape (session_meta
  + tool_call + reasoning + 2 messages with redaction needle); each role
  → parser → packet-writer → schema-valid; provenance preserved at
  source_session_id, source_cwd, bounded_latest_transcript.message_count;
  omitted_classes contains reasoning_records + function_call_output +
  redacted_secrets; transcript.md does NOT contain "sk-FakeAbCdEfGhIjKlMn"
  (redaction applied end-to-end).
- 1 mock-daemon --source-session round-trip (compact-plan.test.ts:166-176
  pattern): mock daemon-lifecycle module-level; mock client serves
  /api/transcripts/<session>/full with synthetic JSONL; CLI write
  command produces packet; mutation methods (post/delete/postText/
  postExpectText) NOT called.
- 1 M2b R2 nested Claude content round-trip: full assistant tool_use +
  user tool_result fixture → packet emitted → omitted_classes contains
  function_call_output + raw_tool_outputs; touched_files.top_paths
  includes the path from the omitted tool_result content.

TDD discipline (per memory feedback_tdd_scope): all M2c-CLI tests
authored to fail FIRST. Test commit: 11 fail (modules don't exist), 1
pass (the writer's "transcript-latest bounded" test was passing on
parser data alone). Then packet-writer + write action implemented; all
75 pass.

Re-verification gates (per dispatch):

- npx vitest run test/restore-packet.test.ts → 75/75 pass.
- npx tsc --noEmit → exit 0.
- npm run build → exit 0; dist/restore-packet/packet-writer.js +
  dist/schemas/restore-summary.schema.json present.
- Compiled-output check: node -e 'import(.../packet-writer.js).then(...)'
  → writePacket: function. (Schema-validator embedded const pattern from
  M2a R2 means the validator works after raw tsc emit; packet-writer
  uses it directly.)
- Full CLI suite: 715/719 pass. M2b R2 baseline 703/707 + 12 new M2c-CLI
  tests = 715/719. Same 4 named environmental flakes (chatroom 10s
  timing, config-store env pollution, preflight x2 host port 7433); none
  touch M2c-CLI files. NO regression.

Boundary discipline (per orch sub-chunking decision):
- M2c-Daemon surfaces NOT touched (packages/daemon/src/routes/transcripts.ts
  unchanged; packages/daemon/test/transcript-routes.test.ts unchanged).
- The /api/transcripts/<session>/full route path is a DRAFT in this
  commit; M2c-Daemon owns the actual route implementation + auth
  primitive identification. If M2c-Daemon picks a different path, the
  same commit can adjust this CLI line.
- Velocity prior-art .mjs FROZEN reference UNCHANGED.
- restore-packet read + validate stay M3-pending stubs.
- M3/M4/M5/M6 surfaces NOT touched.
- No daemon code changes.
- No unrelated dirty work staged.

Quality lessons applied:
- v3 distinct test summary prefixes.
- v4 marshal-layer wire-shape: tests assert each contract § 2.1
  required field populated correctly + bounded_latest_transcript.bound
  const 120 + redaction_policy_id "openrig-v0".
- v5 test-naming-vs-instantiation: tests invoke writePacket and
  restorePacketCommand directly; mock-daemon test exercises real CLI
  command parsing + real CLI dispatch through a mocked DaemonClient.
- v6 boundary isolation: M2c-CLI stays in packages/cli/src/restore-packet/
  + packages/cli/src/commands/restore-packet.ts. Daemon code untouched.
- v7 refined: M2c-CLI does NOT extend M2b R2 grant; cites the M2c
  sub-chunking decision in this commit message.
- v8 honest baseline: full CLI suite re-run; named flakes baseline-checked.
- v9 synthetic tokens only: all redaction fixtures use synthetic patterns
  (sk-FakeAbCdEf..., etc.); no real provider tokens in fixtures or
  commits. CLI logs metadata only — never transcript content.
- v10 schema-packaging gap: packet-writer uses validateRestoreSummary
  which embeds the schema as a TS const (M2a R2 fix); compiled-output
  runtime check confirms reachability from dist.
- v11 (from M2b R2): nested-content-part counter for Claude parser is
  exercised end-to-end via the M2b R2 round-trip test which round-trips
  the guard reproducer fixture through packet-writer and asserts the
  resulting summary has function_call_output and raw_tool_outputs in
  omitted_classes.

Posture: full QA/testing window (per dispatch).

Source qitem: qitem-20260502013415-e9dab505 (M2c dispatch).
Sub-chunking decision: orch picked Path B (split M2c → M2c-CLI + M2c-Daemon)
in reply on qitem-20260502013530-1fad3a3d.
This commit is contained in:
mvschwarz committed 2026-05-01 18:44:01 -07:00
1 parent 5c87145e54
commit e5de3abd12
3 files changed
+890 -24

No files matched your search

+181 -23
View File
@@ -1,17 +1,25 @@
// restore-packet.ts — `rig restore-packet {write,read,validate}` CLI command.
//
// M2a chunk: command shape + mutual-exclusion for `write`; `read` and
// `validate` are subcommand stubs that return a clear "M3 only" error so
// the CLI surface is registrable now.
//
// M2b will add: source-adapter wiring (codex-jsonl-parser, claude-transcript-
// parser, runtime-detect, redaction, omitted-records).
// M2c will add: packet-writer atomic emission + daemon route integration.
// M3 will replace the read/validate stubs with real implementations.
// M2a: command shape + mutual-exclusion + read/validate stubs.
// M2b: source-adapter wiring (codex-jsonl-parser, claude-transcript-parser,
// runtime-detect, redaction, omitted-records).
// M2c-CLI: write action full impl using packet-writer.ts; --source-jsonl
// + --source-session adapter wiring; CLI flag defaults for the
// ~11 contract-required fields not derivable from the parser.
// M2c-Daemon: full-read transcript route + auth + redaction + tests.
// M3: real read + validate implementations replacing the stubs.
import { Command } from "commander";
import { readFileSync } from "node:fs";
import { resolve as resolvePath } from "node:path";
import { DaemonClient } from "../client.js";
import { type LifecycleDeps } from "../daemon-lifecycle.js";
import { getDaemonStatus, getDaemonUrl, type LifecycleDeps } from "../daemon-lifecycle.js";
import { realDeps } from "./daemon.js";
import { detectRuntime } from "../restore-packet/runtime-detect.js";
import { parseCodexJsonl } from "../restore-packet/codex-jsonl-parser.js";
import { parseClaudeTranscript } from "../restore-packet/claude-transcript-parser.js";
import { writePacket, type WritePacketOptions } from "../restore-packet/packet-writer.js";
import type { SourceRuntime, StructuredTranscript } from "../restore-packet/types.js";
export interface RestorePacketDeps {
lifecycleDeps?: LifecycleDeps;
@@ -27,6 +35,13 @@ interface WriteOptions {
targetRuntime?: string;
targetWorkspaceRoot?: string;
defaultTargetRepo?: string;
rolePointer?: string;
currentWorkSummary?: string;
nextOwner?: string;
caveat?: string[];
authorityBoundaries?: string;
sourceTrustRanking?: string;
generatorVersion?: string;
}
function reportFailure(message: string): void {
@@ -34,11 +49,79 @@ function reportFailure(message: string): void {
process.exitCode = 2;
}
export function restorePacketCommand(_depsOverride?: RestorePacketDeps): Command {
// _depsOverride is reserved for M2b/M2c when the source adapters need a
// DaemonClient (full-read transcript route) and a LifecycleDeps for daemon
// boot / status checks. M2a does not use either; the test suite passes
// depsOverride? as undefined.
function isValidRuntime(value: string | undefined): value is SourceRuntime {
return value === "codex" || value === "claude-code";
}
async function fetchSourceTranscriptViaDaemon(
session: string,
deps: RestorePacketDeps | undefined,
): Promise<{ content: string; sourceCwd: string | null }> {
const lifecycleDeps = deps?.lifecycleDeps ?? realDeps();
const status = await getDaemonStatus(lifecycleDeps);
if (status.state !== "running" || typeof status.port !== "number") {
throw new Error("daemon not running; start it with: rig daemon start");
}
const url = getDaemonUrl(status);
const client = (deps?.clientFactory ?? ((u: string) => new DaemonClient(u)))(url);
// M2c-Daemon will define this route. M2c-CLI calls it; M2c-CLI tests
// mock the daemon (compact-plan.test.ts:166-176 pattern). The path is
// a draft — M2c-Daemon may pick a different naming convention; if so,
// M2c-Daemon will land that change in restore-packet.ts the same commit
// that adds the daemon route.
const path = `/api/transcripts/${encodeURIComponent(session)}/full`;
const response = await client.get<{ content: string; cwd?: string | null }>(path);
if (response.status >= 400 || !response.data) {
throw new Error(
`daemon transcript fetch failed: HTTP ${response.status}: ${
typeof response.data === "object" && response.data !== null
? JSON.stringify(response.data)
: "(no body)"
}`,
);
}
return {
content: response.data.content ?? "",
sourceCwd: response.data.cwd ?? null,
};
}
function buildWritePacketOptions(
opts: WriteOptions,
structured: StructuredTranscript,
sourceRuntime: SourceRuntime,
sourceCwdFallback: string,
sourceSessionId: string,
): WritePacketOptions {
const trustRankingDefault = "rig_whoami,bounded_latest_transcript";
const trustRankingRaw = opts.sourceTrustRanking ?? trustRankingDefault;
const trustRanking = trustRankingRaw
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
return {
targetDir: resolvePath(opts.target),
structured,
sourceRuntime,
targetRig: opts.targetRig ?? "openrig-velocity",
targetRuntime: isValidRuntime(opts.targetRuntime) ? opts.targetRuntime : "claude-code",
targetWorkspaceRoot: opts.targetWorkspaceRoot ?? structured.sessionMeta?.cwd ?? sourceCwdFallback,
defaultTargetRepo: opts.defaultTargetRepo ?? null,
rolePointer: opts.rolePointer ?? `rigs/${opts.targetRig ?? "openrig-velocity"}/state/velocity/role.md`,
currentWorkSummary: opts.currentWorkSummary ?? "Cross-runtime restore packet generated by rig restore-packet write.",
nextOwner: opts.nextOwner ?? "self",
caveats: opts.caveat ?? [],
authorityBoundaries: opts.authorityBoundaries ?? "Restored seat authority per source session role.",
sourceTrustRanking: trustRanking,
sourceSessionId,
sourceCwd: structured.sessionMeta?.cwd ?? sourceCwdFallback,
generatorVersion: opts.generatorVersion ?? "[email protected]",
includeFullTranscript: structured.messages.length > 0,
};
}
export function restorePacketCommand(depsOverride?: RestorePacketDeps): Command {
const cmd = new Command("restore-packet")
.description(
"Generate, read, and validate cross-runtime restore packets per the v0 standard",
@@ -74,6 +157,36 @@ export function restorePacketCommand(_depsOverride?: RestorePacketDeps): Command
"--default-target-repo <path>",
"Default target repo absolute path or null",
)
.option(
"--role-pointer <path>",
"Role guidance pointer path or URI for the restored seat",
)
.option(
"--current-work-summary <text>",
"One-paragraph summary of the source seat's current work",
)
.option(
"--next-owner <name>",
'Who the restored seat hands off to next (default: "self")',
)
.option(
"--caveat <text>",
"Caveat to include in the packet (repeatable)",
(value: string, prev: string[] = []) => prev.concat([value]),
[] as string[],
)
.option(
"--authority-boundaries <text>",
"Explicit statement of restored-seat authority",
)
.option(
"--source-trust-ranking <csv>",
"Comma-separated source-trust ranking; default: rig_whoami,bounded_latest_transcript",
)
.option(
"--generator-version <version>",
'Generator version identifier (default: "[email protected]")',
)
.action(async (opts: WriteOptions) => {
const hasSession = typeof opts.sourceSession === "string" && opts.sourceSession.length > 0;
const hasJsonl = typeof opts.sourceJsonl === "string" && opts.sourceJsonl.length > 0;
@@ -89,15 +202,60 @@ export function restorePacketCommand(_depsOverride?: RestorePacketDeps): Command
);
return;
}
// M2b lands the actual write path. The mutual-exclusion checks above
// are M2a-final; from here on (M2b) the source-adapter dispatch will
// route to codex-jsonl-parser, claude-transcript-parser, or the
// daemon full-read route.
reportFailure(
"M2a: source adapters land in M2b; this command is registered but not yet executable. (target was: " +
opts.target +
")",
);
try {
let content: string;
let sourceCwdFallback: string;
let sourceSessionId: string;
if (hasJsonl) {
const jsonlPath = resolvePath(opts.sourceJsonl!);
content = readFileSync(jsonlPath, "utf-8");
sourceCwdFallback = "/";
sourceSessionId = jsonlPath;
} else {
const session = opts.sourceSession!;
const fetched = await fetchSourceTranscriptViaDaemon(session, depsOverride);
content = fetched.content;
sourceCwdFallback = fetched.sourceCwd ?? "/";
sourceSessionId = session;
}
let runtime: SourceRuntime | null = null;
if (isValidRuntime(opts.sourceRuntime)) {
runtime = opts.sourceRuntime;
} else if (typeof opts.sourceRuntime === "string" && opts.sourceRuntime.length > 0) {
reportFailure(
`--source-runtime must be 'codex' or 'claude-code'; got '${opts.sourceRuntime}'.`,
);
return;
} else {
runtime = detectRuntime(content);
if (runtime === null) {
reportFailure(
"could not auto-detect source runtime; pass --source-runtime <claude-code|codex> explicitly.",
);
return;
}
}
const structured = runtime === "codex"
? parseCodexJsonl(content)
: parseClaudeTranscript(content);
const writeOpts = buildWritePacketOptions(opts, structured, runtime, sourceCwdFallback, sourceSessionId);
const result = await writePacket(writeOpts);
// Per Quality Lesson v9: log only metadata, NOT transcript content.
console.log(`packet: ${result.targetDir}`);
console.log(`files: ${result.files.length}`);
for (const f of result.files) {
console.log(` - ${f}`);
}
console.log(`messages: ${structured.messageCount}`);
console.log(`omitted-classes: ${Object.entries(structured.omittedCounts).filter(([, v]) => v > 0).map(([k, v]) => `${k}=${v}`).join(",") || "(none)"}`);
} catch (err) {
reportFailure((err as Error).message);
}
});
cmd.command("read")
@@ -0,0 +1,309 @@
// packet-writer.ts — assembles + atomically writes a v0 restore packet
// directory.
//
// Per M1 contract § 1: emits 4 required files (restore-instructions.md,
// transcript-latest.md, touched-files.md, restore-summary.json) plus
// optional transcript.md (when full transcript material is available;
// presence in lockstep with `full_transcript` summary key).
//
// Atomic emission via tempdir-then-rename:
// 1. Write all files into a sibling tempdir (`<targetDir>.tmp-<rand>/`).
// 2. Validate the tempdir's restore-summary.json against the embedded
// JSON Schema (RESTORE_SUMMARY_SCHEMA from schema-validator.ts).
// 3. If valid: rename tempdir → targetDir (single fs.rename; atomic on
// same filesystem).
// 4. If validation fails OR any write step fails: rmSync the tempdir;
// propagate the error. The targetDir is NEVER created in this case.
//
// Pre-condition: targetDir MUST NOT exist (operator must supply a fresh
// path). Reject with explicit error otherwise.
import { mkdirSync, writeFileSync, existsSync, renameSync, rmSync, mkdtempSync } from "node:fs";
import { dirname, basename, join } from "node:path";
import {
validateRestoreSummary,
type ValidationResult,
} from "./schema-validator.js";
import type { StructuredTranscript, SourceRuntime } from "./types.js";
const TRANSCRIPT_BOUND = 120;
export interface WritePacketOptions {
/** Absolute path of the target packet directory. MUST NOT exist. */
targetDir: string;
/** Parsed source structured representation. */
structured: StructuredTranscript;
/** Runtime kind of the source seat. */
sourceRuntime: SourceRuntime;
/** Target rig name (operator-supplied). */
targetRig: string;
/** Target runtime kind (operator-supplied). */
targetRuntime: SourceRuntime;
/** Target workspace root (absolute path). */
targetWorkspaceRoot: string;
/** Default target repo (absolute path or null). */
defaultTargetRepo: string | null;
/** Path/URI to role guidance for the restored seat. */
rolePointer: string;
/** One-paragraph human-readable summary of current work. */
currentWorkSummary: string;
/** Who the restored seat hands off to next; "self" if continuing. */
nextOwner: string;
/** Caveats list (cross-runtime quirks, redaction notes, etc.). */
caveats: string[];
/** Explicit statement of restored-seat authority. */
authorityBoundaries: string;
/** Ordered source-trust ranking (subset of contract enum). */
sourceTrustRanking: string[];
/** Source session id. */
sourceSessionId: string;
/** Source cwd (absolute path). */
sourceCwd: string;
/** Generator version identifier (e.g., "[email protected]"). */
generatorVersion: string;
/** Whether to include the full transcript.md file. */
includeFullTranscript: boolean;
}
export interface WritePacketResult {
targetDir: string;
files: string[];
summaryPath: string;
}
function transcriptMarkdownFromMessages(
title: string,
session: string,
messages: StructuredTranscript["messages"],
note: string,
): string {
const body = messages
.map((m, i) => {
const ts = m.timestamp ? ` ${m.timestamp}` : "";
return `## ${i + 1}. ${m.role}${ts}\n\n${m.text}\n`;
})
.join("\n");
return `# ${title}\n\nSource session: \`${session}\`\n\n${note}\n\n${body}`;
}
function buildTranscriptLatest(
structured: StructuredTranscript,
sourceSessionId: string,
): string {
const latest = structured.messages.slice(-TRANSCRIPT_BOUND);
return transcriptMarkdownFromMessages(
"Bounded Latest Transcript",
sourceSessionId,
latest,
`This bounded transcript contains the latest ${latest.length} extracted messages for normal restore. Use transcript.md only when deeper forensic recovery is needed.`,
);
}
function buildTranscriptFull(
structured: StructuredTranscript,
sourceSessionId: string,
): string {
return transcriptMarkdownFromMessages(
"Full Transcript",
sourceSessionId,
structured.messages,
"This transcript contains developer/user/assistant messages extracted from the source. Reasoning records, tool calls, and tool outputs are omitted by design (see restore-summary.json `omitted_classes`).",
);
}
function buildTouchedFiles(structured: StructuredTranscript): string {
const rows = structured.paths.length > 0
? structured.paths.map((entry) => `- \`${entry.path}\` (${entry.count})`).join("\n")
: "- No paths detected in extracted messages or tool arguments.";
return `# Touched Files And Path Inventory\n\nBest-effort path inventory from messages and tool arguments. This is a triage aid, not a complete proof of all files read or written.\n\n${rows}\n`;
}
function buildRestoreInstructions(opts: WritePacketOptions): string {
return `# Restore Instructions
You are a restored seat in \`${opts.targetRig}\`. This packet records the cross-runtime context for resuming work from \`${opts.sourceSessionId}\`.
## Identity
1. Run \`rig whoami --json\`.
2. Trust the current rig identity over the source-session transcript.
3. Current workspace root: \`${opts.targetWorkspaceRoot}\`.
4. Default target repo: \`${opts.defaultTargetRepo ?? "(none)"}\`.
5. Role guidance pointer: \`${opts.rolePointer}\`.
## Restore Steps
1. Read this file.
2. Read \`touched-files.md\`.
3. Read \`transcript-latest.md\` for bounded current context.
4. Use \`transcript.md\` only if the bounded transcript is insufficient.
5. State: "restored from packet generated by ${opts.generatorVersion}; current rig is ${opts.targetRig}; default target repo is ${opts.defaultTargetRepo ?? "none"}."
6. Name any caveats before resuming work.
7. Hand off to next owner: \`${opts.nextOwner}\` (or "self" to continue).
## Current Work
${opts.currentWorkSummary}
## Authority Boundaries
${opts.authorityBoundaries}
## Caveats
${opts.caveats.length > 0 ? opts.caveats.map((c) => `- ${c}`).join("\n") : "- (none recorded)"}
## Packet Stats
- Source runtime: \`${opts.sourceRuntime}\`
- Target runtime: \`${opts.targetRuntime}\`
- JSONL/transcript lines processed: ${opts.structured.lineCount}
- Extracted messages: ${opts.structured.messageCount}
- Compaction records: ${opts.structured.compactedCount}
- Source cwd: \`${opts.sourceCwd}\`
`;
}
function buildSummary(opts: WritePacketOptions): Record<string, unknown> {
const omittedClassesActive: string[] = [];
for (const k of ["reasoning_records", "raw_tool_outputs", "function_call_output", "redacted_secrets"] as const) {
if (opts.structured.omittedCounts[k] > 0) omittedClassesActive.push(k);
}
const messageCount = Math.min(opts.structured.messages.length, TRANSCRIPT_BOUND);
const summary: Record<string, unknown> = {
source_session_id: opts.sourceSessionId,
source_rig: opts.sourceSessionId.includes("@") ? opts.sourceSessionId.split("@")[1] ?? "unknown" : "unknown",
source_runtime: opts.sourceRuntime,
source_cwd: opts.sourceCwd,
target_rig: opts.targetRig,
target_runtime: opts.targetRuntime,
target_workspace_root: opts.targetWorkspaceRoot,
default_target_repo: opts.defaultTargetRepo,
role_pointer: opts.rolePointer,
bounded_latest_transcript: {
path: "transcript-latest.md",
message_count: messageCount,
bound: TRANSCRIPT_BOUND,
},
touched_files: {
path: "touched-files.md",
top_paths: opts.structured.paths.slice(0, 30),
},
durable_pointers: {
queue_pointers: [],
progress_pointers: [],
field_note_pointers: [],
artifact_pointers: [],
},
current_work_summary: opts.currentWorkSummary,
next_owner: opts.nextOwner,
caveats: opts.caveats,
authority_boundaries: opts.authorityBoundaries,
omitted_classes: omittedClassesActive,
redaction_policy_id: "openrig-v0",
source_trust_ranking: opts.sourceTrustRanking,
generator_version: opts.generatorVersion,
generated_at: new Date().toISOString(),
};
if (opts.includeFullTranscript) {
summary.full_transcript = {
path: "transcript.md",
line_count: opts.structured.messages.length,
};
}
return summary;
}
/**
* Write a v0 restore packet directory atomically.
*
* Atomic guarantee: if any step fails (including schema validation of
* restore-summary.json), the targetDir is never created and any
* intermediate tempdir is removed. Operators see either a complete
* valid packet at targetDir, or no packet at all.
*/
export async function writePacket(opts: WritePacketOptions): Promise<WritePacketResult> {
if (existsSync(opts.targetDir)) {
throw new Error(`restore-packet write: target directory already exists: ${opts.targetDir}`);
}
// Build all file contents BEFORE creating any disk state. This way if
// an in-memory step throws (unlikely, but defensive), no tempdir was
// created.
const summary = buildSummary(opts);
const validation: ValidationResult = validateRestoreSummary(summary);
if (!validation.valid) {
const errSummary = validation.errors
.slice(0, 5)
.map((e) => `${e.field}: ${e.rule}`)
.join("; ");
throw new Error(
`restore-packet write: schema validation failed before atomic rename: ${errSummary}`,
);
}
const restoreInstructions = buildRestoreInstructions(opts);
const transcriptLatest = buildTranscriptLatest(opts.structured, opts.sourceSessionId);
const touchedFiles = buildTouchedFiles(opts.structured);
const transcriptFull = opts.includeFullTranscript
? buildTranscriptFull(opts.structured, opts.sourceSessionId)
: null;
// Create tempdir as a sibling of targetDir using mkdtemp for race-free
// unique name. The dirname() must exist and be writable; we don't
// mkdir the parent for the operator (operator authority bedrock).
const parentDir = dirname(opts.targetDir);
const tempPrefix = `${basename(opts.targetDir)}.tmp-restore-packet-`;
const tempDir = mkdtempSync(join(parentDir, tempPrefix));
try {
writeFileSync(join(tempDir, "restore-instructions.md"), restoreInstructions, "utf-8");
writeFileSync(join(tempDir, "transcript-latest.md"), transcriptLatest, "utf-8");
writeFileSync(join(tempDir, "touched-files.md"), touchedFiles, "utf-8");
writeFileSync(join(tempDir, "restore-summary.json"), JSON.stringify(summary, null, 2), "utf-8");
if (transcriptFull !== null) {
writeFileSync(join(tempDir, "transcript.md"), transcriptFull, "utf-8");
}
// Re-validate by parsing the on-disk summary (round-trip self-check).
// This catches any structural divergence between buildSummary's
// in-memory shape and the JSON.stringify() serialization.
const onDisk = JSON.parse(
// eslint-disable-next-line @typescript-eslint/no-require-imports
(await import("node:fs")).readFileSync(join(tempDir, "restore-summary.json"), "utf-8"),
);
const recheck = validateRestoreSummary(onDisk);
if (!recheck.valid) {
throw new Error(
`restore-packet write: on-disk schema recheck failed: ${recheck.errors.slice(0, 5).map((e) => `${e.field}: ${e.rule}`).join("; ")}`,
);
}
// Atomic rename tempdir → targetDir. fs.rename is atomic when source
// and destination are on the same filesystem; we keep them sibling
// so this holds.
renameSync(tempDir, opts.targetDir);
} catch (err) {
// Clean up tempdir on any failure; do NOT leave a partial packet.
try {
rmSync(tempDir, { recursive: true, force: true });
} catch {
// Best-effort cleanup; original error takes precedence.
}
throw err;
}
const files = [
"restore-instructions.md",
"transcript-latest.md",
"touched-files.md",
"restore-summary.json",
];
if (transcriptFull !== null) files.push("transcript.md");
return {
targetDir: opts.targetDir,
files,
summaryPath: join(opts.targetDir, "restore-summary.json"),
};
}
+400 -1
View File
@@ -9,8 +9,22 @@
// fail first, then the implementation was written to green. The committed
// state is the green/passing state.
import { describe, it, expect } from "vitest";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Command } from "commander";
// Module-level mock for daemon-lifecycle so the M2c-CLI mock-daemon
// round-trip test can drive a fake "daemon running" state without
// touching the host's real daemon. Other tests in this file don't
// invoke daemon-lifecycle (they exercise pure parsers / packet-writer),
// so they're unaffected.
vi.mock("../src/daemon-lifecycle.js", async () => {
const actual = await vi.importActual<Record<string, unknown>>("../src/daemon-lifecycle.js");
return {
...actual,
getDaemonStatus: vi.fn(async () => ({ state: "running", healthy: true, pid: 1234, port: 7433 })),
getDaemonUrl: vi.fn(() => "http://localhost:7433"),
};
});
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
@@ -799,3 +813,388 @@ describe("M2b R2 Claude parser nested-content-part handling", () => {
expect(r.omittedCounts.raw_tool_outputs).toBe(0);
});
});
// ─────────────────────────────────────────────────────────────────────
// M2c-CLI: packet-writer atomic emission + round-trip tests.
//
// packet-writer.ts assembles a v0 restore packet directory atomically:
// writes to a tempdir, validates the resulting restore-summary.json
// against the embedded JSON Schema, then renames tempdir → target.
// On any validation failure, the tempdir is removed; no partial packet
// is left in the operator's filesystem.
//
// Per M1 contract § 1: directory contains 4 required files
// (restore-instructions.md, transcript-latest.md, touched-files.md,
// restore-summary.json) plus optional transcript.md.
// ─────────────────────────────────────────────────────────────────────
describe("M2c-CLI packet-writer atomic emission", () => {
let tmpRoot: string;
beforeEach(async () => {
const fs = await import("node:fs");
const os = await import("node:os");
const path = await import("node:path");
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "restore-packet-test-"));
});
afterEach(async () => {
const fs = await import("node:fs");
fs.rmSync(tmpRoot, { recursive: true, force: true });
});
function buildBaselineOpts(targetDir: string): Record<string, unknown> {
return {
targetDir,
structured: parseCodexJsonl(`{"type":"session_meta","payload":{"cwd":"/Users/wrandom/code/projects/openrig-hub","id":"src-session-id"}}
{"type":"response_item","timestamp":"2026-05-02T01:00:00Z","payload":{"type":"message","role":"user","content":"Walk through the M1 contract"}}
{"type":"response_item","timestamp":"2026-05-02T01:00:01Z","payload":{"type":"message","role":"assistant","content":[{"type":"text","text":"Per § 2.1 the schema requires 21 fields"}]}}`),
sourceRuntime: "codex" as const,
targetRig: "openrig-velocity-claude",
targetRuntime: "claude-code" as const,
targetWorkspaceRoot: "/Users/wrandom/code/projects/openrig-hub",
defaultTargetRepo: "/Users/wrandom/code/projects/openrig-hub/openrig",
rolePointer: "rigs/openrig-velocity-claude/state/velocity/driver-role.md",
currentWorkSummary: "Working on Restore-Packet vertical M2c-CLI chunk.",
nextOwner: "self",
caveats: ["Cross-runtime restore from Codex JSONL to Claude Code seat."],
authorityBoundaries: "May edit packages/cli/ and packages/daemon/ within M2c boundary; no M3+ surfaces.",
sourceTrustRanking: ["rig_whoami", "bounded_latest_transcript"],
sourceSessionId: "velocity-driver@openrig-velocity",
sourceCwd: "/Users/wrandom/code/projects/openrig-hub",
generatorVersion: "[email protected]",
includeFullTranscript: false,
};
}
it("emits 4 required files + restore-summary.json schema-valid", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const targetDir = path.join(tmpRoot, "packet-1");
const result = await writePacket(buildBaselineOpts(targetDir) as Parameters<typeof writePacket>[0]);
expect(result.targetDir).toBe(targetDir);
for (const required of ["restore-instructions.md", "transcript-latest.md", "touched-files.md", "restore-summary.json"]) {
expect(fs.existsSync(path.join(targetDir, required)), `missing ${required}`).toBe(true);
}
// Optional transcript.md should NOT be present when includeFullTranscript=false.
expect(fs.existsSync(path.join(targetDir, "transcript.md"))).toBe(false);
// Summary parses and schema-validates.
const summary = JSON.parse(fs.readFileSync(path.join(targetDir, "restore-summary.json"), "utf-8"));
const validation = validateRestoreSummary(summary);
expect(validation.valid, JSON.stringify(validation.errors)).toBe(true);
});
it("emits transcript.md when includeFullTranscript=true; full_transcript key present in summary", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const targetDir = path.join(tmpRoot, "packet-full");
const opts = buildBaselineOpts(targetDir);
(opts as Record<string, unknown>)["includeFullTranscript"] = true;
await writePacket(opts as Parameters<typeof writePacket>[0]);
expect(fs.existsSync(path.join(targetDir, "transcript.md"))).toBe(true);
const summary = JSON.parse(fs.readFileSync(path.join(targetDir, "restore-summary.json"), "utf-8"));
expect(summary.full_transcript).toBeDefined();
expect(typeof summary.full_transcript.path).toBe("string");
expect(typeof summary.full_transcript.line_count).toBe("number");
});
it("populates contract § 2.1 required fields verbatim from operator-supplied options", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const targetDir = path.join(tmpRoot, "packet-fields");
await writePacket(buildBaselineOpts(targetDir) as Parameters<typeof writePacket>[0]);
const summary = JSON.parse(fs.readFileSync(path.join(targetDir, "restore-summary.json"), "utf-8"));
expect(summary.target_rig).toBe("openrig-velocity-claude");
expect(summary.target_runtime).toBe("claude-code");
expect(summary.role_pointer).toBe("rigs/openrig-velocity-claude/state/velocity/driver-role.md");
expect(summary.bounded_latest_transcript.bound).toBe(120);
expect(summary.redaction_policy_id).toBe("openrig-v0");
expect(summary.generator_version).toBe("[email protected]");
expect(summary.source_runtime).toBe("codex");
expect(summary.source_session_id).toBe("velocity-driver@openrig-velocity");
expect(Array.isArray(summary.touched_files.top_paths)).toBe(true);
expect(summary.durable_pointers.queue_pointers).toBeDefined();
expect(summary.durable_pointers.progress_pointers).toBeDefined();
expect(summary.durable_pointers.field_note_pointers).toBeDefined();
expect(summary.durable_pointers.artifact_pointers).toBeDefined();
});
it("rejects when target directory already exists", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const targetDir = path.join(tmpRoot, "preexisting");
fs.mkdirSync(targetDir);
fs.writeFileSync(path.join(targetDir, "marker.txt"), "do not overwrite");
const opts = buildBaselineOpts(targetDir) as Parameters<typeof writePacket>[0];
await expect(writePacket(opts)).rejects.toThrow(/exists|already/i);
// Marker preserved (no partial overwrite).
expect(fs.readFileSync(path.join(targetDir, "marker.txt"), "utf-8")).toBe("do not overwrite");
});
it("atomic emission: schema-validation failure mid-write leaves NO target dir AND cleans tempdir", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const targetDir = path.join(tmpRoot, "packet-fail");
const opts = buildBaselineOpts(targetDir) as Record<string, unknown>;
// Inject a contract violation: empty current_work_summary should fail
// schema validation (minLength: 1).
opts["currentWorkSummary"] = "";
await expect(
writePacket(opts as Parameters<typeof writePacket>[0]),
).rejects.toThrow(/schema|valid|current_work_summary/i);
// Target dir was NEVER created (atomic rename semantics).
expect(fs.existsSync(targetDir)).toBe(false);
// No leftover .tmp- prefixed dirs in tmpRoot.
const tmpRootContents = fs.readdirSync(tmpRoot);
const leftovers = tmpRootContents.filter((name) => name.includes(".tmp-restore-packet-"));
expect(leftovers).toEqual([]);
});
it("transcript-latest.md bounded at 120 messages; message_count reflects actual count", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
// Build 150 user messages (exceeds the 120 bound).
const lines: string[] = [`{"type":"session_meta","payload":{"cwd":"/x"}}`];
for (let i = 0; i < 150; i++) {
lines.push(`{"type":"response_item","payload":{"type":"message","role":"user","content":"msg ${i}"}}`);
}
const opts = buildBaselineOpts(path.join(tmpRoot, "packet-bound")) as Record<string, unknown>;
opts["structured"] = parseCodexJsonl(lines.join("\n"));
await writePacket(opts as Parameters<typeof writePacket>[0]);
const summary = JSON.parse(fs.readFileSync(path.join(tmpRoot, "packet-bound", "restore-summary.json"), "utf-8"));
expect(summary.bounded_latest_transcript.bound).toBe(120);
expect(summary.bounded_latest_transcript.message_count).toBe(120);
// Latest transcript file contains exactly 120 sectioned messages.
const latest = fs.readFileSync(path.join(tmpRoot, "packet-bound", "transcript-latest.md"), "utf-8");
const sectionCount = (latest.match(/^## \d+\. /gm) ?? []).length;
expect(sectionCount).toBe(120);
});
});
describe("M2c-CLI Velocity-shape round-trip (synthetic 4-role fixtures)", () => {
let tmpRoot: string;
beforeEach(async () => {
const fs = await import("node:fs");
const os = await import("node:os");
const path = await import("node:path");
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "restore-packet-rt-"));
});
afterEach(async () => {
const fs = await import("node:fs");
fs.rmSync(tmpRoot, { recursive: true, force: true });
});
// Build a synthetic Codex JSONL with the same structural shape as
// Velocity prior art: session_meta + response_item messages + tool_calls
// + reasoning records + a credential-pattern needle for redaction.
function syntheticCodexJsonlFor(role: string): string {
return [
`{"type":"session_meta","payload":{"cwd":"/Users/wrandom/code/projects/openrig-hub","id":"velocity-${role}@openrig-velocity-code"}}`,
`{"type":"response_item","payload":{"type":"reasoning","content":"<thinking>"}}`,
`{"type":"response_item","payload":{"type":"function_call","arguments":"{\\"path\\":\\"packages/cli/src/index.ts\\"}"}}`,
`{"type":"response_item","timestamp":"2026-04-23T18:08:00Z","payload":{"type":"message","role":"user","content":"${role}: please review the diff"}}`,
`{"type":"response_item","timestamp":"2026-04-23T18:08:30Z","payload":{"type":"message","role":"assistant","content":[{"type":"text","text":"${role}: token sk-FakeAbCdEfGhIjKlMn was visible in logs"}]}}`,
`{"type":"compacted","payload":{}}`,
].join("\n");
}
for (const role of ["driver", "guard", "planner", "tester"]) {
it(`Velocity-shape ${role} round-trip: parser → packet-writer → schema-valid; counts + redaction preserved`, async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const structured = parseCodexJsonl(syntheticCodexJsonlFor(role));
// Sanity-check parser output before writer round-trip.
expect(structured.messageCount).toBe(2);
expect(structured.compactedCount).toBe(1);
expect(structured.omittedCounts.reasoning_records).toBeGreaterThanOrEqual(1);
expect(structured.omittedCounts.function_call_output).toBe(1);
expect(structured.omittedCounts.redacted_secrets).toBe(1);
const targetDir = path.join(tmpRoot, `packet-${role}`);
await writePacket({
targetDir,
structured,
sourceRuntime: "codex",
targetRig: "openrig-velocity-claude",
targetRuntime: "claude-code",
targetWorkspaceRoot: "/Users/wrandom/code/projects/openrig-hub",
defaultTargetRepo: "/Users/wrandom/code/projects/openrig-hub/openrig",
rolePointer: `rigs/openrig-velocity-claude/state/velocity/${role}-role.md`,
currentWorkSummary: `Synthetic ${role} round-trip for Velocity-shape parity.`,
nextOwner: "self",
caveats: [],
authorityBoundaries: `${role} authority for Velocity-replay context.`,
sourceTrustRanking: ["rig_whoami", "bounded_latest_transcript"],
sourceSessionId: `velocity-${role}@openrig-velocity-code`,
sourceCwd: "/Users/wrandom/code/projects/openrig-hub",
generatorVersion: "[email protected]",
includeFullTranscript: true,
});
const summary = JSON.parse(fs.readFileSync(path.join(targetDir, "restore-summary.json"), "utf-8"));
const validation = validateRestoreSummary(summary);
expect(validation.valid, JSON.stringify(validation.errors)).toBe(true);
// Provenance preserved at relevant fields:
expect(summary.source_session_id).toBe(`velocity-${role}@openrig-velocity-code`);
expect(summary.source_cwd).toBe("/Users/wrandom/code/projects/openrig-hub");
expect(summary.bounded_latest_transcript.message_count).toBe(2);
// Omitted-class enumeration matches what the parser counted:
expect(summary.omitted_classes).toContain("reasoning_records");
expect(summary.omitted_classes).toContain("function_call_output");
expect(summary.omitted_classes).toContain("redacted_secrets");
// Redacted output: assistant's "sk-FakeAbCdEfGhIjKlMn" must NOT appear in transcript.md.
const fullT = fs.readFileSync(path.join(targetDir, "transcript.md"), "utf-8");
expect(fullT).not.toContain("sk-FakeAbCdEfGhIjKlMn");
expect(fullT).toContain("[REDACTED]");
});
}
});
describe("M2c-CLI redaction + omitted-record round-trip end-to-end", () => {
let tmpRoot: string;
beforeEach(async () => {
const fs = await import("node:fs");
const os = await import("node:os");
const path = await import("node:path");
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "restore-packet-er-"));
});
afterEach(async () => {
const fs = await import("node:fs");
fs.rmSync(tmpRoot, { recursive: true, force: true });
});
it("mock-daemon --source-session round-trip writes packet and forwards no mutation", async () => {
const { restorePacketCommand } = await import("../src/commands/restore-packet.js");
const { createProgram } = await import("../src/index.js");
const fs = await import("node:fs");
const path = await import("node:path");
const requestedPaths: string[] = [];
const mutationMethods = {
post: vi.fn(async () => { throw new Error("mock-daemon: post should not be called by restore-packet write"); }),
delete: vi.fn(async () => { throw new Error("mock-daemon: delete should not be called by restore-packet write"); }),
postText: vi.fn(async () => { throw new Error("mock-daemon: postText should not be called"); }),
postExpectText: vi.fn(async () => { throw new Error("mock-daemon: postExpectText should not be called"); }),
};
// Synthetic Codex JSONL the mock-daemon serves on the new full-read route.
const fixtureContent = `{"type":"session_meta","payload":{"cwd":"/Users/wrandom/code/x","id":"src-session"}}
{"type":"response_item","payload":{"type":"message","role":"user","content":"hello from session"}}
{"type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"text","text":"reply"}]}}`;
const deps = {
lifecycleDeps: {} as Parameters<typeof restorePacketCommand>[0] extends undefined ? never : NonNullable<Parameters<typeof restorePacketCommand>[0]>["lifecycleDeps"],
clientFactory: () => ({
get: vi.fn(async (p: string) => {
requestedPaths.push(p);
if (p === "/api/transcripts/src-session/full") {
return { status: 200, data: { content: fixtureContent, cwd: "/Users/wrandom/code/x" } };
}
return { status: 404, data: { error: "not found" } };
}),
getText: vi.fn(async () => ({ status: 200, data: "" })),
...mutationMethods,
}),
};
const targetDir = path.join(tmpRoot, "via-daemon");
const program = createProgram({ restorePacketDeps: deps as unknown as Parameters<typeof createProgram>[0]["restorePacketDeps"] });
program.exitOverride();
await program.parseAsync([
"node", "rig", "restore-packet", "write",
"--source-session", "src-session",
"--target", targetDir,
"--target-rig", "openrig-velocity-claude",
"--target-runtime", "claude-code",
]);
// Daemon was queried at the new full-read route; no mutation methods called.
expect(requestedPaths).toContain("/api/transcripts/src-session/full");
expect(mutationMethods.post).not.toHaveBeenCalled();
expect(mutationMethods.delete).not.toHaveBeenCalled();
// Packet emitted.
expect(fs.existsSync(targetDir)).toBe(true);
const summary = JSON.parse(fs.readFileSync(path.join(targetDir, "restore-summary.json"), "utf-8"));
const validation = validateRestoreSummary(summary);
expect(validation.valid, JSON.stringify(validation.errors)).toBe(true);
expect(summary.source_session_id).toBe("src-session");
});
it("M2b R2 nested Claude content fixture: round-trips with non-zero omittedCounts in summary", async () => {
const { writePacket } = await import("../src/restore-packet/packet-writer.js");
const fs = await import("node:fs");
const path = await import("node:path");
const content = JSON.stringify({
type: "assistant",
message: {
role: "assistant",
content: [
{ type: "tool_use", id: "toolu_1", name: "Bash", input: { cmd: "pwd" } },
{ type: "text", text: "checking now" },
],
},
cwd: "/Users/wrandom/code/projects/openrig-hub",
sessionId: "claude-session-id",
}) + "\n" + JSON.stringify({
type: "user",
message: {
role: "user",
content: [
{ type: "tool_result", tool_use_id: "toolu_1", content: "/Users/wrandom/code/projects/openrig-hub" },
],
},
cwd: "/Users/wrandom/code/projects/openrig-hub",
sessionId: "claude-session-id",
});
const structured = parseClaudeTranscript(content);
const targetDir = path.join(tmpRoot, "claude-nested");
await writePacket({
targetDir,
structured,
sourceRuntime: "claude-code",
targetRig: "openrig-velocity-claude",
targetRuntime: "claude-code",
targetWorkspaceRoot: "/Users/wrandom/code/projects/openrig-hub",
defaultTargetRepo: null,
rolePointer: "rigs/openrig-velocity-claude/state/velocity/driver-role.md",
currentWorkSummary: "Nested-content round-trip for M2c-CLI.",
nextOwner: "self",
caveats: [],
authorityBoundaries: "Velocity authority.",
sourceTrustRanking: ["rig_whoami"],
sourceSessionId: "claude-session-id",
sourceCwd: "/Users/wrandom/code/projects/openrig-hub",
generatorVersion: "[email protected]",
includeFullTranscript: false,
});
const summary = JSON.parse(fs.readFileSync(path.join(targetDir, "restore-summary.json"), "utf-8"));
expect(summary.omitted_classes).toContain("function_call_output");
expect(summary.omitted_classes).toContain("raw_tool_outputs");
// Touched-files inventory captured the path from the omitted tool_result.
expect(summary.touched_files.top_paths.some((p: { path: string }) =>
p.path === "/Users/wrandom/code/projects/openrig-hub")).toBe(true);
});
});