@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.
Files changed (2) hide show
  1. package/dist/mstar-harness.js +351 -145
  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) {
@@ -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 MARKETPLACE_NAME = "personal";
11136
- var MARKETPLACE_DISPLAY_NAME = "Personal";
11137
- var GLOBAL_MARKETPLACE_PATH = path5.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
11138
- var PLUGIN_CATEGORY = "Productivity";
11139
- var CODEX_PLUGIN_LINK = ".codex/plugins/mstar-harness";
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 globalMarketplacePath() {
11163
- return GLOBAL_MARKETPLACE_PATH;
11164
+ function runCodex(args, dryRun, timeoutMs) {
11165
+ return runCliCommand([CODEX_BIN, ...args], { dryRun, timeoutMs, env: process.env });
11164
11166
  }
11165
- function projectMarketplacePath() {
11166
- return path5.join(resolveProjectRoot2(), ".agents", "plugins", "marketplace.json");
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 pathToMarketplace = marketplacePath(scope);
11305
- const current = readJson2(pathToMarketplace);
11306
- const next = upsertEntry(current, scope);
11307
- const existingEntry = findEntry(current);
11308
- const notes = ensureLocalHarnessRepo(dryRun);
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(ensureSymlink(HARNESS_REPO_PATH, path5.join(projectRoot, CODEX_PLUGIN_LINK), dryRun));
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: pathToMarketplace,
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
- if (!fs3.existsSync(pathToMarketplace)) {
11333
- return { location: pathToMarketplace, errors: [...errors2, `Missing Codex marketplace: ${pathToMarketplace}`] };
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
- const marketplace = readJson2(pathToMarketplace);
11336
- if (marketplace.name !== MARKETPLACE_NAME) {
11337
- errors2.push(`Codex personal marketplace name must be ${MARKETPLACE_NAME}.`);
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: pathToMarketplace, errors: errors2 };
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 PLUGIN_CATEGORY2 = "Productivity";
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: PLUGIN_CATEGORY2
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 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;
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
- 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;
14199
14197
  }
14200
- return gitIf(diffCmdArgs, worktreePath).length === 0;
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: ../<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) => {
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 ?? 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
+ }
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
- const rollbackNewWorktree = (branchToDelete) => {
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
- rollbackNewWorktree(mode === "pr" ? reviewBranch : "");
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 recordedBase = mode === "pr" || mode !== "commit" && !baseRef.startsWith("origin/") ? `origin/${baseRef}` : baseRef;
14489
- const sidecar = {
14490
- reviewBranch: mode === "pr" ? reviewBranch : "",
14491
- worktreePath,
14492
- base: recordedBase,
14493
- mergeBase,
14494
- diffCmd,
14495
- reportSaved: false,
14496
- createdAt: new Date().toISOString(),
14497
- 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;
14498
14584
  };
14499
- 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
+ }
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 sidecarPathFor(worktreePath) {
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.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
+ }
14515
14676
  }
14516
- function joinDirnameBeside(repoRoot, leaf) {
14517
- 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
+ }
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 = sidecarPathFor(worktreePath);
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.2",
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": {