@mstar-harness/cli 3.6.0-alpha.3 → 3.6.0-alpha.4

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.
Files changed (2) hide show
  1. package/dist/mstar-harness.js +251 -28
  2. package/package.json +1 -1
@@ -2415,6 +2415,7 @@ var require_picocolors2 = __commonJS((exports, module) => {
2415
2415
 
2416
2416
  // src/index.ts
2417
2417
  import { execFileSync as execFileSync10 } from "child_process";
2418
+ import { createHash, randomUUID as randomUUID3 } from "crypto";
2418
2419
  import fs8 from "fs";
2419
2420
  import path12 from "path";
2420
2421
 
@@ -8544,6 +8545,9 @@ function prReviewSeatPrompt(opts) {
8544
8545
  if (!isAbsolute9(worktreePath)) {
8545
8546
  throw new TypeError(`prReviewSeatPrompt: worktreePath must be an absolute path - got ${JSON.stringify(opts.worktreePath)}`);
8546
8547
  }
8548
+ if (opts.diffFile !== undefined && opts.diffFile !== "" && !isAbsolute9(opts.diffFile)) {
8549
+ throw new TypeError(`prReviewSeatPrompt: diffFile must be an absolute path - got ${JSON.stringify(opts.diffFile)}`);
8550
+ }
8547
8551
  const slug = `${domain}-${seat}`;
8548
8552
  const lines = [];
8549
8553
  lines.push(`# PR review audit seat \u2014 Stage ${opts.stage}${opts.securitySeat === true ? " (security)" : ""}`);
@@ -8569,6 +8573,9 @@ function prReviewSeatPrompt(opts) {
8569
8573
  const sections = opts.stage === 1 ? tier === "quick" ? "Scoping, Evidence rules" : "Review pipeline, Worktree isolation, Scoping, Evidence rules" : "Merge class, Attack and vet, Evidence rules, Sizing & change shape";
8570
8574
  lines.push(`1. \`${prReviewRef}\` \u2014 read at least these sections: ${sections}.`);
8571
8575
  lines.push(`2. The review worktree: \`${worktreePath}\` \u2014 your ONLY working directory this session; read-only (no edits, no fixes, no stash, no commits, no posts).`);
8576
+ if (opts.diffFile) {
8577
+ lines.push(`- Read the pinned diff snapshot FIRST: \`${opts.diffFile}\` \u2014 it is the review's diff basis (already computed at setup); read it before opening files.`);
8578
+ }
8572
8579
  if (opts.stage === 2) {
8573
8580
  lines.push(`3. \`${join15(skillRoot, "references", "finding-format.md")}\` \u2014 the template every finding follows.`);
8574
8581
  if (opts.securitySeat === true) {
@@ -14136,6 +14143,9 @@ prReviewCommand.command("validate-report").description("Validate a saved local P
14136
14143
  function gitSync(args, cwd) {
14137
14144
  return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
14138
14145
  }
14146
+ function gitRaw(args, cwd) {
14147
+ return execFileSync10("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], maxBuffer: GIT_CAPTURE_MAX_BYTES });
14148
+ }
14139
14149
  function ghSync(args, input) {
14140
14150
  return execFileSync10("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...input !== undefined ? { input } : {} }).trim();
14141
14151
  }
@@ -14161,7 +14171,11 @@ function refResolves(ref, cwd) {
14161
14171
  }
14162
14172
  function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
14163
14173
  if (diffCmdArgs[0] === "__working_tree__") {
14164
- const dirty = gitIf(["diff"], worktreePath).length > 0 || gitIf(["diff", "--cached"], worktreePath).length > 0;
14174
+ const diffOut = gitProbe(["diff"], worktreePath);
14175
+ const cachedOut = gitProbe(["diff", "--cached"], worktreePath);
14176
+ if (diffOut === null || cachedOut === null)
14177
+ return false;
14178
+ const dirty = diffOut.length > 0 || cachedOut.length > 0;
14165
14179
  if (dirty)
14166
14180
  return false;
14167
14181
  let untracked = "";
@@ -14178,17 +14192,26 @@ function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
14178
14192
  const sha = diffCmdArgs[1];
14179
14193
  const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
14180
14194
  const diffFrom = refResolves(`${sha}^`, worktreePath) ? `${sha}^` : EMPTY_TREE;
14181
- return gitIf(["diff", diffFrom, sha], worktreePath).trim().length === 0;
14195
+ const probe2 = gitProbe(["diff", diffFrom, sha], worktreePath);
14196
+ return probe2 === null ? false : probe2.trim().length === 0;
14182
14197
  }
14183
- return gitIf(diffCmdArgs, worktreePath).length === 0;
14198
+ const probe = gitProbe(diffCmdArgs, worktreePath);
14199
+ return probe === null ? false : probe.length === 0;
14184
14200
  }
14185
14201
  function gitIf(args, cwd) {
14186
14202
  try {
14187
- return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
14203
+ return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: GIT_CAPTURE_MAX_BYTES });
14188
14204
  } catch {
14189
14205
  return "";
14190
14206
  }
14191
14207
  }
14208
+ function gitProbe(args, cwd) {
14209
+ try {
14210
+ return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: GIT_CAPTURE_MAX_BYTES });
14211
+ } catch {
14212
+ return null;
14213
+ }
14214
+ }
14192
14215
  function probeChangesetEmptyPreworktree(mode, headSpec, cwd) {
14193
14216
  if (mode === "working-tree")
14194
14217
  return probeChangesetEmpty(["__working_tree__"], cwd, cwd);
@@ -14315,7 +14338,7 @@ function ghApi(apiPath, payload) {
14315
14338
  input: payload
14316
14339
  });
14317
14340
  }
14318
- prReviewCommand.command("worktree-setup").description("Create the isolated review worktree per pr-review.md \xA7 Worktree isolation: resolves the real base, picks a " + "collision-free branch name, fetches with explicit refspecs, creates the worktree, computes the diff basis INSIDE it, " + "and records a sidecar json consumed by worktree-cleanup; prints {reviewBranch, worktreePath, base, mergeBase, diffCmd} " + "(input modes: --pr <n> | --branch <b> | --diff | --working-tree | --commit <sha>)").option("--pr <n>", "PR number input mode (pull/<n>/head via gh)").option("--branch <name>", "Bare remote-branch input mode").option("--diff", "Arbitrary diff input mode \u2014 no worktree, no refs").option("--working-tree", "Uncommitted working-tree input mode \u2014 no worktree, no refs").option("--commit <sha>", "Single-commit input mode").option("--path <dir>", "Worktree target directory (default: ../<repo-name>.review-pr<N> beside the repo)").action((options) => {
14341
+ prReviewCommand.command("worktree-setup").description("Create the isolated review worktree per pr-review.md \xA7 Worktree isolation: resolves the real base, picks a " + "collision-free branch name, fetches with explicit refspecs, creates the worktree, computes the diff basis INSIDE it, " + "and records a sidecar json consumed by worktree-cleanup; prints {reviewBranch, worktreePath, base, mergeBase, diffCmd, diffFile} " + "(input modes: --pr <n> | --branch <b> | --diff | --working-tree | --commit <sha>)").option("--pr <n>", "PR number input mode (pull/<n>/head via gh)").option("--branch <name>", "Bare remote-branch input mode").option("--diff", "Arbitrary diff input mode \u2014 no worktree, no refs").option("--working-tree", "Uncommitted working-tree input mode \u2014 no worktree, no refs").option("--commit <sha>", "Single-commit input mode").option("--path <dir>", "Worktree target directory (default: <repo>/.worktrees/review-<branch>)").action((options) => {
14319
14342
  try {
14320
14343
  const modesDeclared = [
14321
14344
  options.pr !== undefined,
@@ -14355,7 +14378,7 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
14355
14378
  process.exitCode = 1;
14356
14379
  return;
14357
14380
  }
14358
- console.log(JSON.stringify({ reviewBranch: null, worktreePath: repoRoot, base: null, mergeBase: null, diffCmd: mode === "diff" ? "(provided changeset)" : "git diff + git diff --cached + ls-files --others" }, null, 2));
14381
+ console.log(JSON.stringify({ reviewBranch: null, worktreePath: repoRoot, base: null, mergeBase: null, diffCmd: mode === "diff" ? "(provided changeset)" : "git diff + git diff --cached + ls-files --others", diffFile: null }, null, 2));
14359
14382
  return;
14360
14383
  }
14361
14384
  let prNumber = 0;
@@ -14405,7 +14428,21 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
14405
14428
  const baseCandidate = mode === "pr" ? namePrNumber : Number(String(Math.abs([...headSpec].reduce((h, ch) => (h * 31 + ch.charCodeAt(0)) % 1000003, 7))));
14406
14429
  const reviewBranch = pickReviewBranchName(existing, baseCandidate === 0 ? 1 : baseCandidate, cliToday().replace(/-/g, ""));
14407
14430
  const branchSuffix = mode === "pr" ? "" : `-${headSpec.slice(0, 8)}`;
14408
- const worktreePath = path12.resolve(options.path ?? joinDirnameBeside(repoRoot, `.review-${reviewBranch}${branchSuffix}`));
14431
+ const worktreePath = path12.resolve(options.path ?? path12.join(repoRoot, ".worktrees", `review-${reviewBranch}${branchSuffix}`));
14432
+ if (options.path === undefined) {
14433
+ fs8.mkdirSync(path12.join(repoRoot, ".worktrees"), { recursive: true });
14434
+ if (gitIf(["check-ignore", ".worktrees/"], repoRoot) === "") {
14435
+ const excludePath = path12.join(repoRoot, ".git", "info", "exclude");
14436
+ const existing2 = fs8.existsSync(excludePath) ? fs8.readFileSync(excludePath, "utf8") : "";
14437
+ if (!existing2.split(`
14438
+ `).some((line) => line.trim() === ".worktrees/")) {
14439
+ fs8.appendFileSync(excludePath, `${existing2 === "" || existing2.endsWith(`
14440
+ `) ? "" : `
14441
+ `}.worktrees/
14442
+ `);
14443
+ }
14444
+ }
14445
+ }
14409
14446
  const originUrl = gitIf(["remote", "get-url", "origin"], repoRoot);
14410
14447
  let fetched = true;
14411
14448
  try {
@@ -14421,7 +14458,11 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
14421
14458
  } catch {
14422
14459
  fetched = false;
14423
14460
  }
14424
- const rollbackNewWorktree = (branchToDelete) => {
14461
+ let wroteSnapshot = false;
14462
+ let wroteSidecar = false;
14463
+ let pendingSidecar;
14464
+ let sidecarFd;
14465
+ const cleanupOnFailure = (branchToDelete) => {
14425
14466
  try {
14426
14467
  if (fs8.existsSync(worktreePath))
14427
14468
  gitSync(["worktree", "remove", "--force", worktreePath], repoRoot);
@@ -14429,6 +14470,12 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
14429
14470
  if (branchToDelete !== "" && !existing.has(branchToDelete)) {
14430
14471
  gitSync(["branch", "-D", branchToDelete], repoRoot);
14431
14472
  }
14473
+ if (wroteSidecar && sidecarFd !== undefined) {
14474
+ removeOwnedFreshSidecarFile(sidecarFd, worktreePath);
14475
+ sidecarFd = undefined;
14476
+ }
14477
+ if (wroteSnapshot && pendingSidecar !== undefined)
14478
+ removeOwnedSnapshotFile(worktreePath, pendingSidecar);
14432
14479
  } catch {}
14433
14480
  };
14434
14481
  const fetchedHeadResolves = mode === "pr" ? refResolves(reviewBranch, repoRoot) : mode === "branch" ? originUrl !== "" && refResolves(`origin/${options.branch}`, repoRoot) : mode === "commit" ? Boolean(gitIf(["rev-parse", "--verify", "--quiet", `${headSpec}^{commit}`], repoRoot).trim()) : refResolves(headSpec, repoRoot);
@@ -14461,48 +14508,222 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
14461
14508
  const changesetEmpty = probeChangesetEmpty(diffArgs, worktreePath, worktreePath);
14462
14509
  const emptyGate = preflightChangeset(mode, { refsResolve: true, changesetEmpty });
14463
14510
  if (changesetEmpty) {
14464
- rollbackNewWorktree(mode === "pr" ? reviewBranch : "");
14511
+ cleanupOnFailure(mode === "pr" ? reviewBranch : "");
14465
14512
  }
14466
14513
  if (!emptyGate.ok && emptyGate.violations.some((v) => v.code === "prreview.preflight.changeset-empty")) {
14467
14514
  printChecklist("pr-review worktree-setup preflight", emptyGate);
14468
14515
  process.exitCode = 1;
14469
14516
  return;
14470
14517
  }
14471
- const recordedBase = mode === "pr" || mode !== "commit" && !baseRef.startsWith("origin/") ? `origin/${baseRef}` : baseRef;
14472
- const sidecar = {
14473
- reviewBranch: mode === "pr" ? reviewBranch : "",
14474
- worktreePath,
14475
- base: recordedBase,
14476
- mergeBase,
14477
- diffCmd,
14478
- reportSaved: false,
14479
- createdAt: new Date().toISOString(),
14480
- repoRoot
14518
+ const captureAndRecordSnapshot = () => {
14519
+ const diffFile = prReviewArtifactPathFor(worktreePath, "diff");
14520
+ const sidecarPath = prReviewArtifactPathFor(worktreePath, "json");
14521
+ const snapshotParts = [];
14522
+ if (mode === "commit") {
14523
+ snapshotParts.push(Buffer.from(`# Review package: ${baseRef} (single commit)
14524
+
14525
+ ## Commits
14526
+ `), gitRaw(["log", "--oneline", "-1", headSpec], worktreePath), Buffer.from(`
14527
+ ## Files changed
14528
+ `), gitRaw(["show", "--stat", headSpec], worktreePath), Buffer.from(`
14529
+ ## Diff
14530
+ `), gitRaw(["show", "-U10", headSpec], worktreePath));
14531
+ } else {
14532
+ const range = diffArgs[1];
14533
+ const [rangeBase, rangeHead] = range.split("...");
14534
+ snapshotParts.push(Buffer.from(`# Review package: ${baseRef}..${headSpec}
14535
+
14536
+ ## Commits
14537
+ `), gitRaw(["log", "--oneline", `${rangeBase}..${rangeHead}`], worktreePath), Buffer.from(`
14538
+ ## Files changed
14539
+ `), gitRaw(["diff", "--stat", range], worktreePath), Buffer.from(`
14540
+ ## Diff
14541
+ `), gitRaw(["diff", "-U10", range], worktreePath));
14542
+ }
14543
+ const snapshot = Buffer.concat(snapshotParts);
14544
+ const recordedBase = mode === "pr" || mode !== "commit" && !baseRef.startsWith("origin/") ? `origin/${baseRef}` : baseRef;
14545
+ const sidecar2 = {
14546
+ reviewBranch: mode === "pr" ? reviewBranch : "",
14547
+ worktreePath,
14548
+ base: recordedBase,
14549
+ mergeBase,
14550
+ diffCmd,
14551
+ reportSaved: false,
14552
+ createdAt: new Date().toISOString(),
14553
+ repoRoot,
14554
+ diffFile,
14555
+ diffFileSha256: createHash("sha256").update(snapshot).digest("hex")
14556
+ };
14557
+ try {
14558
+ sidecarFd = fs8.openSync(sidecarPath, "wx+");
14559
+ } catch (error) {
14560
+ if (error.code !== "EEXIST" && error.code !== "EISDIR")
14561
+ throw error;
14562
+ throw new Error(`cannot record setup sidecar at ${sidecarPath} - run mstar pr-review worktree-cleanup first (never cleaning a foreign review)`);
14563
+ }
14564
+ writeFdSync(sidecarFd, Buffer.from(JSON.stringify(sidecar2, null, 2), "utf8"));
14565
+ wroteSidecar = true;
14566
+ try {
14567
+ fs8.writeFileSync(diffFile, snapshot, { flag: "wx" });
14568
+ } catch (error) {
14569
+ if (error.code !== "EEXIST" && error.code !== "EISDIR")
14570
+ throw error;
14571
+ throw new Error(`refusing to overwrite pre-existing non-snapshot path ${diffFile}`);
14572
+ }
14573
+ wroteSnapshot = true;
14574
+ const snapshotStat = fs8.lstatSync(diffFile);
14575
+ sidecar2.diffFileDev = snapshotStat.dev;
14576
+ sidecar2.diffFileIno = String(snapshotStat.ino);
14577
+ sidecar2.diffFileMtimeMs = snapshotStat.mtimeMs;
14578
+ pendingSidecar = sidecar2;
14579
+ fs8.ftruncateSync(sidecarFd, 0);
14580
+ writeFdSync(sidecarFd, Buffer.from(JSON.stringify(sidecar2, null, 2), "utf8"));
14581
+ fs8.closeSync(sidecarFd);
14582
+ sidecarFd = undefined;
14583
+ return sidecar2;
14481
14584
  };
14482
- fs8.writeFileSync(sidecarPathFor(worktreePath), JSON.stringify(sidecar, null, 2));
14585
+ let sidecar;
14586
+ try {
14587
+ sidecar = captureAndRecordSnapshot();
14588
+ } catch (error) {
14589
+ cleanupOnFailure(mode === "pr" ? reviewBranch : "");
14590
+ throw error;
14591
+ }
14483
14592
  console.log(JSON.stringify({
14484
14593
  reviewBranch: sidecar.reviewBranch === "" ? null : sidecar.reviewBranch,
14485
14594
  worktreePath: sidecar.worktreePath,
14486
14595
  base: sidecar.base,
14487
14596
  mergeBase: sidecar.mergeBase === "" ? null : sidecar.mergeBase,
14488
- diffCmd: sidecar.diffCmd
14597
+ diffCmd: sidecar.diffCmd,
14598
+ diffFile: sidecar.diffFile
14489
14599
  }, null, 2));
14490
14600
  } catch (error) {
14491
14601
  failScript(error, "pr-review worktree-setup");
14492
14602
  }
14493
14603
  });
14494
- function sidecarPathFor(worktreePath) {
14604
+ function prReviewArtifactPathFor(worktreePath, suffix) {
14495
14605
  const parent = path12.dirname(path12.resolve(worktreePath));
14496
14606
  const name = path12.basename(path12.resolve(worktreePath));
14497
- return path12.join(parent, `.${name}.prreview.json`);
14607
+ return path12.join(parent, `.${name}.prreview.${suffix}`);
14608
+ }
14609
+ function writeFdSync(fd, buf) {
14610
+ let written = 0;
14611
+ while (written < buf.length)
14612
+ written += fs8.writeSync(fd, buf, written, buf.length - written, written);
14613
+ }
14614
+ function removeOwnedSnapshotFile(worktreePath, sidecar) {
14615
+ const snapshotPath = prReviewArtifactPathFor(worktreePath, "diff");
14616
+ let fd;
14617
+ try {
14618
+ fd = fs8.openSync(snapshotPath, "r");
14619
+ } catch (error) {
14620
+ if (error.code === "ENOENT")
14621
+ return;
14622
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: snapshot at ${snapshotPath} left in place (cannot open: ${error.message})`));
14623
+ return;
14624
+ }
14625
+ try {
14626
+ const st = fs8.fstatSync(fd);
14627
+ if (!st.isFile())
14628
+ return;
14629
+ const recordedIno = sidecar.diffFileIno;
14630
+ const recordedDev = sidecar.diffFileDev;
14631
+ const recordedMtimeMs = sidecar.diffFileMtimeMs;
14632
+ const owned = typeof recordedIno === "string" && recordedIno !== "" && typeof recordedDev === "number" && Number.isFinite(recordedDev) && typeof recordedMtimeMs === "number" && Number.isFinite(recordedMtimeMs) && String(st.ino) === recordedIno && st.dev === recordedDev && st.mtimeMs === recordedMtimeMs;
14633
+ if (!owned) {
14634
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: snapshot at ${snapshotPath} left in place (file identity does not match the recorded snapshot)`));
14635
+ return;
14636
+ }
14637
+ if (st.nlink !== 1) {
14638
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: snapshot at ${snapshotPath} left in place (link count does not match the recorded snapshot)`));
14639
+ return;
14640
+ }
14641
+ const tmp = `${snapshotPath}.cleanup.${process.pid}.${randomUUID3()}`;
14642
+ try {
14643
+ fs8.renameSync(snapshotPath, tmp);
14644
+ } catch (error) {
14645
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: snapshot at ${snapshotPath} left in place (cannot detach: ${error.message})`));
14646
+ return;
14647
+ }
14648
+ const st2 = fs8.fstatSync(fd);
14649
+ let tmpStat;
14650
+ try {
14651
+ tmpStat = fs8.lstatSync(tmp);
14652
+ } catch {
14653
+ tmpStat = undefined;
14654
+ }
14655
+ if (tmpStat !== undefined && tmpStat.ino === st2.ino && tmpStat.dev === st2.dev && st2.nlink === 1 && tmpStat.nlink === 1) {
14656
+ fs8.unlinkSync(tmp);
14657
+ const st3 = fs8.fstatSync(fd);
14658
+ if (st3.nlink !== 0) {
14659
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: unlink at ${tmp} detached a pathname replacement, not the verified snapshot inode (${snapshotPath} still holds ${st3.nlink} link(s)) - the replacement is gone, the verified snapshot was NOT deleted`));
14660
+ }
14661
+ } else {
14662
+ try {
14663
+ fs8.linkSync(tmp, snapshotPath);
14664
+ fs8.unlinkSync(tmp);
14665
+ } catch (error) {
14666
+ if (error.code === "EEXIST") {
14667
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: snapshot replacement left at ${tmp} (snapshot path was recreated concurrently)`));
14668
+ } else {
14669
+ console.error(import_picocolors2.default.yellow(`worktree-cleanup: snapshot replacement left at ${tmp} (restore failed: ${error.message})`));
14670
+ }
14671
+ }
14672
+ }
14673
+ } finally {
14674
+ fs8.closeSync(fd);
14675
+ }
14498
14676
  }
