@mstar-harness/cli 3.1.2 → 3.2.0

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 +350 -95
  2. package/package.json +1 -1
@@ -2414,7 +2414,7 @@ var require_picocolors2 = __commonJS((exports, module) => {
2414
2414
  });
2415
2415
 
2416
2416
  // src/index.ts
2417
- import { execFileSync as execFileSync9 } from "child_process";
2417
+ import { execFileSync as execFileSync8 } from "child_process";
2418
2418
  import fs8 from "fs";
2419
2419
  import path12 from "path";
2420
2420
 
@@ -4069,6 +4069,8 @@ var {
4069
4069
 
4070
4070
  // ../engine/dist/engine.js
4071
4071
  import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
4072
+ import { randomUUID } from "node:crypto";
4073
+ import { basename, dirname, join, resolve } from "node:path";
4072
4074
  import { readFileSync as readFileSync2, statSync } from "node:fs";
4073
4075
  import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
4074
4076
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "node:fs";
@@ -4076,7 +4078,9 @@ import { execFileSync } from "node:child_process";
4076
4078
  import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
4077
4079
  import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "node:fs";
4078
4080
  import { dirname as dirname5, join as join6, resolve as resolve5, sep } from "node:path";
4081
+ import { mkdirSync as mkdirSync3, rmdirSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4079
4082
  import { dirname as dirname4, isAbsolute as isAbsolute3, join as join4, resolve as resolve4 } from "node:path";
4083
+ import { setTimeout as sleep } from "node:timers/promises";
4080
4084
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
4081
4085
  import { execFileSync as execFileSync2 } from "node:child_process";
4082
4086
  import { existsSync as existsSync4 } from "node:fs";
@@ -4087,11 +4091,11 @@ import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5,
4087
4091
  import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "node:fs";
4088
4092
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5 } from "node:fs";
4089
4093
  import { join as join9 } from "node:path";
4090
- import { mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
4091
- import { join as join11, resolve as resolve9 } from "node:path";
4092
- import { existsSync as existsSync7, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
4093
- import { basename as basename4, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve10, sep as sep3 } from "node:path";
4094
- import { existsSync as existsSync8 } from "node:fs";
4094
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
4095
+ import { basename as basename4, join as join11, resolve as resolve9, sep as sep3 } from "node:path";
4096
+ import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
4097
+ import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve10, sep as sep4 } from "node:path";
4098
+ import { existsSync as existsSync9 } from "node:fs";
4095
4099
  import { join as join13 } from "node:path";
4096
4100
  var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
4097
4101
  function readJson(filePath) {
@@ -4106,6 +4110,21 @@ function readJson(filePath) {
4106
4110
  throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
4107
4111
  }
4108
4112
  }
4113
+ function writeJson(filePath, value) {
4114
+ const parent = dirname(filePath);
4115
+ mkdirSync(parent, { recursive: true });
4116
+ const tmp = join(parent, `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
4117
+ try {
4118
+ writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
4119
+ `, "utf8");
4120
+ renameSync(tmp, filePath);
4121
+ } catch (error) {
4122
+ try {
4123
+ unlinkSync(tmp);
4124
+ } catch {}
4125
+ throw error;
4126
+ }
4127
+ }
4109
4128
  var MSTARC_FILE = ".mstarc";
4110
4129
  var MSTARC_SECTION = "config";
4111
4130
  var MSTARC_HARNESS_DIR_KEY = "harness_dir";
@@ -4414,7 +4433,54 @@ function validateIntegrationMergeLease(lease) {
4414
4433
  }
4415
4434
  return { ok: violations.length === 0, violations };
4416
4435
  }
4436
+ var STATUS_WRITE_LOCKDIR = ".status-write.lockdir";
4437
+ var LOCKDIR_HOLDER_PID = "holder.pid";
4417
4438
  var heldLockDirs = new AsyncLocalStorage2;
4439
+ async function withStatusWriteLock(statusPath, fn, opts = {}) {
4440
+ const lockDir = join4(dirname4(resolve4(statusPath)), STATUS_WRITE_LOCKDIR);
4441
+ const held = heldLockDirs.getStore();
4442
+ if (held !== undefined && held.has(lockDir)) {
4443
+ throw new Error(`${lockDir} is already held by this process in this async context \u2014 withStatusWriteLock is not reentrant; a nested acquisition on the same status.json is a bug`);
4444
+ }
4445
+ const timeoutMs = opts.timeoutMs ?? 30000;
4446
+ const pollMs = opts.pollMs ?? 25;
4447
+ const deadline = Date.now() + timeoutMs;
4448
+ let acquired = null;
4449
+ for (;; ) {
4450
+ try {
4451
+ mkdirSync3(lockDir);
4452
+ const st = statSync3(lockDir);
4453
+ acquired = { dev: st.dev, ino: st.ino };
4454
+ break;
4455
+ } catch (error) {
4456
+ if (error.code !== "EEXIST")
4457
+ throw error;
4458
+ if (Date.now() >= deadline) {
4459
+ throw new Error(`${lockDir} already exists \u2014 another writer holds the status write lock; Blocked (same-host exclusive lock; status-and-residuals.md \u00a7 Same-host exclusive write lock). ` + `Recovery: remove ${lockDir} if no writer is alive (holder.pid inside names the acquiring process)`);
4460
+ }
4461
+ await sleep(pollMs);
4462
+ }
4463
+ }
4464
+ try {
4465
+ writeFileSync2(join4(lockDir, LOCKDIR_HOLDER_PID), String(process.pid), "utf8");
4466
+ } catch {}
4467
+ const owns = held ?? new Set;
4468
+ owns.add(lockDir);
4469
+ try {
4470
+ return await heldLockDirs.run(owns, fn);
4471
+ } finally {
4472
+ owns.delete(lockDir);
4473
+ try {
4474
+ const current = statSync3(lockDir);
4475
+ if (acquired !== null && current.dev === acquired.dev && current.ino === acquired.ino) {
4476
+ try {
4477
+ unlinkSync2(join4(lockDir, LOCKDIR_HOLDER_PID));
4478
+ } catch {}
4479
+ rmdirSync(lockDir);
4480
+ }
4481
+ } catch {}
4482
+ }
4483
+ }
4418
4484
  var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> \u2014 <reason>"';
