@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.
- package/dist/mstar-harness.js +251 -28
- package/package.json +1 -1
package/dist/mstar-harness.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
14195
|
+
const probe2 = gitProbe(["diff", diffFrom, sha], worktreePath);
|
|
14196
|
+
return probe2 === null ? false : probe2.trim().length === 0;
|
|
14182
14197
|
}
|
|
14183
|
-
|
|
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:
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
14500
|
-
|
|
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 =
|
|
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
|
+
"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": {
|