14499
- function joinDirnameBeside(repoRoot, leaf) {
14500
- return path12.join(path12.dirname(path12.resolve(repoRoot)), leaf);
14677
+ function removeOwnedFreshSidecarFile(sidecarFd, worktreePath) {
14678
+ const sidecarPath = prReviewArtifactPathFor(worktreePath, "json");
14679
+ try {
14680
+ const st = fs8.fstatSync(sidecarFd);
14681
+ if (!st.isFile() || st.nlink !== 1) {
14682
+ console.error(import_picocolors2.default.yellow(`worktree-setup rollback: sidecar at ${sidecarPath} left in place (identity no longer held)`));
14683
+ return;
14684
+ }
14685
+ const tmp = `${sidecarPath}.cleanup.${process.pid}.${randomUUID3()}`;
14686
+ try {
14687
+ fs8.renameSync(sidecarPath, tmp);
14688
+ } catch (error) {
14689
+ console.error(import_picocolors2.default.yellow(`worktree-setup rollback: sidecar at ${sidecarPath} left in place (cannot detach: ${error.message})`));
14690
+ return;
14691
+ }
14692
+ const st2 = fs8.fstatSync(sidecarFd);
14693
+ let tmpStat;
14694
+ try {
14695
+ tmpStat = fs8.lstatSync(tmp);
14696
+ } catch {
14697
+ tmpStat = undefined;
14698
+ }
14699
+ if (tmpStat !== undefined && tmpStat.ino === st2.ino && tmpStat.dev === st2.dev) {
14700
+ fs8.unlinkSync(tmp);
14701
+ const st3 = fs8.fstatSync(sidecarFd);
14702
+ if (st3.nlink !== 0) {
14703
+ console.error(import_picocolors2.default.yellow(`worktree-setup rollback: unlink at ${tmp} detached a pathname replacement, not the verified sidecar inode (${sidecarPath} still holds ${st3.nlink} link(s)) - the replacement is gone, the verified sidecar was NOT deleted`));
14704
+ }
14705
+ } else {
14706
+ try {
14707
+ fs8.linkSync(tmp, sidecarPath);
14708
+ fs8.unlinkSync(tmp);
14709
+ } catch (error) {
14710
+ if (error.code === "EEXIST") {
14711
+ console.error(import_picocolors2.default.yellow(`worktree-setup rollback: sidecar replacement left at ${tmp} (sidecar path was recreated concurrently)`));
14712
+ } else {
14713
+ console.error(import_picocolors2.default.yellow(`worktree-setup rollback: sidecar replacement left at ${tmp} (restore failed: ${error.message})`));
14714
+ }
14715
+ }
14716
+ }
14717
+ } finally {
14718
+ try {
14719
+ fs8.closeSync(sidecarFd);
14720
+ } catch {}
14721
+ }
14501
14722
  }
14502
14723
  prReviewCommand.command("worktree-cleanup").description("Remove the review worktree per pr-review.md \xA7 Worktree isolation cleanup: refuses unless --report-saved is given OR the " + "setup sidecar records report-saved; removes the worktree, prunes, and deletes EXACTLY the recorded review branch \u2014 a " + "--branch argument that disagrees with the sidecar is refused (never delete a foreign/pre-existing branch)").requiredOption("--path <dir>", "Worktree directory created by worktree-setup").requiredOption("--branch <name>", "The recorded review branch (must match the setup sidecar for PR mode)").option("--report-saved", "Assert the local report has been saved before removal").action((options) => {
14503
14724
  try {
14504
14725
  const worktreePath = path12.resolve(resolveCliPath(options.path));
14505
- const sidecarPath = sidecarPathFor(worktreePath);
14726
+ const sidecarPath = prReviewArtifactPathFor(worktreePath, "json");
14506
14727
  if (!fs8.existsSync(sidecarPath)) {
14507
14728
  throw new Error(`no setup sidecar found at ${sidecarPath} - run pr-review worktree-setup first (foreign worktrees are never cleaned here)`);
14508
14729
  }
@@ -14534,6 +14755,7 @@ prReviewCommand.command("worktree-cleanup").description("Remove the review workt
14534
14755
  }
14535
14756
  gitSync(["branch", "-D", sidecar.reviewBranch], gitRoot);
14536
14757
  }
14758
+ removeOwnedSnapshotFile(worktreePath, sidecar);
14537
14759
  fs8.rmSync(sidecarPath, { force: true });
14538
14760
  console.log(import_picocolors2.default.green(`worktree-cleanup: removed ${worktreePath}${sidecar.reviewBranch !== "" ? ` + deleted ${sidecar.reviewBranch}` : " (no local branch to delete)"}`));
14539
14761
  } catch (error) {
@@ -14583,7 +14805,7 @@ function measureLargestTouchedTotal(diffOutput, headRef, cwd) {
14583
14805
  }
14584
14806
  return maxTotal;
14585
14807
  }
14586
- prReviewCommand.command("seat-prompt").description("Generate the read-only audit-seat prompt per pr-review.md \xA7 Seat prompts (Hard Rules 4/5 verbatim, payload-return contract, " + "no-verdict/no-post clauses, slug <domain>-<seat>, Merge-class instruction on stage 2); prints the prompt").requiredOption("--stage <1|2>", "Pipeline stage (1 = collect, 2 = domain/security)").requiredOption("--domain <d>", "Review domain for this seat").requiredOption("--seat <id>", "Seat id (slug becomes <domain>-<seat>)").requiredOption("--worktree <path>", "Absolute review worktree path").option("--security", "Mark this seat as the security-lens seat (stage 2)").option("--skill-root <dir>", "Skill root containing references/pr-review.md (default: resolved skills/mstar-audit)").option("--recon <facts...>", "Recon facts (variadic: --recon fact1 fact2 ...)").option("--tier <quick|default|deep>", "Prompt tier (SP-A): quick shrinks read-first + folds the security lens in-seat; deep adds cross-domain security seat + stage-as-wave (default: default)").action((options) => {
14808
+ prReviewCommand.command("seat-prompt").description("Generate the read-only audit-seat prompt per pr-review.md \xA7 Seat prompts (Hard Rules 4/5 verbatim, payload-return contract, " + "no-verdict/no-post clauses, slug <domain>-<seat>, Merge-class instruction on stage 2); prints the prompt").requiredOption("--stage <1|2>", "Pipeline stage (1 = collect, 2 = domain/security)").requiredOption("--domain <d>", "Review domain for this seat").requiredOption("--seat <id>", "Seat id (slug becomes <domain>-<seat>)").requiredOption("--worktree <path>", "Absolute review worktree path").option("--security", "Mark this seat as the security-lens seat (stage 2)").option("--skill-root <dir>", "Skill root containing references/pr-review.md (default: resolved skills/mstar-audit)").option("--recon <facts...>", "Recon facts (variadic: --recon fact1 fact2 ...)").option("--tier <quick|default|deep>", "Prompt tier (SP-A): quick shrinks read-first + folds the security lens in-seat; deep adds cross-domain security seat + stage-as-wave (default: default)").option("--diff-file <path>", "Absolute path to the pinned diff snapshot written by worktree-setup (read-first ingredient)").action((options) => {
14587
14809
  try {
14588
14810
  if (options.stage !== "1" && options.stage !== "2") {
14589
14811
  throw new SddScriptError(`usage: pr-review seat-prompt \u2014 --stage must be 1 or 2, got ${JSON.stringify(options.stage)}`, 2);
@@ -14601,7 +14823,8 @@ prReviewCommand.command("seat-prompt").description("Generate the read-only audit
14601
14823
  worktreePath: path12.resolve(options.worktree),
14602
14824
  reconFacts: options.recon ?? [],
14603
14825
  ...options.security === true ? { securitySeat: true } : {},
14604
- ...tier !== undefined ? { tier } : {}
14826
+ ...tier !== undefined ? { tier } : {},
14827
+ ...options.diffFile !== undefined && options.diffFile !== "" ? { diffFile: path12.resolve(resolveCliPath(options.diffFile)) } : {}
14605
14828
  });
14606
14829
  console.log(prompt);
14607
14830
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/cli",
3
- "version": "3.6.0-alpha.3",
3
+ "version": "3.6.0-alpha.4",
4
4
  "description": "Morning Star harness CLI — installer bootstrap + mstar workflow verbs (path/status/lease/sdd/iteration/dispatch/worktree/lint/design-md/audit/compound/host/skill).",
5
5
  "license": "MIT",
6
6
  "repository": {