4419
4485
  var REQUIRED_FIELDS = [
4420
4486
  { key: "executeAs", label: "Execute as", code: "execute-as" },
@@ -4693,6 +4759,12 @@ function isPlainObject3(value) {
4693
4759
  function violation4(severity, code, message, fix) {
4694
4760
  return { ok: false, severity, code, message, fix };
4695
4761
  }
4762
+ function todayString() {
4763
+ const now = new Date;
4764
+ const month = String(now.getMonth() + 1).padStart(2, "0");
4765
+ const day = String(now.getDate()).padStart(2, "0");
4766
+ return `${now.getFullYear()}-${month}-${day}`;
4767
+ }
4696
4768
  function normalizeSeverity(value) {
4697
4769
  if (value === "warning")
4698
4770
  return "low";
@@ -4881,13 +4953,35 @@ function validateStatusV2(docOrPath, opts = {}) {
4881
4953
  violations.push(violation4("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} \u2014 the root entry mirrors the snapshot; align them`));
4882
4954
  }
4883
4955
  if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
4884
- violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} \u2014 the root entry mirrors the snapshot; align them`));
4956
+ violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} \u2014 workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot \u2014 align them or remove the colliding workflow`));
4885
4957
  }
4886
4958
  }
4887
4959
  }
4888
4960
  return { ok: violations.length === 0, violations };
4889
4961
  }
4890
4962
  var validateStatus = validateStatusV2;
4963
+ function registerWorkflowEntryLocked(statusPath, entry) {
4964
+ const harnessDir = dirname5(statusPath);
4965
+ const current = readJson(statusPath);
4966
+ const fresh = Object.keys(current).length === 0;
4967
+ const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
4968
+ if (!fresh && !Array.isArray(doc.workflows)) {
4969
+ throw new Error("refusing to modify status.json: workflows must be an array \u2014 a v1 root must be migrated first (run `mstar migrate`)");
4970
+ }
4971
+ const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
4972
+ if (existing >= 0) {
4973
+ doc.workflows[existing] = entry;
4974
+ } else {
4975
+ doc.workflows.push(entry);
4976
+ }
4977
+ doc.updated_at = todayString();
4978
+ const gate = validateStatusV2(doc, { harnessDir });
4979
+ if (!gate.ok) {
4980
+ throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
4981
+ }
4982
+ writeJson(statusPath, doc);
4983
+ return doc;
4984
+ }
4891
4985
  var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
4892
4986
  function probeTimeoutMs() {
4893
4987
  const raw = process.env.MSTAR_GIT_PROBE_TIMEOUT_MS;
@@ -6189,6 +6283,137 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
6189
6283
  }));
6190
6284
  return { outDir: resolve9(outDir), date, files: written, nextNumber: next };
6191
6285
  }
6286
+ async function promoteAuditPlans(outDir, selected, options) {
6287
+ if (selected.length === 0) {
6288
+ throw new Error("promoteAuditPlans: at least one plan id must be selected (--plans 001,002,\u2026)");
6289
+ }
6290
+ if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
6291
+ throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
6292
+ }
6293
+ const workflowId = options.workflowId ?? basename4(resolve9(outDir));
6294
+ assertSafePathComponent(workflowId, "workflow id");
6295
+ const harnessDir = resolve9(options.harnessDir);
6296
+ const statusPath = join11(harnessDir, "status.json");
6297
+ const workflowDir = join11(harnessDir, "workflows", workflowId);
6298
+ const snapshotPath = join11(workflowDir, WORKFLOW_SNAPSHOT_FILE);
6299
+ const planFiles = resolveSelectedPlanFiles(outDir, selected);
6300
+ const indexRows = readExecutionOrderIndex(outDir);
6301
+ const plans = planFiles.map((planFile) => {
6302
+ const stem = planFile.replace(/\.md$/, "");
6303
+ const num = stem.slice(0, 3);
6304
+ const indexRow = indexRows.get(num);
6305
+ const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
6306
+ return {
6307
+ id: stem,
6308
+ title,
6309
+ file: planFileRel(outDir, planFile),
6310
+ status: "Todo"
6311
+ };
6312
+ });
6313
+ const now = new Date;
6314
+ const snapshot = {
6315
+ schema_version: 1,
6316
+ id: workflowId,
6317
+ type: "plan",
6318
+ status: "running",
6319
+ started_at: now.toISOString(),
6320
+ updated_at: now.toISOString().slice(0, 10),
6321
+ plans
6322
+ };
6323
+ const entry = {
6324
+ id: workflowId,
6325
+ type: "plan",
6326
+ started_at: snapshot.started_at,
6327
+ dir: `workflows/${workflowId}`
6328
+ };
6329
+ const entryGate = validateWorkflowEntry(entry);
6330
+ if (!entryGate.ok) {
6331
+ throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
6332
+ }
6333
+ await withStatusWriteLock(statusPath, () => {
6334
+ if (existsSync7(snapshotPath)) {
6335
+ throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists ` + `(snapshot at ${snapshotPath}) \u2014 re-promote would drop its registered plan rows; ` + `remove that workflow before promoting again`);
6336
+ }
6337
+ mkdirSync7(workflowDir, { recursive: true });
6338
+ try {
6339
+ writeJson(snapshotPath, snapshot);
6340
+ registerWorkflowEntryLocked(statusPath, entry);
6341
+ } catch (error) {
6342
+ rmSync(snapshotPath, { force: true });
6343
+ try {
6344
+ if (readdirSync7(workflowDir).length === 0) {
6345
+ rmdirSync2(workflowDir);
6346
+ }
6347
+ } catch {}
6348
+ throw error;
6349
+ }
6350
+ return { workflowId, snapshotPath };
6351
+ });
6352
+ return { workflowId, snapshotPath };
6353
+ }
6354
+ function resolveSelectedPlanFiles(outDir, selected) {
6355
+ const files = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
6356
+ const byNum = new Map;
6357
+ const byStem = new Map;
6358
+ for (const file of files) {
6359
+ const stem = file.replace(/\.md$/, "");
6360
+ if (!byNum.has(stem.slice(0, 3))) {
6361
+ byNum.set(stem.slice(0, 3), file);
6362
+ }
6363
+ byStem.set(stem, file);
6364
+ }
6365
+ const resolved = [];
6366
+ const seen = new Set;
6367
+ for (const id of selected) {
6368
+ const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
6369
+ if (file === undefined) {
6370
+ throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve9(outDir)}`);
6371
+ }
6372
+ if (!seen.has(file)) {
6373
+ seen.add(file);
6374
+ resolved.push(file);
6375
+ }
6376
+ }
6377
+ return resolved;
6378
+ }
6379
+ function readExecutionOrderIndex(outDir) {
6380
+ const readmePath = join11(outDir, "README.md");
6381
+ let text;
6382
+ try {
6383
+ text = readFileSync9(readmePath, "utf8");
6384
+ } catch {
6385
+ return new Map;
6386
+ }
6387
+ const rows = new Map;
6388
+ const lines = text.split(`
6389
+ `);
6390
+ let inSection = false;
6391
+ for (const line of lines) {
6392
+ if (/^##\s+Execution order & status/.test(line)) {
6393
+ inSection = true;
6394
+ continue;
6395
+ }
6396
+ if (inSection && /^#/.test(line)) {
6397
+ break;
6398
+ }
6399
+ if (!inSection)
6400
+ continue;
6401
+ const cells = line.split(/(?<!\\)\|/).map((c) => c.trim());
6402
+ if (cells.length >= 3 && /^\d{3}$/.test(cells[1])) {
6403
+ rows.set(cells[1], { title: cells[2].replace(/\\\|/g, "|") });
6404
+ }
6405
+ }
6406
+ return rows;
6407
+ }
6408
+ function planFileRel(outDir, planFile) {
6409
+ const resolved = resolve9(outDir);
6410
+ const parts = resolved.split(sep3);
6411
+ const plansIdx = parts.lastIndexOf("plans");
6412
+ if (plansIdx >= 0) {
6413
+ return `${parts.slice(plansIdx + 1).join(sep3)}${sep3}${planFile}`;
6414
+ }
6415
+ return planFile;
6416
+ }
6192
6417
  function violation10(severity, code, message, fix) {
6193
6418
  return { ok: false, severity, code, message, fix };
6194
6419
  }
