@mstar-harness/cli 3.6.0-alpha.2 → 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 +351 -145
- 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) {
|
|
@@ -10916,11 +10923,6 @@ import fs3 from "node:fs";
|
|
|
10916
10923
|
import os2 from "node:os";
|
|
10917
10924
|
import path5 from "node:path";
|
|
10918
10925
|
|
|
10919
|
-
// src/adapters/shared-install.ts
|
|
10920
|
-
import fs2 from "node:fs";
|
|
10921
|
-
import os from "node:os";
|
|
10922
|
-
import path4 from "node:path";
|
|
10923
|
-
|
|
10924
10926
|
// src/exec.ts
|
|
10925
10927
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
10926
10928
|
function runCliCommand(command, opts = {}) {
|
|
@@ -10936,6 +10938,9 @@ function runCliCommand(command, opts = {}) {
|
|
|
10936
10938
|
}
|
|
10937
10939
|
|
|
10938
10940
|
// src/adapters/shared-install.ts
|
|
10941
|
+
import fs2 from "node:fs";
|
|
10942
|
+
import os from "node:os";
|
|
10943
|
+
import path4 from "node:path";
|
|
10939
10944
|
var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
|
|
10940
10945
|
var PLUGIN_NAME = "morning-star-harness";
|
|
10941
10946
|
var HARNESS_REPO_PATH = path4.join(os.homedir(), ".mstar", "harness");
|
|
@@ -11126,17 +11131,14 @@ function appendGitignore(projectRoot, entries, dryRun) {
|
|
|
11126
11131
|
function appendHarnessProjectGitignore(projectRoot, dryRun) {
|
|
11127
11132
|
return appendGitignore(projectRoot, HARNESS_PROCESS_GITIGNORE, dryRun);
|
|
11128
11133
|
}
|
|
11129
|
-
function homeRelativeSourcePath(targetPath) {
|
|
11130
|
-
const rel = path4.relative(os.homedir(), targetPath).split(path4.sep).join("/");
|
|
11131
|
-
return rel.startsWith("..") ? targetPath : `./${rel}`;
|
|
11132
|
-
}
|
|
11133
11134
|
|
|
11134
11135
|
// src/adapters/codex.ts
|
|
11135
|
-
var
|
|
11136
|
-
var
|
|
11137
|
-
var
|
|
11138
|
-
var
|
|
11139
|
-
var
|
|
11136
|
+
var CODEX_BIN = "codex";
|
|
11137
|
+
var CODEX_LOCAL_TIMEOUT_MS = 1e4;
|
|
11138
|
+
var CODEX_MARKETPLACE_TIMEOUT_MS = 300000;
|
|
11139
|
+
var MARKETPLACE_GIT_SOURCE = "btspoony/mstar-harness";
|
|
11140
|
+
var MARKETPLACE_NAME = "mstar-repo";
|
|
11141
|
+
var CODEX_INSTALL_HINT = "Install the Codex CLI (https://github.com/openai/codex), e.g. `npm install -g @openai/codex`, then re-run init.";
|
|
11140
11142
|
var CODEX_AGENT_NAMES = [
|
|
11141
11143
|
"product-manager",
|
|
11142
11144
|
"architect",
|
|
@@ -11159,11 +11161,50 @@ var CODEX_PROJECT_COMMAND_NAMES = [
|
|
|
11159
11161
|
"amazing-pr-review"
|
|
11160
11162
|
];
|
|
11161
11163
|
var GLOBAL_ITERATION_SKILLS_WARNING = "Codex project-scoped commands (iteration-start / iteration-drive / iteration-loop / codebase-audit / amazing-pr-review) are installed as project-local skills under .agents/skills/ only. Global install skips them to avoid polluting other code agents. Re-run with --scope project to enable.";
|
|
11162
|
-
function
|
|
11163
|
-
return
|
|
11164
|
+
function runCodex(args, dryRun, timeoutMs) {
|
|
11165
|
+
return runCliCommand([CODEX_BIN, ...args], { dryRun, timeoutMs, env: process.env });
|
|
11164
11166
|
}
|
|
11165
|
-
function
|
|
11166
|
-
|
|
11167
|
+
function codexAvailable() {
|
|
11168
|
+
try {
|
|
11169
|
+
runCodex(["--version"], false, CODEX_LOCAL_TIMEOUT_MS);
|
|
11170
|
+
return true;
|
|
11171
|
+
} catch {
|
|
11172
|
+
return false;
|
|
11173
|
+
}
|
|
11174
|
+
}
|
|
11175
|
+
function configuredMarketplaceNames(dryRun) {
|
|
11176
|
+
if (dryRun)
|
|
11177
|
+
return [];
|
|
11178
|
+
const dump = runCodex(["plugin", "marketplace", "list", "--json"], false, CODEX_LOCAL_TIMEOUT_MS);
|
|
11179
|
+
const parsed = JSON.parse(dump);
|
|
11180
|
+
const list = Array.isArray(parsed.marketplaces) ? parsed.marketplaces : [];
|
|
11181
|
+
return list.map((entry) => entry && typeof entry.name === "string" ? entry.name : "").filter((name) => name !== "");
|
|
11182
|
+
}
|
|
11183
|
+
function installedPluginIds(dryRun) {
|
|
11184
|
+
if (dryRun)
|
|
11185
|
+
return [];
|
|
11186
|
+
const dump = runCodex(["plugin", "list", "--json"], false, CODEX_LOCAL_TIMEOUT_MS);
|
|
11187
|
+
const parsed = JSON.parse(dump);
|
|
11188
|
+
const list = Array.isArray(parsed.installed) ? parsed.installed : [];
|
|
11189
|
+
return list.map((entry) => entry && typeof entry.pluginId === "string" ? entry.pluginId : "").filter((pluginId) => pluginId !== "");
|
|
11190
|
+
}
|
|
11191
|
+
function legacyPersonalMarketplaceNote() {
|
|
11192
|
+
const legacyPath = path5.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
|
|
11193
|
+
let raw;
|
|
11194
|
+
try {
|
|
11195
|
+
raw = fs3.readFileSync(legacyPath, "utf8");
|
|
11196
|
+
} catch {
|
|
11197
|
+
return null;
|
|
11198
|
+
}
|
|
11199
|
+
try {
|
|
11200
|
+
const parsed = JSON.parse(raw);
|
|
11201
|
+
const plugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
11202
|
+
const hasMstar = plugins.some((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && ("name" in entry) && entry.name === PLUGIN_NAME);
|
|
11203
|
+
if (hasMstar) {
|
|
11204
|
+
return `Legacy personal marketplace entry found at ${legacyPath} \u2014 the ${PLUGIN_NAME} plugin now installs from the repo marketplace (${MARKETPLACE_GIT_SOURCE}). Remove the entry, then install: codex plugin add ${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
11205
|
+
}
|
|
11206
|
+
} catch {}
|
|
11207
|
+
return null;
|
|
11167
11208
|
}
|
|
11168
11209
|
function agentSourcePath(agentName) {
|
|
11169
11210
|
return path5.join(HARNESS_REPO_PATH, "codex", "agents", `${agentName}.toml`);
|
|
@@ -11174,76 +11215,6 @@ function globalAgentLinkPath(agentName) {
|
|
|
11174
11215
|
function projectAgentLinkPath(agentName) {
|
|
11175
11216
|
return path5.join(resolveProjectRoot2(), ".codex", "agents", `${agentName}.toml`);
|
|
11176
11217
|
}
|
|
11177
|
-
function mstarEntry(scope) {
|
|
11178
|
-
const sourcePath = scope === "global" ? homeRelativeSourcePath(HARNESS_REPO_PATH) : `./${CODEX_PLUGIN_LINK}`;
|
|
11179
|
-
return {
|
|
11180
|
-
name: PLUGIN_NAME,
|
|
11181
|
-
source: {
|
|
11182
|
-
source: "local",
|
|
11183
|
-
path: sourcePath
|
|
11184
|
-
},
|
|
11185
|
-
policy: {
|
|
11186
|
-
installation: "AVAILABLE",
|
|
11187
|
-
authentication: "ON_INSTALL"
|
|
11188
|
-
},
|
|
11189
|
-
category: PLUGIN_CATEGORY
|
|
11190
|
-
};
|
|
11191
|
-
}
|
|
11192
|
-
function normalizeMarketplace(raw) {
|
|
11193
|
-
const next = ensureObject(raw);
|
|
11194
|
-
next.name = MARKETPLACE_NAME;
|
|
11195
|
-
const iface = ensureObject(next.interface);
|
|
11196
|
-
if (typeof iface.displayName !== "string" || !iface.displayName.trim()) {
|
|
11197
|
-
iface.displayName = MARKETPLACE_DISPLAY_NAME;
|
|
11198
|
-
}
|
|
11199
|
-
next.interface = iface;
|
|
11200
|
-
if (!Array.isArray(next.plugins)) {
|
|
11201
|
-
next.plugins = [];
|
|
11202
|
-
}
|
|
11203
|
-
return next;
|
|
11204
|
-
}
|
|
11205
|
-
function upsertEntry(raw, scope) {
|
|
11206
|
-
const next = normalizeMarketplace(raw);
|
|
11207
|
-
const plugins = next.plugins.filter((entry) => {
|
|
11208
|
-
return !(entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME);
|
|
11209
|
-
});
|
|
11210
|
-
plugins.push(mstarEntry(scope));
|
|
11211
|
-
next.plugins = plugins;
|
|
11212
|
-
return next;
|
|
11213
|
-
}
|
|
11214
|
-
function findEntry(raw) {
|
|
11215
|
-
const plugins = Array.isArray(raw.plugins) ? raw.plugins : [];
|
|
11216
|
-
return plugins.find((entry) => {
|
|
11217
|
-
return entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME;
|
|
11218
|
-
});
|
|
11219
|
-
}
|
|
11220
|
-
function validateEntryShape(entry, scope, marketplacePath) {
|
|
11221
|
-
const errors2 = [];
|
|
11222
|
-
if (!entry) {
|
|
11223
|
-
errors2.push(`Missing ${PLUGIN_NAME} entry in ${marketplacePath}.`);
|
|
11224
|
-
return errors2;
|
|
11225
|
-
}
|
|
11226
|
-
const source = ensureObject(entry.source);
|
|
11227
|
-
const expectedSourcePath = mstarEntry(scope).source.path;
|
|
11228
|
-
if (source.source !== "local")
|
|
11229
|
-
errors2.push("Codex marketplace entry source.source must be `local`.");
|
|
11230
|
-
if (source.path !== expectedSourcePath) {
|
|
11231
|
-
errors2.push(`Codex marketplace entry source.path must be ${expectedSourcePath}.`);
|
|
11232
|
-
}
|
|
11233
|
-
const policy = ensureObject(entry.policy);
|
|
11234
|
-
if (policy.installation !== "AVAILABLE")
|
|
11235
|
-
errors2.push("Codex marketplace entry policy.installation must be AVAILABLE.");
|
|
11236
|
-
if (policy.authentication !== "ON_INSTALL")
|
|
11237
|
-
errors2.push("Codex marketplace entry policy.authentication must be ON_INSTALL.");
|
|
11238
|
-
if (entry.category !== PLUGIN_CATEGORY)
|
|
11239
|
-
errors2.push(`Codex marketplace entry category must be ${PLUGIN_CATEGORY}.`);
|
|
11240
|
-
return errors2;
|
|
11241
|
-
}
|
|
11242
|
-
function marketplacePath(scope) {
|
|
11243
|
-
if (scope === "global")
|
|
11244
|
-
return globalMarketplacePath();
|
|
11245
|
-
return projectMarketplacePath();
|
|
11246
|
-
}
|
|
11247
11218
|
function ensureAgentLinks(scope, dryRun) {
|
|
11248
11219
|
const notes = [];
|
|
11249
11220
|
for (const agentName of CODEX_AGENT_NAMES) {
|
|
@@ -11301,59 +11272,78 @@ function validateIterationSkillLinks() {
|
|
|
11301
11272
|
return errors2;
|
|
11302
11273
|
}
|
|
11303
11274
|
function runInit(scope, dryRun) {
|
|
11304
|
-
const
|
|
11305
|
-
|
|
11306
|
-
const
|
|
11307
|
-
|
|
11308
|
-
|
|
11275
|
+
const notes = [];
|
|
11276
|
+
notes.push(...ensureLocalHarnessRepo(dryRun));
|
|
11277
|
+
const marketplaceArgs = ["plugin", "marketplace", "add", REPO_URL, "--ref", "main"];
|
|
11278
|
+
if (dryRun) {
|
|
11279
|
+
notes.push(`Would run: ${CODEX_BIN} ${marketplaceArgs.join(" ")}`);
|
|
11280
|
+
} else {
|
|
11281
|
+
if (!codexAvailable()) {
|
|
11282
|
+
throw new Error(`${CODEX_BIN} CLI not found on PATH. ${CODEX_INSTALL_HINT}`);
|
|
11283
|
+
}
|
|
11284
|
+
try {
|
|
11285
|
+
const already = configuredMarketplaceNames(false).includes(MARKETPLACE_NAME);
|
|
11286
|
+
if (already) {
|
|
11287
|
+
notes.push(`Marketplace ${MARKETPLACE_NAME} already configured.`);
|
|
11288
|
+
} else {
|
|
11289
|
+
runCodex(marketplaceArgs, false, CODEX_MARKETPLACE_TIMEOUT_MS);
|
|
11290
|
+
notes.push(`Added marketplace ${MARKETPLACE_NAME} from ${REPO_URL} (ref main).`);
|
|
11291
|
+
}
|
|
11292
|
+
} catch (error) {
|
|
11293
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11294
|
+
throw new Error(`Failed to add marketplace via ${CODEX_BIN}: ${message}`);
|
|
11295
|
+
}
|
|
11296
|
+
notes.push(`Next: codex plugin add ${PLUGIN_NAME}@${MARKETPLACE_NAME}`);
|
|
11297
|
+
}
|
|
11309
11298
|
if (scope === "project") {
|
|
11310
11299
|
const projectRoot = resolveProjectRoot2();
|
|
11311
|
-
notes.push(
|
|
11312
|
-
notes.push(...appendGitignore(projectRoot, [CODEX_PLUGIN_LINK, ".codex/agents/*.toml"], dryRun));
|
|
11300
|
+
notes.push(...appendGitignore(projectRoot, [".codex/agents/*.toml"], dryRun));
|
|
11313
11301
|
notes.push(...appendHarnessProjectGitignore(projectRoot, dryRun));
|
|
11314
11302
|
notes.push(...ensureIterationSkillLinks(dryRun));
|
|
11315
11303
|
} else {
|
|
11316
11304
|
notes.push(GLOBAL_ITERATION_SKILLS_WARNING);
|
|
11317
11305
|
}
|
|
11318
11306
|
notes.push(...ensureAgentLinks(scope, dryRun));
|
|
11319
|
-
if (!dryRun)
|
|
11320
|
-
writeJson2(pathToMarketplace, next);
|
|
11321
|
-
notes.push(existingEntry ? `Updated ${PLUGIN_NAME} local marketplace entry.` : `Added ${PLUGIN_NAME} local marketplace entry.`);
|
|
11322
|
-
notes.push(`Source path: ${mstarEntry(scope).source.path}`);
|
|
11323
|
-
notes.push(`Install after init: codex plugin add ${PLUGIN_NAME} --marketplace ${MARKETPLACE_NAME}`);
|
|
11324
11307
|
return {
|
|
11325
|
-
location:
|
|
11308
|
+
location: `${CODEX_BIN} marketplaces (config.toml)`,
|
|
11326
11309
|
notes
|
|
11327
11310
|
};
|
|
11328
11311
|
}
|
|
11329
11312
|
function runDoctor(scope) {
|
|
11330
|
-
const pathToMarketplace = marketplacePath(scope);
|
|
11331
11313
|
const errors2 = validateLocalHarnessRepo();
|
|
11332
|
-
|
|
11333
|
-
|
|
11314
|
+
const notes = [];
|
|
11315
|
+
const legacyNote = legacyPersonalMarketplaceNote();
|
|
11316
|
+
if (legacyNote)
|
|
11317
|
+
notes.push(legacyNote);
|
|
11318
|
+
if (!codexAvailable()) {
|
|
11319
|
+
errors2.push(`${CODEX_BIN} CLI not found on PATH. ${CODEX_INSTALL_HINT}`);
|
|
11320
|
+
return { location: `${CODEX_BIN} marketplaces (config.toml)`, errors: errors2, notes };
|
|
11334
11321
|
}
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11322
|
+
try {
|
|
11323
|
+
const marketplaces = configuredMarketplaceNames(false);
|
|
11324
|
+
if (!marketplaces.includes(MARKETPLACE_NAME)) {
|
|
11325
|
+
errors2.push(`Marketplace ${MARKETPLACE_NAME} not configured (run init, or: ${CODEX_BIN} plugin marketplace add ${REPO_URL} --ref main).`);
|
|
11326
|
+
}
|
|
11327
|
+
const installed = installedPluginIds(false);
|
|
11328
|
+
const pluginId = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
11329
|
+
if (marketplaces.includes(MARKETPLACE_NAME) && !installed.includes(pluginId)) {
|
|
11330
|
+
notes.push(`Plugin not installed yet: ${CODEX_BIN} plugin add ${pluginId}`);
|
|
11331
|
+
}
|
|
11332
|
+
} catch (error) {
|
|
11333
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11334
|
+
errors2.push(`Could not query ${CODEX_BIN} plugin marketplace list: ${message}`);
|
|
11338
11335
|
}
|
|
11339
|
-
errors2.push(...validateEntryShape(findEntry(marketplace), scope, pathToMarketplace));
|
|
11340
11336
|
if (scope === "project") {
|
|
11341
11337
|
const projectRoot = resolveProjectRoot2();
|
|
11342
|
-
errors2.push(...validateSymlink(HARNESS_REPO_PATH, path5.join(projectRoot, CODEX_PLUGIN_LINK)));
|
|
11343
11338
|
const gitignorePath = path5.join(projectRoot, ".gitignore");
|
|
11344
11339
|
const gitignore = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
|
|
11345
|
-
const lines = gitignore.split(/\r?\n/);
|
|
11346
|
-
if (!lines.includes(CODEX_PLUGIN_LINK))
|
|
11347
|
-
errors2.push(`Missing .gitignore entry: ${CODEX_PLUGIN_LINK}`);
|
|
11348
|
-
if (!lines.includes(".codex/agents/*.toml"))
|
|
11349
|
-
errors2.push("Missing .gitignore entry: .codex/agents/*.toml");
|
|
11350
11340
|
for (const entry of missingHarnessProcessGitignoreEntries(gitignore)) {
|
|
11351
11341
|
errors2.push(`Missing .gitignore entry: ${entry}`);
|
|
11352
11342
|
}
|
|
11353
11343
|
errors2.push(...validateIterationSkillLinks());
|
|
11354
11344
|
}
|
|
11355
11345
|
errors2.push(...validateAgentLinks(scope));
|
|
11356
|
-
return { location:
|
|
11346
|
+
return { location: `${CODEX_BIN} marketplaces (config.toml)`, errors: errors2, notes };
|
|
11357
11347
|
}
|
|
11358
11348
|
var codexAdapter = {
|
|
11359
11349
|
target: "codex",
|
|
@@ -11921,7 +11911,7 @@ var MARKETPLACE_ID = "mstar-local";
|
|
|
11921
11911
|
var MARKETPLACE_NAME2 = "mstar-local";
|
|
11922
11912
|
var MARKETPLACE_DESCRIPTION = "Morning Star harness marketplace (GitHub source).";
|
|
11923
11913
|
var PLUGIN_DESCRIPTION = "Multi-agent code harness framework with unified skills for OpenCode, Cursor, Codex, Kimi Code, and ZCode.";
|
|
11924
|
-
var
|
|
11914
|
+
var PLUGIN_CATEGORY = "Productivity";
|
|
11925
11915
|
var GITHUB_REPO = "btspoony/mstar-harness";
|
|
11926
11916
|
var GITHUB_REF = "main";
|
|
11927
11917
|
var ZCODE_PLUGIN_MARKER = ".zcode-plugin/plugin.json";
|
|
@@ -11941,7 +11931,7 @@ function marketplacePluginEntry() {
|
|
|
11941
11931
|
source: { ...GITHUB_SOURCE },
|
|
11942
11932
|
description: PLUGIN_DESCRIPTION,
|
|
11943
11933
|
version: readHarnessVersion(),
|
|
11944
|
-
category:
|
|
11934
|
+
category: PLUGIN_CATEGORY
|
|
11945
11935
|
};
|
|
11946
11936
|
}
|
|
11947
11937
|
function knownMarketplaceEntry(existing) {
|
|
@@ -14153,6 +14143,9 @@ prReviewCommand.command("validate-report").description("Validate a saved local P
|
|
|
14153
14143
|
function gitSync(args, cwd) {
|
|
14154
14144
|
return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
14155
14145
|
}
|
|
14146
|
+
function gitRaw(args, cwd) {
|
|
14147
|
+
return execFileSync10("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], maxBuffer: GIT_CAPTURE_MAX_BYTES });
|
|
14148
|
+
}
|
|
14156
14149
|
function ghSync(args, input) {
|
|
14157
14150
|
return execFileSync10("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...input !== undefined ? { input } : {} }).trim();
|
|
14158
14151
|
}
|
|
@@ -14178,7 +14171,11 @@ function refResolves(ref, cwd) {
|
|
|
14178
14171
|
}
|
|
14179
14172
|
function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
|
|
14180
14173
|
if (diffCmdArgs[0] === "__working_tree__") {
|
|
14181
|
-
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;
|
|
14182
14179
|
if (dirty)
|
|
14183
14180
|
return false;
|
|
14184
14181
|
let untracked = "";
|
|
@@ -14195,17 +14192,26 @@ function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
|
|
|
14195
14192
|
const sha = diffCmdArgs[1];
|
|
14196
14193
|
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
14197
14194
|
const diffFrom = refResolves(`${sha}^`, worktreePath) ? `${sha}^` : EMPTY_TREE;
|
|
14198
|
-
|
|
14195
|
+
const probe2 = gitProbe(["diff", diffFrom, sha], worktreePath);
|
|
14196
|
+
return probe2 === null ? false : probe2.trim().length === 0;
|
|
14199
14197
|
}
|
|
14200
|
-
|
|
14198
|
+
const probe = gitProbe(diffCmdArgs, worktreePath);
|
|
14199
|
+
return probe === null ? false : probe.length === 0;
|
|
14201
14200
|
}
|
|
14202
14201
|
function gitIf(args, cwd) {
|
|
14203
14202
|
try {
|
|
14204
|
-
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 });
|
|
14205
14204
|
} catch {
|
|
14206
14205
|
return "";
|
|
14207
14206
|
}
|
|
14208
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
|
+
}
|
|
14209
14215
|
function probeChangesetEmptyPreworktree(mode, headSpec, cwd) {
|
|
14210
14216
|
if (mode === "working-tree")
|
|
14211
14217
|
return probeChangesetEmpty(["__working_tree__"], cwd, cwd);
|
|
@@ -14332,7 +14338,7 @@ function ghApi(apiPath, payload) {
|
|
|
14332
14338
|
input: payload
|
|
14333
14339
|
});
|
|
14334
14340
|
}
|
|
14335
|
-
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) => {
|
|
14336
14342
|
try {
|
|
14337
14343
|
const modesDeclared = [
|
|
14338
14344
|
options.pr !== undefined,
|
|
@@ -14372,7 +14378,7 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
|
|
|
14372
14378
|
process.exitCode = 1;
|
|
14373
14379
|
return;
|
|
14374
14380
|
}
|
|
14375
|
-
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));
|
|
14376
14382
|
return;
|
|
14377
14383
|
}
|
|
14378
14384
|
let prNumber = 0;
|
|
@@ -14422,7 +14428,21 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
|
|
|
14422
14428
|
const baseCandidate = mode === "pr" ? namePrNumber : Number(String(Math.abs([...headSpec].reduce((h, ch) => (h * 31 + ch.charCodeAt(0)) % 1000003, 7))));
|
|
14423
14429
|
const reviewBranch = pickReviewBranchName(existing, baseCandidate === 0 ? 1 : baseCandidate, cliToday().replace(/-/g, ""));
|
|
14424
14430
|
const branchSuffix = mode === "pr" ? "" : `-${headSpec.slice(0, 8)}`;
|
|
14425
|
-
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
|
+
}
|
|
14426
14446
|
const originUrl = gitIf(["remote", "get-url", "origin"], repoRoot);
|
|
14427
14447
|
let fetched = true;
|
|
14428
14448
|
try {
|
|
@@ -14438,7 +14458,11 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
|
|
|
14438
14458
|
} catch {
|
|
14439
14459
|
fetched = false;
|
|
14440
14460
|
}
|
|
14441
|
-
|
|
14461
|
+
let wroteSnapshot = false;
|
|
14462
|
+
let wroteSidecar = false;
|
|
14463
|
+
let pendingSidecar;
|
|
14464
|
+
let sidecarFd;
|
|
14465
|
+
const cleanupOnFailure = (branchToDelete) => {
|
|
14442
14466
|
try {
|
|
14443
14467
|
if (fs8.existsSync(worktreePath))
|
|
14444
14468
|
gitSync(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
@@ -14446,6 +14470,12 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
|
|
|
14446
14470
|
if (branchToDelete !== "" && !existing.has(branchToDelete)) {
|
|
14447
14471
|
gitSync(["branch", "-D", branchToDelete], repoRoot);
|
|
14448
14472
|
}
|
|
14473
|
+
if (wroteSidecar && sidecarFd !== undefined) {
|
|
14474
|
+
removeOwnedFreshSidecarFile(sidecarFd, worktreePath);
|
|
14475
|
+
sidecarFd = undefined;
|
|
14476
|
+
}
|
|
14477
|
+
if (wroteSnapshot && pendingSidecar !== undefined)
|
|
14478
|
+
removeOwnedSnapshotFile(worktreePath, pendingSidecar);
|
|
14449
14479
|
} catch {}
|
|
14450
14480
|
};
|
|
14451
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);
|
|
@@ -14478,48 +14508,222 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
|
|
|
14478
14508
|
const changesetEmpty = probeChangesetEmpty(diffArgs, worktreePath, worktreePath);
|
|
14479
14509
|
const emptyGate = preflightChangeset(mode, { refsResolve: true, changesetEmpty });
|
|
14480
14510
|
if (changesetEmpty) {
|
|
14481
|
-
|
|
14511
|
+
cleanupOnFailure(mode === "pr" ? reviewBranch : "");
|
|
14482
14512
|
}
|
|
14483
14513
|
if (!emptyGate.ok && emptyGate.violations.some((v) => v.code === "prreview.preflight.changeset-empty")) {
|
|
14484
14514
|
printChecklist("pr-review worktree-setup preflight", emptyGate);
|
|
14485
14515
|
process.exitCode = 1;
|
|
14486
14516
|
return;
|
|
14487
14517
|
}
|
|
14488
|
-
const
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
|
|
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;
|
|
14498
14584
|
};
|
|
14499
|
-
|
|
14585
|
+
let sidecar;
|
|
14586
|
+
try {
|
|
14587
|
+
sidecar = captureAndRecordSnapshot();
|
|
14588
|
+
} catch (error) {
|
|
14589
|
+
cleanupOnFailure(mode === "pr" ? reviewBranch : "");
|
|
14590
|
+
throw error;
|
|
14591
|
+
}
|
|
14500
14592
|
console.log(JSON.stringify({
|
|
14501
14593
|
reviewBranch: sidecar.reviewBranch === "" ? null : sidecar.reviewBranch,
|
|
14502
14594
|
worktreePath: sidecar.worktreePath,
|
|
14503
14595
|
base: sidecar.base,
|
|
14504
14596
|
mergeBase: sidecar.mergeBase === "" ? null : sidecar.mergeBase,
|
|
14505
|
-
diffCmd: sidecar.diffCmd
|
|
14597
|
+
diffCmd: sidecar.diffCmd,
|
|
14598
|
+
diffFile: sidecar.diffFile
|
|
14506
14599
|
}, null, 2));
|
|
14507
14600
|
} catch (error) {
|
|
14508
14601
|
failScript(error, "pr-review worktree-setup");
|
|
14509
14602
|
}
|
|
14510
14603
|
});
|
|
14511
|
-
function
|
|
14604
|
+
function prReviewArtifactPathFor(worktreePath, suffix) {
|
|
14512
14605
|
const parent = path12.dirname(path12.resolve(worktreePath));
|
|
14513
14606
|
const name = path12.basename(path12.resolve(worktreePath));
|
|
14514
|
-
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
|
+
}
|
|
14515
14676
|
}
|
|
14516
|
-
function
|
|
14517
|
-
|
|
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
|
+
}
|
|
14518
14722
|
}
|
|
14519
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) => {
|
|
14520
14724
|
try {
|
|
14521
14725
|
const worktreePath = path12.resolve(resolveCliPath(options.path));
|
|
14522
|
-
const sidecarPath =
|
|
14726
|
+
const sidecarPath = prReviewArtifactPathFor(worktreePath, "json");
|
|
14523
14727
|
if (!fs8.existsSync(sidecarPath)) {
|
|
14524
14728
|
throw new Error(`no setup sidecar found at ${sidecarPath} - run pr-review worktree-setup first (foreign worktrees are never cleaned here)`);
|
|
14525
14729
|
}
|
|
@@ -14551,6 +14755,7 @@ prReviewCommand.command("worktree-cleanup").description("Remove the review workt
|
|
|
14551
14755
|
}
|
|
14552
14756
|
gitSync(["branch", "-D", sidecar.reviewBranch], gitRoot);
|
|
14553
14757
|
}
|
|
14758
|
+
removeOwnedSnapshotFile(worktreePath, sidecar);
|
|
14554
14759
|
fs8.rmSync(sidecarPath, { force: true });
|
|
14555
14760
|
console.log(import_picocolors2.default.green(`worktree-cleanup: removed ${worktreePath}${sidecar.reviewBranch !== "" ? ` + deleted ${sidecar.reviewBranch}` : " (no local branch to delete)"}`));
|
|
14556
14761
|
} catch (error) {
|
|
@@ -14600,7 +14805,7 @@ function measureLargestTouchedTotal(diffOutput, headRef, cwd) {
|
|
|
14600
14805
|
}
|
|
14601
14806
|
return maxTotal;
|
|
14602
14807
|
}
|
|
14603
|
-
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) => {
|
|
14604
14809
|
try {
|
|
14605
14810
|
if (options.stage !== "1" && options.stage !== "2") {
|
|
14606
14811
|
throw new SddScriptError(`usage: pr-review seat-prompt \u2014 --stage must be 1 or 2, got ${JSON.stringify(options.stage)}`, 2);
|
|
@@ -14618,7 +14823,8 @@ prReviewCommand.command("seat-prompt").description("Generate the read-only audit
|
|
|
14618
14823
|
worktreePath: path12.resolve(options.worktree),
|
|
14619
14824
|
reconFacts: options.recon ?? [],
|
|
14620
14825
|
...options.security === true ? { securitySeat: true } : {},
|
|
14621
|
-
...tier !== undefined ? { tier } : {}
|
|
14826
|
+
...tier !== undefined ? { tier } : {},
|
|
14827
|
+
...options.diffFile !== undefined && options.diffFile !== "" ? { diffFile: path12.resolve(resolveCliPath(options.diffFile)) } : {}
|
|
14622
14828
|
});
|
|
14623
14829
|
console.log(prompt);
|
|
14624
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": {
|