@@ -6444,7 +6669,7 @@ function collectKnowledgeDocs(dir) {
6444
6669
  if (entry.isDirectory()) {
6445
6670
  stack.push(full);
6446
6671
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
6447
- docs.push(relative4(dir, full).split(sep3).join("/"));
6672
+ docs.push(relative4(dir, full).split(sep4).join("/"));
6448
6673
  }
6449
6674
  }
6450
6675
  }
@@ -6461,7 +6686,7 @@ function normalizeIndexRef(cell) {
6461
6686
  function assertIndexRows(knowledgeDir) {
6462
6687
  const violations = [];
6463
6688
  const readmePath = join12(knowledgeDir, "README.md");
6464
- if (!existsSync7(readmePath)) {
6689
+ if (!existsSync8(readmePath)) {
6465
6690
  violations.push(violation10("medium", "compound.index.missing-readme", `missing ${readmePath} \u2014 the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
6466
6691
  return { ok: false, violations };
6467
6692
  }
@@ -6485,7 +6710,7 @@ function assertIndexRows(knowledgeDir) {
6485
6710
  return { ok: violations.length === 0, violations };
6486
6711
  }
6487
6712
  function isFileLikeRoot(root) {
6488
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename4(root));
6713
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
6489
6714
  }
6490
6715
  function scopeGuard(path2, allowedRoots) {
6491
6716
  const resolved = resolve10(path2);
@@ -6494,7 +6719,7 @@ function scopeGuard(path2, allowedRoots) {
6494
6719
  if (isFileLikeRoot(r)) {
6495
6720
  if (resolved === r)
6496
6721
  return { ok: true, violations: [] };
6497
- } else if (resolved === r || resolved.startsWith(r + sep3)) {
6722
+ } else if (resolved === r || resolved.startsWith(r + sep4)) {
6498
6723
  return { ok: true, violations: [] };
6499
6724
  }
6500
6725
  }
@@ -6642,7 +6867,7 @@ function planQualityBar(planText) {
6642
6867
  if (token !== null) {
6643
6868
  const text = trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
6644
6869
  findings.push({ token, line: i + 1, text });
6645
- violations.push(violation11("medium", "lint.plan-quality.placeholder", `placeholder token "${token}" at line ${i + 1}: "${text}"`, "replace the placeholder with concrete content before locking the plan (mstar-plan-artifacts/references/plan-quality-bar.md; templates/plan.main.md placeholder scan)"));
6870
+ violations.push(violation11("medium", "lint.plan-quality.placeholder", `placeholder token "${token}" at line ${i + 1}: "${text}"`, "replace the placeholder with concrete content before locking the plan (mstar-artifacts/references/plan-quality-bar.md; templates/plan.main.md placeholder scan)"));
6646
6871
  }
6647
6872
  }
6648
6873
  return { ok: violations.length === 0, violations, findings };
@@ -6791,7 +7016,7 @@ function validateRoleMapping(rolesDir, options = {}) {
6791
7016
  const violations = [];
6792
7017
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
6793
7018
  for (const { agentId, reference } of mapping) {
6794
- if (!existsSync8(join13(rolesDir, reference))) {
7019
+ if (!existsSync9(join13(rolesDir, reference))) {
6795
7020
  violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles \u00a7 Role Reference Mapping)`, `create ${join13(rolesDir, reference)} or fix the mapping row`));
6796
7021
  }
6797
7022
  }
@@ -6993,28 +7218,28 @@ function lintFiveQuestion(bodyText, mode = "authoring") {
6993
7218
  }
6994
7219
 
6995
7220
  // ../engine/dist/engine.js
6996
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
6997
- import { randomUUID } from "node:crypto";
6998
- import { basename, dirname, join, resolve } from "node:path";
7221
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
7222
+ import { randomUUID as randomUUID2 } from "node:crypto";
7223
+ import { basename as basename6, dirname as dirname7, join as join5, resolve as resolve8 } from "node:path";
6999
7224
  import { fileURLToPath } from "node:url";
7000
- import { readFileSync as readFileSync22, statSync as statSync3 } from "node:fs";
7225
+ import { readFileSync as readFileSync22, statSync as statSync5 } from "node:fs";
7001
7226
  import { dirname as dirname22, isAbsolute as isAbsolute6, join as join22, relative as relative3, resolve as resolve22 } from "node:path";
7002
7227
  import { existsSync as existsSync22, mkdirSync as mkdirSync22, readdirSync as readdirSync6, readFileSync as readFileSync32, realpathSync as realpathSync4, statSync as statSync22 } from "node:fs";
7003
7228
  import { execFileSync as execFileSync4 } from "node:child_process";
7004
7229
  import { basename as basename22, dirname as dirname32, isAbsolute as isAbsolute22, join as join32, relative as relative22, resolve as resolve32 } from "node:path";
7005
7230
  import { existsSync as existsSync32, readFileSync as readFileSync42, readdirSync as readdirSync22, realpathSync as realpathSync22 } from "node:fs";
7006
7231
  import { dirname as dirname52, join as join62, resolve as resolve52, sep as sep2 } from "node:path";
7007
- import { mkdirSync as mkdirSync32, rmdirSync, statSync as statSync32, unlinkSync as unlinkSync22, writeFileSync as writeFileSync22 } from "node:fs";
7232
+ import { mkdirSync as mkdirSync32, rmdirSync as rmdirSync3, statSync as statSync32, unlinkSync as unlinkSync22, writeFileSync as writeFileSync22 } from "node:fs";
7008
7233
  import { dirname as dirname42, isAbsolute as isAbsolute32, join as join42, resolve as resolve42 } from "node:path";
7009
- import { setTimeout as sleep } from "node:timers/promises";
7234
+ import { setTimeout as sleep2 } from "node:timers/promises";
7010
7235
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
7011
- import { mkdirSync as mkdirSync4 } from "node:fs";
7012
- import { join as join5 } from "node:path";
7013
- import { copyFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync82, readdirSync as readdirSync62, writeFileSync as writeFileSync4 } from "node:fs";
7014
- import { dirname as dirname7, isAbsolute as isAbsolute62, join as join10, relative as relative32, resolve as resolve8, sep as sep22 } from "node:path";
7236
+ import { mkdirSync as mkdirSync42 } from "node:fs";
7237
+ import { join as join52 } from "node:path";
7238
+ import { copyFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync82, readdirSync as readdirSync62, writeFileSync as writeFileSync42 } from "node:fs";
7239
+ import { dirname as dirname72, isAbsolute as isAbsolute62, join as join10, relative as relative32, resolve as resolve82, sep as sep22 } from "node:path";
7015
7240
  var SEVERITY_ORDER2 = ["critical", "high", "medium", "low", "nit"];
7016
7241
  function readJson2(filePath) {
7017
- if (!existsSync9(filePath))
7242
+ if (!existsSync10(filePath))
7018
7243
  return {};
7019
7244
  const content = readFileSync8(filePath, "utf8").trim();
7020
7245
  if (!content)
@@ -7025,28 +7250,28 @@ function readJson2(filePath) {
7025
7250
  throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
7026
7251
  }
7027
7252
  }
7028
- function writeJson(filePath, value) {
7029
- const parent = dirname(filePath);
7030
- mkdirSync3(parent, { recursive: true });
7031
- const tmp = join(parent, `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
7253
+ function writeJson2(filePath, value) {
7254
+ const parent = dirname7(filePath);
7255
+ mkdirSync4(parent, { recursive: true });
7256
+ const tmp = join5(parent, `.${basename6(filePath)}.${process.pid}.${randomUUID2()}.tmp`);
7032
7257
  try {
7033
- writeFileSync2(tmp, `${JSON.stringify(value, null, 2)}
7258
+ writeFileSync4(tmp, `${JSON.stringify(value, null, 2)}
7034
7259
  `, "utf8");
7035
7260
  renameSync2(tmp, filePath);
7036
7261
  } catch (error) {
7037
7262
  try {
7038
- unlinkSync2(tmp);
7263
+ unlinkSync3(tmp);
7039
7264
  } catch {}
7040
7265
  throw error;
7041
7266
  }
7042
7267
  }
7043
7268
  function resolveProjectRoot(startDir = process.cwd()) {
7044
- const start = resolve(startDir);
7269
+ const start = resolve8(startDir);
7045
7270
  let dir = start;
7046
7271
  for (;; ) {
7047
- if (existsSync9(join(dir, "package.json")) || existsSync9(join(dir, "bun.lock")))
7272
+ if (existsSync10(join5(dir, "package.json")) || existsSync10(join5(dir, "bun.lock")))
7048
7273
  return dir;
7049
- const parent = dirname(dir);
7274
+ const parent = dirname7(dir);
7050
7275
  if (parent === dir)
7051
7276
  return start;
7052
7277
  dir = parent;
@@ -7055,20 +7280,20 @@ function resolveProjectRoot(startDir = process.cwd()) {
7055
7280
  function findRootPackageJson(startDir) {
7056
7281
  let dir = startDir;
7057
7282
  for (;; ) {
7058
- const candidate = resolve(dir, "package.json");
7283
+ const candidate = resolve8(dir, "package.json");
7059
7284
  try {
7060
7285
  const pkg = JSON.parse(readFileSync8(candidate, "utf8"));
7061
7286
  if (pkg.name === "morning-star")
7062
7287
  return candidate;
7063
7288
  } catch {}
7064
- const parent = dirname(dir);
7289
+ const parent = dirname7(dir);
7065
7290
  if (parent === dir)
7066
7291
  return null;
7067
7292
  dir = parent;
7068
7293
  }
7069
7294
  }
7070
7295
  function harnessVersionFrom(moduleDir) {
7071
- const ownManifest = join(moduleDir, "..", "package.json");
7296
+ const ownManifest = join5(moduleDir, "..", "package.json");
7072
7297
  try {
7073
7298
  const pkg = JSON.parse(readFileSync8(ownManifest, "utf8"));
7074
7299
  if (typeof pkg.version === "string" && pkg.version !== "")
@@ -7085,7 +7310,7 @@ function harnessVersionFrom(moduleDir) {
7085
7310
  }
7086
7311
  }
7087
7312
  function readHarnessVersion() {
7088
- return harnessVersionFrom(dirname(fileURLToPath(import.meta.url)));
7313
+ return harnessVersionFrom(dirname7(fileURLToPath(import.meta.url)));
7089
7314
  }
7090
7315
  var MSTARC_FILE2 = ".mstarc";
7091
7316
  var MSTARC_SECTION2 = "config";
@@ -7140,7 +7365,7 @@ function parseMstarc2(text) {
7140
7365
  }
7141
7366
  function isFile3(file) {
7142
7367
  try {
7143
- return statSync3(file).isFile();
7368
+ return statSync5(file).isFile();
7144
7369
  } catch {
7145
7370
  return false;
7146
7371
  }
@@ -7374,11 +7599,11 @@ function verifyPlanExecutionLease(row, planId) {
7374
7599
  violations.push(...validateExecutionLease2(lease).violations);
7375
7600
  return { ok: violations.length === 0, violations, lease };
7376
7601
  }
7377
- var STATUS_WRITE_LOCKDIR = ".status-write.lockdir";
7378
- var LOCKDIR_HOLDER_PID = "holder.pid";
7602
+ var STATUS_WRITE_LOCKDIR2 = ".status-write.lockdir";
7603
+ var LOCKDIR_HOLDER_PID2 = "holder.pid";
7379
7604
  var heldLockDirs2 = new AsyncLocalStorage3;
7380
- async function withStatusWriteLock(statusPath, fn, opts = {}) {
7381
- const lockDir = join42(dirname42(resolve42(statusPath)), STATUS_WRITE_LOCKDIR);
7605
+ async function withStatusWriteLock2(statusPath, fn, opts = {}) {
7606
+ const lockDir = join42(dirname42(resolve42(statusPath)), STATUS_WRITE_LOCKDIR2);
7382
7607
  const held = heldLockDirs2.getStore();
7383
7608
  if (held !== undefined && held.has(lockDir)) {
7384
7609
  throw new Error(`${lockDir} is already held by this process in this async context \u2014 withStatusWriteLock is not reentrant; a nested acquisition on the same status.json is a bug`);
@@ -7399,11 +7624,11 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
7399
7624
  if (Date.now() >= deadline) {
7400
7625
  throw new Error(`${lockDir} already exists \u2014 another writer holds the status write lock; Blocked (same-host exclusive lock; status-and-residuals.md \u00a7 Same-host exclusive write lock). ` + `Recovery: remove ${lockDir} if no writer is alive (holder.pid inside names the acquiring process)`);
7401
7626
  }
7402
- await sleep(pollMs);
7627
+ await sleep2(pollMs);
7403
7628
  }
7404
7629
  }
7405
7630
  try {
7406
- writeFileSync22(join42(lockDir, LOCKDIR_HOLDER_PID), String(process.pid), "utf8");
7631
+ writeFileSync22(join42(lockDir, LOCKDIR_HOLDER_PID2), String(process.pid), "utf8");
7407
7632
  } catch {}
7408
7633
  const owns = held ?? new Set;
7409
7634
  owns.add(lockDir);
@@ -7415,9 +7640,9 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
7415
7640
  const current = statSync32(lockDir);
7416
7641
  if (acquired !== null && current.dev === acquired.dev && current.ino === acquired.ino) {
7417
7642
  try {
7418
- unlinkSync22(join42(lockDir, LOCKDIR_HOLDER_PID));
7643
+ unlinkSync22(join42(lockDir, LOCKDIR_HOLDER_PID2));
7419
7644
  } catch {}
7420
- rmdirSync(lockDir);
7645
+ rmdirSync3(lockDir);
7421
7646
  }
7422
7647
  } catch {}
7423
7648
  }
@@ -7538,10 +7763,10 @@ async function writeWorkflowSnapshot(snapshot, dir) {
7538
7763
  const detail = gate2.violations.map((v) => v.message).join("; ");
7539
7764
  throw new Error(`refusing to write invalid workflow snapshot: ${detail}`);
7540
7765
  }
7541
- const snapshotPath = join5(dir, WORKFLOW_SNAPSHOT_FILE2);
7542
- mkdirSync4(dir, { recursive: true });
7543
- await withStatusWriteLock(snapshotPath, () => {
7544
- writeJson(snapshotPath, snapshot);
7766
+ const snapshotPath = join52(dir, WORKFLOW_SNAPSHOT_FILE2);
7767
+ mkdirSync42(dir, { recursive: true });
7768
+ await withStatusWriteLock2(snapshotPath, () => {
7769
+ writeJson2(snapshotPath, snapshot);
7545
7770
  });
7546
7771
  }
7547
7772
  var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
@@ -7788,7 +8013,7 @@ function validateStatusV22(docOrPath, opts = {}) {
7788
8013
  violations.push(violation42("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} \u2014 the root entry mirrors the snapshot; align them`));
7789
8014
  }
7790
8015
  if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
7791
- violations.push(violation42("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} \u2014 the root entry mirrors the snapshot; align them`));
8016
+ violations.push(violation42("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} \u2014 workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot \u2014 align them or remove the colliding workflow`));
7792
8017
  }
7793
8018
  }
7794
8019
  }
@@ -8220,7 +8445,7 @@ function collectNotesFiles(snapshots) {
8220
8445
  if (lines.length === 0)
8221
8446
  continue;
8222
8447
  out.push({
8223
- file: join10(dirname7(snapshot.file), NOTES_LEDGER_FILE),
8448
+ file: join10(dirname72(snapshot.file), NOTES_LEDGER_FILE),
8224
8449
  source,
8225
8450
  lines
8226
8451
  });
@@ -8228,7 +8453,7 @@ function collectNotesFiles(snapshots) {
8228
8453
  return out;
8229
8454
  }
8230
8455
  function migrateHarnessTree(root, opts = {}) {
8231
- const harnessDir = resolve8(root);
8456
+ const harnessDir = resolve82(root);
8232
8457
  const workflowDir = resolveWorkflowDir2(harnessDir, { harnessDir });
8233
8458
  const projectDir = resolveProjectDir2(harnessDir, { harnessDir });
8234
8459
  const projectId = opts.projectId ?? _DEFAULT_PROJECT2;
@@ -8382,9 +8607,9 @@ async function applyMigratePlan(plan) {
8382
8607
  if (current.version === 2) {
8383
8608
  return { applied: false, message: "no-op: status.json already at schema version 2 (migrated) \u2014 nothing to do" };
8384
8609
  }
8385
- const harnessRoot = resolve8(plan.root);
8386
- const workflowRoot = resolve8(plan.workflowDir);
8387
- const projectRoot = resolve8(plan.projectDir);
8610
+ const harnessRoot = resolve82(plan.root);
8611
+ const workflowRoot = resolve82(plan.workflowDir);
8612
+ const projectRoot = resolve82(plan.projectDir);
8388
8613
  if (!isAbsolute62(plan.workflowDir) || !isAbsolute62(plan.projectDir)) {
8389
8614
  throw new Error(`refusing to apply migration: plan workflowDir/projectDir must be absolute (got ${JSON.stringify(plan.workflowDir)} / ${JSON.stringify(plan.projectDir)})`);
8390
8615
  }
@@ -8398,44 +8623,46 @@ async function applyMigratePlan(plan) {
8398
8623
  ...plan.roadmap !== null ? [plan.roadmap.file] : []
8399
8624
  ];
8400
8625
  for (const destination of allDestinations) {
8401
- const resolvedDest = resolve8(join10(plan.root, destination));
8626
+ const resolvedDest = resolve82(join10(plan.root, destination));
8402
8627
  const inside = (dir) => resolvedDest === dir || resolvedDest.startsWith(`${dir}${sep22}`);
8403
8628
  if (!inside(harnessRoot) && !inside(workflowRoot) && !inside(projectRoot)) {
8404
8629
  throw new Error(`refusing to apply migration: destination escapes the harness dir (${JSON.stringify(destination)}) \u2014 every write must stay under ${JSON.stringify(plan.root)}, the workflow dir (${JSON.stringify(plan.workflowDir)}) or the project dir (${JSON.stringify(plan.projectDir)})`);
8405
8630
  }
8406
8631
  }
8407
- mkdirSync6(join10(plan.root, dirname7(plan.archive.file)), { recursive: true });
8632
+ mkdirSync6(join10(plan.root, dirname72(plan.archive.file)), { recursive: true });
8408
8633
  copyFileSync(statusPath, join10(plan.root, plan.archive.file));
8409
8634
  for (const snapshot of plan.snapshots) {
8410
- await writeWorkflowSnapshot(snapshot.data, dirname7(workflowTargetOf(snapshot.file)));
8635
+ await writeWorkflowSnapshot(snapshot.data, dirname72(workflowTargetOf(snapshot.file)));
8411
8636
  }
8412
8637
  for (const notes of plan.notesFiles) {
8413
8638
  const filePath = workflowTargetOf(notes.file);
8414
- mkdirSync6(dirname7(filePath), { recursive: true });
8639
+ mkdirSync6(dirname72(filePath), { recursive: true });
8415
8640
  const content = notes.lines.length > 0 ? `${notes.lines.join(`
8416
8641
  `)}
8417
8642
  ` : "";
8418
- writeFileSync4(filePath, content, "utf8");
8643
+ writeFileSync42(filePath, content, "utf8");
8419
8644
  }
8420
8645
  if (plan.register !== null) {
8421
8646
  const gate2 = validateProjectRegister(plan.register.data);
8422
8647
  if (!gate2.ok) {
8423
8648
  throw new Error(`refusing to apply migration: invalid project register: ${gate2.violations.map((v) => v.message).join("; ")}`);
8424
8649
  }
8425
- const filePath = projectTargetOf(plan.register.file);
8426
- mkdirSync6(dirname7(filePath), { recursive: true });
8427
- writeJson(filePath, plan.register.data);
8650
+ if (Object.keys(plan.register.data.entries ?? {}).length > 0) {
8651
+ const filePath = projectTargetOf(plan.register.file);
8652
+ mkdirSync6(dirname72(filePath), { recursive: true });
8653
+ writeJson2(filePath, plan.register.data);
8654
+ }
8428
8655
  }
8429
8656
  if (plan.roadmap !== null) {
8430
8657
  const filePath = projectTargetOf(plan.roadmap.file);
8431
- mkdirSync6(dirname7(filePath), { recursive: true });
8432
- writeFileSync4(filePath, plan.roadmap.content, "utf8");
8658
+ mkdirSync6(dirname72(filePath), { recursive: true });
8659
+ writeFileSync42(filePath, plan.roadmap.content, "utf8");
8433
8660
  }
8434
8661
  const rootGate = validateStatusV22(plan.rootV2.data, { harnessDir: plan.root });
8435
8662
  if (!rootGate.ok) {
8436
8663
  throw new Error(`refusing to apply migration: invalid v2 root: ${rootGate.violations.map((v) => v.message).join("; ")}`);
8437
8664
  }
8438
- return withStatusWriteLock(statusPath, () => {
8665
+ return withStatusWriteLock2(statusPath, () => {
8439
8666
  const latest = readJson2(statusPath);
8440
8667
  if (latest.version === 2) {
8441
8668
  return {
@@ -8443,7 +8670,7 @@ async function applyMigratePlan(plan) {
8443
8670
  message: "no-op: status.json already at schema version 2 (migrated) \u2014 nothing to do"
8444
8671
  };
8445
8672
  }
8446
- writeJson(statusPath, plan.rootV2.data);
8673
+ writeJson2(statusPath, plan.rootV2.data);
8447
8674
  return {
8448
8675
  applied: true,
8449
8676
  message: `migrated ${plan.snapshots.length} lifecycles into workflows/, project layer seeded, root status.json replaced (v1 archived to ${plan.archive.file})`
@@ -9028,7 +9255,22 @@ import path5 from "node:path";
9028
9255
  import fs2 from "node:fs";
9029
9256
  import os from "node:os";
9030
9257
  import path4 from "node:path";
9258
+
9259
+ // src/exec.ts
9031
9260
  import { execFileSync as execFileSync6 } from "node:child_process";
9261
+ function runCliCommand(command, opts = {}) {
9262
+ if (opts.dryRun)
9263
+ return "";
9264
+ return execFileSync6(command[0], command.slice(1), {
9265
+ cwd: opts.cwd,
9266
+ encoding: "utf8",
9267
+ env: opts.env,
9268
+ stdio: "pipe",
9269
+ timeout: opts.timeoutMs
9270
+ });
9271
+ }
9272
+
9273
+ // src/adapters/shared-install.ts
9032
9274
  var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
9033
9275
  var PLUGIN_NAME = "morning-star-harness";
9034
9276
  var HARNESS_REPO_PATH = path4.join(os.homedir(), ".mstar", "harness");
@@ -9060,9 +9302,7 @@ function ensureDir(dirPath, dryRun) {
9060
9302
  fs2.mkdirSync(dirPath, { recursive: true });
9061
9303
  }
9062
9304
  function runCommand(command, cwd, dryRun) {
9063
- if (dryRun)
9064
- return;
9065
- execFileSync6(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
9305
+ runCliCommand(command, { cwd, dryRun });
9066
9306
  }
9067
9307
  function ensureLocalHarnessRepo(dryRun) {
9068
9308
  const notes = [];
@@ -9250,9 +9490,10 @@ var CODEX_PROJECT_COMMAND_NAMES = [
9250
9490
  "iteration-start",
9251
9491
  "iteration-drive",
9252
9492
  "iteration-loop",
9253
- "codebase-audit"
9493
+ "codebase-audit",
9494
+ "pr-deep-review"
9254
9495
  ];
9255
- var GLOBAL_ITERATION_SKILLS_WARNING = "Codex project-scoped commands (iteration-start / iteration-drive / iteration-loop / codebase-audit) 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.";
9496
+ var GLOBAL_ITERATION_SKILLS_WARNING = "Codex project-scoped commands (iteration-start / iteration-drive / iteration-loop / codebase-audit / pr-deep-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.";
9256
9497
  function globalMarketplacePath() {
9257
9498
  return GLOBAL_MARKETPLACE_PATH;
9258
9499
  }
@@ -9375,7 +9616,7 @@ function ensureIterationSkillLinks(dryRun) {
9375
9616
  }
9376
9617
  const gitignoreEntries = CODEX_PROJECT_COMMAND_NAMES.map(iterationSkillGitignoreEntry);
9377
9618
  notes.push(...appendGitignore(projectRoot, gitignoreEntries, dryRun));
9378
- notes.push("Installed Codex project-scoped command skills under .agents/skills/ (iteration-start, iteration-drive, iteration-loop, codebase-audit) \u2014 symlinked to harness commands/*.md.");
9619
+ notes.push("Installed Codex project-scoped command skills under .agents/skills/ (iteration-start, iteration-drive, iteration-loop, codebase-audit, pr-deep-review) \u2014 symlinked to harness commands/*.md.");
9379
9620
  return notes;
9380
9621
  }
9381
9622
  function validateIterationSkillLinks() {
@@ -9411,7 +9652,7 @@ function runInit(scope, dryRun) {
9411
9652
  }
9412
9653
  notes.push(...ensureAgentLinks(scope, dryRun));
9413
9654
  if (!dryRun)
9414
- writeJson(pathToMarketplace, next);
9655
+ writeJson2(pathToMarketplace, next);
9415
9656
  notes.push(existingEntry ? `Updated ${PLUGIN_NAME} local marketplace entry.` : `Added ${PLUGIN_NAME} local marketplace entry.`);
9416
9657
  notes.push(`Source path: ${mstarEntry(scope).source.path}`);
9417
9658
  notes.push(`Install after init: codex plugin add ${PLUGIN_NAME} --marketplace ${MARKETPLACE_NAME}`);
@@ -9547,7 +9788,6 @@ var cursorAdapter = {
9547
9788
  };
9548
9789
 
9549
9790
  // src/adapters/dsh.ts
9550
- import { execFileSync as execFileSync7 } from "node:child_process";
9551
9791
  import os4 from "node:os";
9552
9792
  import path7 from "node:path";
9553
9793
  var DSH_BIN = "dsh";
@@ -9571,14 +9811,7 @@ var DSH_PLUGIN_SPECS = ["@mstar-harness/dsh", "dsh-llm-fallbacks"];
9571
9811
  var DSH_FALLBACKS_SPEC = DSH_PLUGIN_SPECS[1];
9572
9812
  var DSH_INSTALL_HINT = "Install the DeepSeek Harness CLI (@deepseek-ai/dsh), e.g. `pnpm add -g @deepseek-ai/dsh` or `npm install -g @deepseek-ai/dsh`, then re-run init.";
9573
9813
  function runDsh(args, dryRun, timeoutMs) {
9574
- if (dryRun)
9575
- return "";
9576
- return execFileSync7(DSH_BIN, args, {
9577
- stdio: "pipe",
9578
- encoding: "utf8",
9579
- env: process.env,
9580
- timeout: timeoutMs
9581
- });
9814
+ return runCliCommand([DSH_BIN, ...args], { dryRun, timeoutMs, env: process.env });
9582
9815
  }
9583
9816
  function dshAvailable() {
9584
9817
  try {
@@ -9717,7 +9950,7 @@ var dshAdapter = {
9717
9950
  // src/adapters/omp.ts
9718
9951
  import fs5 from "node:fs";
9719
9952
  import path8 from "node:path";
9720
- import { execFileSync as execFileSync8 } from "node:child_process";
9953
+ import { execFileSync as execFileSync7 } from "node:child_process";
9721
9954
  var OMP_PLUGIN_MARKER = ".omp-plugin/plugin.json";
9722
9955
  var CLAUDE_PLUGIN_MARKER = ".claude-plugin/plugin.json";
9723
9956
  var PACKAGE_NAMES = new Set(["morning-star", PLUGIN_NAME, "github:btspoony/mstar-harness"]);
@@ -9725,20 +9958,18 @@ var SKILL_SMOKE = ["mstar-host", "mstar-harness-core", "pm"];
9725
9958
  var COMMAND_SMOKE = ["iteration-start", "iteration-drive", "iteration-loop", "codebase-audit"];
9726
9959
  function ompAvailable() {
9727
9960
  try {
9728
- execFileSync8("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
9961
+ execFileSync7("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
9729
9962
  return true;
9730
9963
  } catch {
9731
9964
  return false;
9732
9965
  }
9733
9966
  }
9734
9967
  function runOmp(args, dryRun) {
9735
- if (dryRun)
9736
- return;
9737
- execFileSync8("omp", args, { stdio: "pipe", encoding: "utf8" });
9968
+ runCliCommand(["omp", ...args], { dryRun });
9738
9969
  }
9739
9970
  function listInstalledPlugins() {
9740
9971
  try {
9741
- const raw = execFileSync8("omp", ["plugin", "list", "--json"], {
9972
+ const raw = execFileSync7("omp", ["plugin", "list", "--json"], {
9742
9973
  stdio: "pipe",
9743
9974
  encoding: "utf8"
9744
9975
  });
@@ -9814,7 +10045,7 @@ function runInit3(scope, dryRun) {
9814
10045
  notes.push(`Would update local harness repo: git -C ${HARNESS_REPO_PATH} pull --ff-only`);
9815
10046
  } else {
9816
10047
  try {
9817
- execFileSync8("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
10048
+ execFileSync7("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
9818
10049
  stdio: "pipe",
9819
10050
  encoding: "utf8"
9820
10051
  });
@@ -10175,13 +10406,13 @@ function runInit4(scope, dryRun) {
10175
10406
  if (!dryRun) {
10176
10407
  if (!fs6.existsSync(MARKETPLACE_DIR))
10177
10408
  fs6.mkdirSync(MARKETPLACE_DIR, { recursive: true });
10178
- writeJson(MARKETPLACE_JSON_PATH, buildMarketplaceJson());
10409
+ writeJson2(MARKETPLACE_JSON_PATH, buildMarketplaceJson());
10179
10410
  }
10180
10411
  notes.push(`Wrote ZCode marketplace: ${MARKETPLACE_JSON_PATH}`);
10181
10412
  const knownRaw = readJson2(KNOWN_MARKETPLACES_PATH);
10182
10413
  const knownNext = upsertKnownMarketplace(knownRaw);
10183
10414
  if (!dryRun)
10184
- writeJson(KNOWN_MARKETPLACES_PATH, knownNext);
10415
+ writeJson2(KNOWN_MARKETPLACES_PATH, knownNext);
10185
10416
  notes.push(`Registered ${MARKETPLACE_ID} marketplace in ${KNOWN_MARKETPLACES_PATH}`);
10186
10417
  notes.push(`Then in ZCode: Settings \u2192 Plugin Management \u2192 Discover \u2192 install ${PLUGIN_NAME} from the ${MARKETPLACE_ID} marketplace.`);
10187
10418
  return {
@@ -10364,7 +10595,7 @@ async function runInit5(options) {
10364
10595
  - `)}`);
10365
10596
  }
10366
10597
  if (!options.dryRun) {
10367
- writeJson(configPath, updated);
10598
+ writeJson2(configPath, updated);
10368
10599
  const persistedErrors = adapter.validateConfig?.(readJson2(configPath)) || [];
10369
10600
  if (persistedErrors.length) {
10370
10601
  throw new Error(`Post-write verification failed:
@@ -11219,7 +11450,7 @@ function resolveAuditShortSha(cwd, override) {
11219
11450
  if (override !== undefined && override !== "")
11220
11451
  return override;
11221
11452
  try {
11222
- const out = execFileSync9("git", ["rev-parse", "--short", "HEAD"], {
11453
+ const out = execFileSync8("git", ["rev-parse", "--short", "HEAD"], {
11223
11454
  cwd,
11224
11455
  encoding: "utf8",
11225
11456
  stdio: ["ignore", "pipe", "ignore"]
@@ -11255,6 +11486,30 @@ auditCommand.command("scaffold").description("Scaffold an audit-<date>/ plan dir
11255
11486
  failScript(error, "audit scaffold");
11256
11487
  }
11257
11488
  });
11489
+ auditCommand.command("promote").description("Promote selected audit plans into the v2 workflow lifecycle: write the workflow snapshot " + "(type plan, Todo rows) then register the workflow in {HARNESS_DIR}/status.json " + "(exit 2 on usage, 1 when the harness dir cannot be resolved)").argument("[audit-dir]", "audit-<date>/ directory under {PLAN_DIR}").option("--plans <ids>", "Comma-separated selected plan ids (README Plan column `001`, stem, or basename)").option("--workflow <id>", "Workflow id (default: audit-<date> basename)").option("--harness <dir>", "Harness dir containing status.json (default: resolveHarnessDir() / MSTAR_HARNESS_DIR)").action(async (auditDir, options) => {
11490
+ try {
11491
+ if (!auditDir) {
11492
+ throw new SddScriptError("usage: audit promote <audit-dir> --plans <ids> [--workflow <id>] [--harness <dir>]", 2);
11493
+ }
11494
+ const selected = parseCsv(options.plans);
11495
+ if (!selected || selected.length === 0) {
11496
+ throw new SddScriptError("usage: audit promote <audit-dir> --plans <ids> [--workflow <id>] [--harness <dir>]", 2);
11497
+ }
11498
+ const outDir = resolveCliPath(auditDir);
11499
+ if (!fs8.existsSync(outDir)) {
11500
+ throw new Error(`audit dir not found: ${outDir}`);
11501
+ }
11502
+ const harnessDir = resolveLeaseHarnessDir(options.harness);
11503
+ const result = await promoteAuditPlans(outDir, selected, {
11504
+ harnessDir,
11505
+ ...options.workflow !== undefined ? { workflowId: options.workflow } : {}
11506
+ });
11507
+ console.log(import_picocolors2.default.green(`audit promote: OK \u2014 workflow ${result.workflowId} registered`));
11508
+ console.log(` snapshot: ${result.snapshotPath}`);
11509
+ } catch (error) {
11510
+ failScript(error, "audit promote");
11511
+ }
11512
+ });
11258
11513
  var compoundCommand = program2.command("compound").description("knowledge-doc schema / index / scope checks (engine-backed)");
11259
11514
  compoundCommand.command("validate").description("Validate a knowledge doc frontmatter (schema.yaml contract); with --knowledge-dir, also assert the " + "knowledge README index rows and guard the doc inside the knowledge scope (exit 1 on violations, 2 on usage)").argument("[doc-path]", "Knowledge doc (markdown with YAML frontmatter)").option("--knowledge-dir <dir>", "Knowledge directory (enables index-row asserts + scope guard)").action((docPath, options) => {
11260
11515
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/cli",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
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": {