@mstar-harness/cli 3.4.1 → 3.5.1

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 +864 -228
  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 execFileSync10 } from "child_process";
2418
2418
  import fs8 from "fs";
2419
2419
  import path12 from "path";
2420
2420
 
@@ -4073,33 +4073,35 @@ import { randomUUID } from "node:crypto";
4073
4073
  import { basename, dirname, join, resolve } from "node:path";
4074
4074
  import { readFileSync as readFileSync2, statSync } from "node:fs";
4075
4075
  import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
4076
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4076
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4077
4077
  import { execFileSync } from "node:child_process";
4078
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve6 } from "node:path";
4079
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "node:fs";
4080
- import { join as join7, resolve as resolve5 } from "node:path";
4078
+ import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
4079
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
4080
+ import { basename as basename2, join as join8, resolve as resolve6 } from "node:path";
4081
4081
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
4082
4082
  import { mkdirSync as mkdirSync2, rmdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4083
4083
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
4084
4084
  import { setTimeout as sleep } from "node:timers/promises";
4085
4085
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
4086
- import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "node:fs";
4087
- import { dirname as dirname4, join as join6, resolve as resolve4, sep } from "node:path";
4086
+ import { existsSync as existsSync3, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
4087
+ import { isAbsolute as isAbsolute3, join as join5, resolve as resolve4 } from "node:path";
4088
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync } from "node:fs";
4089
+ import { dirname as dirname4, join as join7, resolve as resolve5, sep } from "node:path";
4088
4090
  import { execFileSync as execFileSync2 } from "node:child_process";
4089
- import { existsSync as existsSync6 } from "node:fs";
4090
- import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
4091
+ import { existsSync as existsSync7 } from "node:fs";
4092
+ import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
4091
4093
  import { execFileSync as execFileSync3 } from "node:child_process";
4092
- import { mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
4093
- import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5, join as join9, resolve as resolve8 } from "node:path";
4094
+ import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
4095
+ import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute6, join as join10, resolve as resolve9 } from "node:path";
4094
4096
  import { execFileSync as execFileSync4 } from "node:child_process";
4095
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
4096
- import { basename as basename4, join as join11, resolve as resolve10, sep as sep3 } from "node:path";
4097
- import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
4098
- import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve11, sep as sep4 } from "node:path";
4099
- import { existsSync as existsSync9 } from "node:fs";
4100
- import { join as join13 } from "node:path";
4101
- import { readdirSync as readdirSync9 } from "node:fs";
4102
- import { isAbsolute as isAbsolute8, join as join14 } from "node:path";
4097
+ import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
4098
+ import { basename as basename5, join as join12, resolve as resolve11, sep as sep3 } from "node:path";
4099
+ import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync10 } from "node:fs";
4100
+ import { basename as basename6, isAbsolute as isAbsolute8, join as join13, relative as relative4, resolve as resolve12, sep as sep4 } from "node:path";
4101
+ import { existsSync as existsSync10 } from "node:fs";
4102
+ import { join as join14 } from "node:path";
4103
+ import { readdirSync as readdirSync10 } from "node:fs";
4104
+ import { isAbsolute as isAbsolute9, join as join15 } from "node:path";
4103
4105
  var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
4104
4106
  function readJson(filePath) {
4105
4107
  if (!existsSync(filePath))
@@ -4620,6 +4622,160 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
4620
4622
  } catch {}
4621
4623
  }
4622
4624
  }
4625
+ var PLAN_SHAPED_KEY_RE = /^[0-9]{8}-[a-z0-9-]+$/;
4626
+ function resolveArtifactPath(harnessRoot, ref) {
4627
+ const { kind, key } = ref;
4628
+ if (kind === "json") {
4629
+ if (!isAbsolute3(key)) {
4630
+ throw new Error(`ArtifactStore json key must be an absolute path \u2014 got ${JSON.stringify(key)}`);
4631
+ }
4632
+ if (key.split(/[\\/]+/).includes("..")) {
4633
+ throw new Error(`ArtifactStore json key must not contain ".." segments \u2014 got ${JSON.stringify(key)}`);
4634
+ }
4635
+ return key;
4636
+ }
4637
+ assertSafePathComponent(key, "ArtifactStore key");
4638
+ if (kind === "status") {
4639
+ if (key !== "root") {
4640
+ throw new Error(`ArtifactStore status key must be "root" \u2014 got ${JSON.stringify(key)}`);
4641
+ }
4642
+ return join5(harnessRoot, "status.json");
4643
+ }
4644
+ if (kind === "snapshot") {
4645
+ return join5(resolveWorkflowDir(harnessRoot, { harnessDir: harnessRoot }), key, "snapshot.json");
4646
+ }
4647
+ if (kind === "residuals") {
4648
+ return join5(resolveProjectDir(harnessRoot, { harnessDir: harnessRoot }), key, "residuals.json");
4649
+ }
4650
+ if (PLAN_SHAPED_KEY_RE.test(key)) {
4651
+ return join5(harnessRoot, "sdd", key, "review", "report.json");
4652
+ }
4653
+ return join5(harnessRoot, "sdd", "_reviews", `${key}.json`);
4654
+ }
4655
+ function listDirNames(dir) {
4656
+ return readDirEntries(dir).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
4657
+ }
4658
+ function listJsonKeys(dir) {
4659
+ return readDirEntries(dir).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length));
4660
+ }
4661
+ function readDirEntries(dir) {
4662
+ try {
4663
+ return readdirSync2(dir, { withFileTypes: true });
4664
+ } catch (error) {
4665
+ const code = error.code;
4666
+ if (code === "ENOENT" || code === "ENOTDIR")
4667
+ return [];
4668
+ throw error;
4669
+ }
4670
+ }
4671
+ function tryResolveGetPath(root, kind, key) {
4672
+ try {
4673
+ return resolveArtifactPath(root, { kind, key });
4674
+ } catch {
4675
+ return;
4676
+ }
4677
+ }
4678
+ function createFsStore(harnessRoot) {
4679
+ const root = resolve4(harnessRoot);
4680
+ return {
4681
+ root,
4682
+ async put(doc) {
4683
+ if (doc.schema !== undefined) {
4684
+ throw new Error("FsStore does not persist schema ids \u2014 omit --schema or inject a store module that persists it");
4685
+ }
4686
+ writeJson(resolveArtifactPath(root, doc), doc.payload);
4687
+ },
4688
+ async get(ref) {
4689
+ const filePath = resolveArtifactPath(root, ref);
4690
+ if (!existsSync3(filePath))
4691
+ return;
4692
+ return readJson(filePath);
4693
+ },
4694
+ async delete(ref) {
4695
+ const filePath = resolveArtifactPath(root, ref);
4696
+ if (existsSync3(filePath))
4697
+ unlinkSync3(filePath);
4698
+ },
4699
+ async list(kind) {
4700
+ if (kind === "json") {
4701
+ throw new Error("ArtifactStore json keys are absolute paths and cannot be listed");
4702
+ }
4703
+ const keys = [];
4704
+ if (kind === "status") {
4705
+ if (existsSync3(resolveArtifactPath(root, { kind, key: "root" })))
4706
+ keys.push("root");
4707
+ } else if (kind === "snapshot" || kind === "residuals") {
4708
+ const baseDir = kind === "snapshot" ? resolveWorkflowDir(root, { harnessDir: root }) : resolveProjectDir(root, { harnessDir: root });
4709
+ for (const name of listDirNames(baseDir)) {
4710
+ const getPath = tryResolveGetPath(root, kind, name);
4711
+ if (getPath !== undefined && existsSync3(getPath))
4712
+ keys.push(name);
4713
+ }
4714
+ } else {
4715
+ const sddDir = join5(root, "sdd");
4716
+ for (const key of listJsonKeys(join5(sddDir, "_reviews"))) {
4717
+ if (!PLAN_SHAPED_KEY_RE.test(key) && tryResolveGetPath(root, kind, key) !== undefined) {
4718
+ keys.push(key);
4719
+ }
4720
+ }
4721
+ for (const name of listDirNames(sddDir)) {
4722
+ if (PLAN_SHAPED_KEY_RE.test(name)) {
4723
+ const getPath = tryResolveGetPath(root, kind, name);
4724
+ if (getPath !== undefined && existsSync3(getPath))
4725
+ keys.push(name);
4726
+ }
4727
+ }
4728
+ }
4729
+ return keys.sort().map((key) => ({ kind, key }));
4730
+ }
4731
+ };
4732
+ }
4733
+ var injectedStore;
4734
+ function setArtifactStore(store) {
4735
+ injectedStore = store;
4736
+ }
4737
+ function getArtifactStore() {
4738
+ if (injectedStore !== undefined)
4739
+ return injectedStore;
4740
+ const root = resolveHarnessDir(process.cwd());
4741
+ if (root === null) {
4742
+ throw new Error(`harness dir not found from ${resolve4(process.cwd())} \u2014 cannot create the default FsStore (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
4743
+ }
4744
+ return createFsStore(root);
4745
+ }
4746
+ function assertFsStorePath(store, ref, expectedPath) {
4747
+ const root = store.root;
4748
+ if (typeof root !== "string")
4749
+ return;
4750
+ const storePath = resolveArtifactPath(root, ref);
4751
+ const expected = resolve4(expectedPath);
4752
+ if (storePath !== expected) {
4753
+ throw new Error(`routed writer path mismatch: the active FsStore resolves ${ref.kind}/${JSON.stringify(ref.key)} to ${JSON.stringify(storePath)} but the caller's target is ${JSON.stringify(expected)} \u2014 call setArtifactStore(createFsStore(<root>)) first when the write target differs from the active store's root`);
4754
+ }
4755
+ }
4756
+ var URI_SCHEME_RE = /^[A-Za-z][A-Za-z0-9+.-]*:/;
4757
+ function isArtifactStore(value) {
4758
+ return typeof value === "object" && value !== null && typeof value.put === "function" && typeof value.get === "function";
4759
+ }
4760
+ async function loadStoreModule(modulePath) {
4761
+ if (modulePath === "") {
4762
+ throw new Error("loadStoreModule: module path must not be empty");
4763
+ }
4764
+ if (URI_SCHEME_RE.test(modulePath)) {
4765
+ throw new Error(`loadStoreModule: only filesystem paths are allowed \u2014 got ${JSON.stringify(modulePath)} (URI schemes such as http:/https:/file: are rejected)`);
4766
+ }
4767
+ const resolved = resolve4(modulePath);
4768
+ if (!existsSync3(resolved)) {
4769
+ throw new Error(`loadStoreModule: module file not found \u2014 ${JSON.stringify(resolved)}`);
4770
+ }
4771
+ const mod = await import(resolved);
4772
+ const candidate = mod.createArtifactStore ?? mod.default ?? mod;
4773
+ const store = typeof candidate === "function" ? await candidate() : candidate;
4774
+ if (!isArtifactStore(store)) {
4775
+ throw new Error(`loadStoreModule: module ${JSON.stringify(resolved)} does not export an ArtifactStore \u2014 expected a createArtifactStore named export, a default factory, or a default object with put() and get() functions`);
4776
+ }
4777
+ return store;
4778
+ }
4623
4779
  var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> \u2014 <reason>"';
4624
4780
  var REQUIRED_FIELDS = [
4625
4781
  { key: "executeAs", label: "Execute as", code: "execute-as" },
@@ -5052,7 +5208,7 @@ function validateStatusV2(docOrPath, opts = {}) {
5052
5208
  if (typeof docOrPath === "string") {
5053
5209
  try {
5054
5210
  doc = readJson(docOrPath);
5055
- harnessDir = dirname4(resolve4(docOrPath));
5211
+ harnessDir = dirname4(resolve5(docOrPath));
5056
5212
  } catch (error) {
5057
5213
  return {
5058
5214
  ok: false,
@@ -5119,8 +5275,8 @@ function validateStatusV2(docOrPath, opts = {}) {
5119
5275
  for (const entry of doc.workflows) {
5120
5276
  if (!isPlainObject4(entry) || typeof entry.dir !== "string")
5121
5277
  continue;
5122
- const relSnapshot = join6(entry.dir, WORKFLOW_SNAPSHOT_FILE);
5123
- const snapshotPath = join6(harnessDir, relSnapshot);
5278
+ const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
5279
+ const snapshotPath = join7(harnessDir, relSnapshot);
5124
5280
  const label = typeof entry.id === "string" ? entry.id : relSnapshot;
5125
5281
  let physical;
5126
5282
  try {
@@ -5154,8 +5310,10 @@ function validateStatusV2(docOrPath, opts = {}) {
5154
5310
  return { ok: violations.length === 0, violations };
5155
5311
  }
5156
5312
  var validateStatus = validateStatusV2;
5157
- function registerWorkflowEntryLocked(statusPath, entry) {
5313
+ async function registerWorkflowEntryLocked(statusPath, entry) {
5158
5314
  const harnessDir = dirname4(statusPath);
5315
+ const store = getArtifactStore();
5316
+ assertFsStorePath(store, { kind: "status", key: "root" }, statusPath);
5159
5317
  const current = readJson(statusPath);
5160
5318
  const fresh = Object.keys(current).length === 0;
5161
5319
  const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
@@ -5173,7 +5331,7 @@ function registerWorkflowEntryLocked(statusPath, entry) {
5173
5331
  if (!gate.ok) {
5174
5332
  throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
5175
5333
  }
5176
- writeJson(statusPath, doc);
5334
+ await store.put({ kind: "status", key: "root", payload: doc });
5177
5335
  return doc;
5178
5336
  }
5179
5337
  var PROJECT_ROADMAP_FILE = "roadmap.md";
@@ -5246,9 +5404,12 @@ async function appendProjectRegisterEntries(opts) {
5246
5404
  if (opts.entries.length === 0) {
5247
5405
  throw new Error("refusing to append residual entries: entries must not be empty");
5248
5406
  }
5249
- const registerPath = resolve5(join7(opts.projectDir, PROJECT_REGISTER_FILE));
5407
+ const registerPath = resolve6(join8(opts.projectDir, PROJECT_REGISTER_FILE));
5408
+ const projectKey = basename2(resolve6(opts.projectDir));
5409
+ const store = getArtifactStore();
5410
+ assertFsStorePath(store, { kind: "residuals", key: projectKey }, registerPath);
5250
5411
  mkdirSync4(opts.projectDir, { recursive: true });
5251
- return withStatusWriteLock(registerPath, () => {
5412
+ return withStatusWriteLock(registerPath, async () => {
5252
5413
  const doc = readJson(registerPath);
5253
5414
  const entriesMap = doc.entries ?? {};
5254
5415
  let key = opts.basePlanKey;
@@ -5283,14 +5444,17 @@ async function appendProjectRegisterEntries(opts) {
5283
5444
  if (!gate.ok) {
5284
5445
  throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
5285
5446
  }
5286
- writeJson(registerPath, register);
5447
+ await store.put({ kind: "residuals", key: projectKey, payload: register });
5287
5448
  return { ok: true, key };
5288
5449
  });
5289
5450
  }
5290
5451
  async function closeProjectRegisterEntry(opts) {
5291
- const registerPath = resolve5(join7(opts.projectDir, PROJECT_REGISTER_FILE));
5452
+ const registerPath = resolve6(join8(opts.projectDir, PROJECT_REGISTER_FILE));
5453
+ const projectKey = basename2(resolve6(opts.projectDir));
5454
+ const store = getArtifactStore();
5455
+ assertFsStorePath(store, { kind: "residuals", key: projectKey }, registerPath);
5292
5456
  mkdirSync4(opts.projectDir, { recursive: true });
5293
- return withStatusWriteLock(registerPath, () => {
5457
+ return withStatusWriteLock(registerPath, async () => {
5294
5458
  const doc = readJson(registerPath);
5295
5459
  const planEntries = doc.entries?.[opts.planKey];
5296
5460
  if (!Array.isArray(planEntries)) {
@@ -5310,7 +5474,7 @@ async function closeProjectRegisterEntry(opts) {
5310
5474
  if (!gate.ok) {
5311
5475
  throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
5312
5476
  }
5313
- writeJson(registerPath, register);
5477
+ await store.put({ kind: "residuals", key: projectKey, payload: register });
5314
5478
  return { ok: true };
5315
5479
  });
5316
5480
  }
@@ -5363,15 +5527,15 @@ function techDebtRollup(projectDir) {
5363
5527
  const items = [];
5364
5528
  let entries;
5365
5529
  try {
5366
- entries = readdirSync3(projectDir, { withFileTypes: true });
5530
+ entries = readdirSync4(projectDir, { withFileTypes: true });
5367
5531
  } catch {
5368
5532
  entries = [];
5369
5533
  }
5370
5534
  for (const project of entries) {
5371
5535
  if (!project.isDirectory())
5372
5536
  continue;
5373
- const registerPath = join7(projectDir, project.name, PROJECT_REGISTER_FILE);
5374
- if (!existsSync4(registerPath))
5537
+ const registerPath = join8(projectDir, project.name, PROJECT_REGISTER_FILE);
5538
+ if (!existsSync5(registerPath))
5375
5539
  continue;
5376
5540
  let register;
5377
5541
  try {
@@ -5407,19 +5571,19 @@ function techDebtRollup(projectDir) {
5407
5571
  return { computed, stored, checks, overall };
5408
5572
  }
5409
5573
  function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
5410
- const start = resolve6(startDir);
5574
+ const start = resolve7(startDir);
5411
5575
  const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
5412
5576
  if (explicit)
5413
- return resolve6(start, explicit);
5414
- const boundary = resolve6(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
5577
+ return resolve7(start, explicit);
5578
+ const boundary = resolve7(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
5415
5579
  const rc = loadMstarc(start, boundary);
5416
5580
  if (rc !== null && rc.config.harnessDir)
5417
- return resolve6(rc.dir, rc.config.harnessDir);
5581
+ return resolve7(rc.dir, rc.config.harnessDir);
5418
5582
  let dir = start;
5419
5583
  for (;; ) {
5420
5584
  if (!isAtOrBelow2(dir, boundary))
5421
5585
  return null;
5422
- for (const candidate of [join8(dir, ".mstar"), join8(dir, ".agents"), join8(dir, ".plans"), join8(dir, "plans")]) {
5586
+ for (const candidate of [join9(dir, ".mstar"), join9(dir, ".agents"), join9(dir, ".plans"), join9(dir, "plans")]) {
5423
5587
  if (isDirectory(candidate))
5424
5588
  return candidate;
5425
5589
  }
@@ -5445,19 +5609,19 @@ function defaultWorkspaceRoot(startDir) {
5445
5609
  if (segment && segment !== ".")
5446
5610
  boundary = dirname5(boundary);
5447
5611
  }
5448
- return resolve6(boundary);
5612
+ return resolve7(boundary);
5449
5613
  } catch {}
5450
5614
  return startDir;
5451
5615
  }
5452
5616
  function isAtOrBelow2(dir, root) {
5453
5617
  const rel = relative2(root, dir);
5454
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
5618
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
5455
5619
  }
5456
5620
  function mstarcDirOverride(harnessDir, key) {
5457
- const dir = resolve6(harnessDir);
5621
+ const dir = resolve7(harnessDir);
5458
5622
  const rc = loadMstarc(dir, dirname5(dir));
5459
5623
  const declared = rc?.config[key];
5460
- return declared ? resolve6(rc.dir, declared) : null;
5624
+ return declared ? resolve7(rc.dir, declared) : null;
5461
5625
  }
5462
5626
  function resolveSpecsDir(harnessDir, opts = {}) {
5463
5627
  const declared = mstarcDirOverride(harnessDir, "specsDir");
@@ -5466,20 +5630,20 @@ function resolveSpecsDir(harnessDir, opts = {}) {
5466
5630
  mkdirSync5(declared, { recursive: true });
5467
5631
  return declared;
5468
5632
  }
5469
- const harness = resolve6(harnessDir);
5633
+ const harness = resolve7(harnessDir);
5470
5634
  const repoRoot = dirname5(harness);
5471
5635
  const candidates = [
5472
- join8(harness, "specs"),
5473
- join8(repoRoot, "docs", "specs"),
5474
- join8(repoRoot, "specs"),
5475
- join8(harness, "designs"),
5476
- join8(repoRoot, "designs")
5636
+ join9(harness, "specs"),
5637
+ join9(repoRoot, "docs", "specs"),
5638
+ join9(repoRoot, "specs"),
5639
+ join9(harness, "designs"),
5640
+ join9(repoRoot, "designs")
5477
5641
  ];
5478
5642
  for (const candidate of candidates) {
5479
5643
  if (isDirectory(candidate) && hasFiles(candidate))
5480
5644
  return candidate;
5481
5645
  }
5482
- const fallback = join8(harness, "specs");
5646
+ const fallback = join9(harness, "specs");
5483
5647
  if (opts.create !== false)
5484
5648
  mkdirSync5(fallback, { recursive: true });
5485
5649
  return fallback;
@@ -5491,18 +5655,18 @@ function assertSafePathComponent(value, what) {
5491
5655
  }
5492
5656
  function resolveSddDir(harnessDir, planId) {
5493
5657
  assertSafePathComponent(planId, "planId");
5494
- const base = resolve6(harnessDir);
5658
+ const base = resolve7(harnessDir);
5495
5659
  const declared = mstarcDirOverride(base, "sddDir");
5496
- const sddBase = declared !== null ? declared : join8(base, "sdd");
5497
- return join8(sddBase, planId);
5660
+ const sddBase = declared !== null ? declared : join9(base, "sdd");
5661
+ return join9(sddBase, planId);
5498
5662
  }
5499
5663
  function resolveHarnessSubdir(startDir, opts, key, fallback) {
5500
5664
  const harness = resolveHarnessDir(startDir, opts);
5501
5665
  if (harness === null) {
5502
- throw new Error(`harness dir not found from ${resolve6(startDir)} \u2014 cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
5666
+ throw new Error(`harness dir not found from ${resolve7(startDir)} \u2014 cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
5503
5667
  }
5504
5668
  const declared = mstarcDirOverride(harness, key);
5505
- return declared !== null ? declared : join8(resolve6(harness), fallback);
5669
+ return declared !== null ? declared : join9(resolve7(harness), fallback);
5506
5670
  }
5507
5671
  function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
5508
5672
  return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
@@ -5517,13 +5681,13 @@ var EMPTY_STATUS_TEMPLATE = {
5517
5681
  };
5518
5682
  var SCAFFOLD_DIRS = ["plans", "iterations", "knowledge", "specs", "sdd"];
5519
5683
  function resolveScaffoldDirs(root) {
5520
- const start = resolve6(root);
5521
- const boundary = resolve6(start, defaultWorkspaceRoot(start));
5684
+ const start = resolve7(root);
5685
+ const boundary = resolve7(start, defaultWorkspaceRoot(start));
5522
5686
  const rc = loadMstarc(start, boundary);
5523
5687
  const explicit = process.env.MSTAR_HARNESS_DIR;
5524
- const harnessDir = explicit ? resolve6(start, explicit) : rc !== null && rc.config.harnessDir ? resolve6(rc.dir, rc.config.harnessDir) : join8(start, ".mstar");
5688
+ const harnessDir = explicit ? resolve7(start, explicit) : rc !== null && rc.config.harnessDir ? resolve7(rc.dir, rc.config.harnessDir) : join9(start, ".mstar");
5525
5689
  const declaredProjectDir = mstarcDirOverride(harnessDir, "projectDir");
5526
- const projectDir = declaredProjectDir !== null ? declaredProjectDir : join8(harnessDir, "projects");
5690
+ const projectDir = declaredProjectDir !== null ? declaredProjectDir : join9(harnessDir, "projects");
5527
5691
  return { harnessDir, projectDir };
5528
5692
  }
5529
5693
  var ROADMAP_TEMPLATE = `---
@@ -5545,18 +5709,18 @@ var EMPTY_REGISTER_TEMPLATE = {
5545
5709
  function scaffoldHarness(root) {
5546
5710
  const { harnessDir, projectDir } = resolveScaffoldDirs(root);
5547
5711
  for (const dir of SCAFFOLD_DIRS)
5548
- mkdirSync5(join8(harnessDir, dir), { recursive: true });
5549
- const statusPath = join8(harnessDir, "status.json");
5712
+ mkdirSync5(join9(harnessDir, dir), { recursive: true });
5713
+ const statusPath = join9(harnessDir, "status.json");
5550
5714
  if (Object.keys(readJson(statusPath)).length === 0)
5551
5715
  writeJson(statusPath, EMPTY_STATUS_TEMPLATE);
5552
- const defaultProjectDir = join8(projectDir, _DEFAULT_PROJECT);
5716
+ const defaultProjectDir = join9(projectDir, _DEFAULT_PROJECT);
5553
5717
  mkdirSync5(defaultProjectDir, { recursive: true });
5554
- const roadmapPath = join8(defaultProjectDir, PROJECT_ROADMAP_FILE);
5555
- if (!existsSync5(roadmapPath)) {
5718
+ const roadmapPath = join9(defaultProjectDir, PROJECT_ROADMAP_FILE);
5719
+ if (!existsSync6(roadmapPath)) {
5556
5720
  const created = new Date().toISOString().slice(0, 10);
5557
5721
  writeFileSync3(roadmapPath, ROADMAP_TEMPLATE.replace("{created_at}", created), "utf8");
5558
5722
  }
5559
- const registerPath = join8(defaultProjectDir, PROJECT_REGISTER_FILE);
5723
+ const registerPath = join9(defaultProjectDir, PROJECT_REGISTER_FILE);
5560
5724
  if (Object.keys(readJson(registerPath)).length === 0)
5561
5725
  writeJson(registerPath, EMPTY_REGISTER_TEMPLATE);
5562
5726
  return harnessDir;
@@ -5596,7 +5760,7 @@ function emitGitignoreSnippet(kind) {
5596
5760
  function detectHarnessKind(harnessDir) {
5597
5761
  if (!harnessDir)
5598
5762
  return null;
5599
- const name = basename2(resolve6(harnessDir));
5763
+ const name = basename3(resolve7(harnessDir));
5600
5764
  if (name === ".mstar")
5601
5765
  return "mstar";
5602
5766
  if (name === ".agents")
@@ -5612,9 +5776,9 @@ function isDirectory(dir) {
5612
5776
  }
5613
5777
  function hasFiles(dir) {
5614
5778
  try {
5615
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
5779
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
5616
5780
  if (entry.isDirectory()) {
5617
- if (hasFiles(join8(dir, entry.name)))
5781
+ if (hasFiles(join9(dir, entry.name)))
5618
5782
  return true;
5619
5783
  } else if (entry.isFile()) {
5620
5784
  return true;
@@ -5675,10 +5839,10 @@ function l1PreDispatchCheck(input, opts = {}) {
5675
5839
  if (leaseWorkingBranch.trim() === "") {
5676
5840
  violations.push(violation7("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
5677
5841
  }
5678
- if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve7(controlWorktreePath) === resolve7(leaseWorktreePath)) {
5842
+ if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
5679
5843
  violations.push(violation7("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path \u2014 the feature worktree MUST differ from the control worktree (L1 isolation; product edits never land in the control checkout)`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
5680
5844
  }
5681
- if (leaseWorktreePath !== "" && !existsSync6(leaseWorktreePath)) {
5845
+ if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
5682
5846
  violations.push(violation7("high", "worktree.l1.feature-missing", `feature worktree directory "${leaseWorktreePath}" does not exist for plan "${planId}"`, `create it before dispatch: git worktree add ${leaseWorktreePath} <working-branch>`));
5683
5847
  } else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
5684
5848
  const probe = probeBranch(leaseWorktreePath, opts);
@@ -5702,17 +5866,17 @@ function l2PreDispatchCheck(input, opts = {}) {
5702
5866
  violations.push(violation7("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
5703
5867
  return;
5704
5868
  }
5705
- if (!isAbsolute4(track.worktreePath)) {
5869
+ if (!isAbsolute5(track.worktreePath)) {
5706
5870
  violations.push(violation7("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path \u2014 L2 tracks MUST use absolute worktree checkout paths (consistent with the lease validator's absolute worktree_path enforcement)`, `use an absolute path for track ${index + 1} (e.g. /Users/<you>/worktrees/<branch>)`));
5707
5871
  return;
5708
5872
  }
5709
- const normalized = resolve7(track.worktreePath);
5873
+ const normalized = resolve8(track.worktreePath);
5710
5874
  if (seenPaths.has(normalized)) {
5711
5875
  violations.push(violation7("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks \u2014 L2 parallel-writable isolation requires a distinct absolute Worktree path per track (N parallel invokes \u2260 isolation)`, "give every parallel track its own git worktree checkout"));
5712
5876
  return;
5713
5877
  }
5714
5878
  seenPaths.add(normalized);
5715
- if (!existsSync6(track.worktreePath)) {
5879
+ if (!existsSync7(track.worktreePath)) {
5716
5880
  violations.push(violation7("high", "worktree.l2.track-missing", `track worktree directory "${track.worktreePath}" does not exist`, `create it before dispatch: git worktree add ${track.worktreePath} ${track.workingBranch}`));
5717
5881
  return;
5718
5882
  }
@@ -5763,7 +5927,7 @@ function isFile2(file) {
5763
5927
  return false;
5764
5928
  }
5765
5929
  }
5766
- var GIT_CAPTURE_MAX_BYTES = 64 * 1024 * 1024;
5930
+ var GIT_CAPTURE_MAX_BYTES = 67108864;
5767
5931
  function gitOut(cwd, args) {
5768
5932
  try {
5769
5933
  return execFileSync3("git", args, {
@@ -5777,14 +5941,14 @@ function gitOut(cwd, args) {
5777
5941
  }
5778
5942
  }
5779
5943
  function probeHarnessWithStatus(root) {
5780
- if (isFile2(join9(root, ".mstar", "status.json")))
5781
- return join9(root, ".mstar");
5782
- if (isFile2(join9(root, ".agents", "status.json")))
5783
- return join9(root, ".agents");
5784
- if (hasWorkflowSnapshot(join9(root, ".mstar")))
5785
- return join9(root, ".mstar");
5786
- if (hasWorkflowSnapshot(join9(root, ".agents")))
5787
- return join9(root, ".agents");
5944
+ if (isFile2(join10(root, ".mstar", "status.json")))
5945
+ return join10(root, ".mstar");
5946
+ if (isFile2(join10(root, ".agents", "status.json")))
5947
+ return join10(root, ".agents");
5948
+ if (hasWorkflowSnapshot(join10(root, ".mstar")))
5949
+ return join10(root, ".mstar");
5950
+ if (hasWorkflowSnapshot(join10(root, ".agents")))
5951
+ return join10(root, ".agents");
5788
5952
  return null;
5789
5953
  }
5790
5954
  function hasWorkflowSnapshot(harnessDir) {
@@ -5792,13 +5956,13 @@ function hasWorkflowSnapshot(harnessDir) {
5792
5956
  try {
5793
5957
  workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
5794
5958
  } catch {
5795
- workflowsDir = join9(harnessDir, "workflows");
5959
+ workflowsDir = join10(harnessDir, "workflows");
5796
5960
  }
5797
5961
  if (!isDirectory2(workflowsDir))
5798
5962
  return false;
5799
5963
  try {
5800
- for (const entry of readdirSync5(workflowsDir, { withFileTypes: true })) {
5801
- if (entry.isDirectory() && isFile2(join9(workflowsDir, entry.name, "snapshot.json")))
5964
+ for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
5965
+ if (entry.isDirectory() && isFile2(join10(workflowsDir, entry.name, "snapshot.json")))
5802
5966
  return true;
5803
5967
  }
5804
5968
  } catch {
@@ -5811,14 +5975,14 @@ function isLinkedWorktree(root) {
5811
5975
  const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
5812
5976
  if (gitDirRaw === null || commonRaw === null)
5813
5977
  return false;
5814
- const gitDir = isAbsolute5(gitDirRaw) ? gitDirRaw : join9(root, gitDirRaw);
5815
- const common2 = isAbsolute5(commonRaw) ? commonRaw : join9(root, commonRaw);
5978
+ const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join10(root, gitDirRaw);
5979
+ const common2 = isAbsolute6(commonRaw) ? commonRaw : join10(root, commonRaw);
5816
5980
  if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
5817
5981
  return true;
5818
5982
  try {
5819
5983
  const gdParent = realpathSync3(dirname6(gitDir));
5820
5984
  const cmAbs = realpathSync3(common2);
5821
- return join9(gdParent, basename3(gitDir)) !== cmAbs && gitDir !== cmAbs;
5985
+ return join10(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
5822
5986
  } catch {
5823
5987
  return false;
5824
5988
  }
@@ -5826,7 +5990,7 @@ function isLinkedWorktree(root) {
5826
5990
  function sddWorkspace(planId, opts = {}) {
5827
5991
  if (!planId) {
5828
5992
  throw new SddScriptError(`usage: mstar sdd workspace PLAN_ID [CONTROL_ROOT]
5829
- ` + " Set MSTAR_CONTROL_ROOT=<control_worktree_path> when running from a feature worktree.", 2);
5993
+ Set MSTAR_CONTROL_ROOT=<control_worktree_path> when running from a feature worktree.`, 2);
5830
5994
  }
5831
5995
  const cwd = opts.cwd ?? process.cwd();
5832
5996
  const controlRoot = opts.controlRoot ?? (process.env.MSTAR_CONTROL_ROOT || undefined);
@@ -5842,35 +6006,35 @@ function sddWorkspace(planId, opts = {}) {
5842
6006
  }
5843
6007
  if (!controlRoot && isLinkedWorktree(root)) {
5844
6008
  throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
5845
- ` + ` Refusing to create a second SDD tree under the feature checkout.
5846
- ` + ` Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
6009
+ Refusing to create a second SDD tree under the feature checkout.
6010
+ Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
5847
6011
  ` + ` See mstar-branch-worktree \u00abHarness path SSOT under default gitignore\u00bb.`, 1);
5848
6012
  }
5849
6013
  const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
5850
6014
  let harnessDir;
5851
6015
  if (harnessOverride) {
5852
- harnessDir = resolve8(root, harnessOverride);
6016
+ harnessDir = resolve9(root, harnessOverride);
5853
6017
  } else {
5854
6018
  const rc = findMstarc(root, root);
5855
6019
  const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
5856
6020
  if (rcHarnessDir) {
5857
- harnessDir = resolve8(rc !== null ? dirname6(rc) : root, rcHarnessDir);
6021
+ harnessDir = resolve9(rc !== null ? dirname6(rc) : root, rcHarnessDir);
5858
6022
  } else {
5859
6023
  const probed = probeHarnessWithStatus(root);
5860
6024
  if (probed) {
5861
6025
  harnessDir = probed;
5862
- } else if (isDirectory2(join9(root, ".mstar"))) {
5863
- harnessDir = join9(root, ".mstar");
5864
- } else if (isDirectory2(join9(root, ".agents"))) {
5865
- harnessDir = join9(root, ".agents");
6026
+ } else if (isDirectory2(join10(root, ".mstar"))) {
6027
+ harnessDir = join10(root, ".mstar");
6028
+ } else if (isDirectory2(join10(root, ".agents"))) {
6029
+ harnessDir = join10(root, ".agents");
5866
6030
  } else {
5867
- harnessDir = join9(root, ".mstar");
6031
+ harnessDir = join10(root, ".mstar");
5868
6032
  }
5869
6033
  }
5870
6034
  }
5871
6035
  const sddDir = resolveSddDir(harnessDir, planId);
5872
6036
  mkdirSync6(sddDir, { recursive: true });
5873
- writeFileSync4(join9(sddDir, ".gitignore"), `*
6037
+ writeFileSync4(join10(sddDir, ".gitignore"), `*
5874
6038
  `);
5875
6039
  return realpathSync3(sddDir);
5876
6040
  }
@@ -5893,7 +6057,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
5893
6057
  throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
5894
6058
  }
5895
6059
  mkdirSync6(sddDir, { recursive: true });
5896
- out = join9(sddDir, `task-${taskN}-brief.md`);
6060
+ out = join10(sddDir, `task-${taskN}-brief.md`);
5897
6061
  }
5898
6062
  const records = content.endsWith(`
5899
6063
  `) ? content.split(`
@@ -5946,7 +6110,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
5946
6110
  mkdirSync6(sddDir, { recursive: true });
5947
6111
  const shortBase = gitOut(cwd, ["rev-parse", "--short", base]) ?? base;
5948
6112
  const shortHead = gitOut(cwd, ["rev-parse", "--short", head]) ?? head;
5949
- out = join9(sddDir, `review-${shortBase}..${shortHead}.diff`);
6113
+ out = join10(sddDir, `review-${shortBase}..${shortHead}.diff`);
5950
6114
  }
5951
6115
  const run = (args) => execFileSync3("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
5952
6116
  const parts = [
@@ -6634,7 +6798,7 @@ function scanSecrets(files) {
6634
6798
  unreadableFiles++;
6635
6799
  continue;
6636
6800
  }
6637
- const base = basename4(file);
6801
+ const base = basename5(file);
6638
6802
  for (const entry of NEVER_COMMIT_FILENAMES) {
6639
6803
  if (entry.re.test(base))
6640
6804
  findings.push({ file, line: 1, type: entry.type });
@@ -6686,12 +6850,12 @@ var LOCKFILE_NAMES = [
6686
6850
  function rootLockfiles(root) {
6687
6851
  let entries;
6688
6852
  try {
6689
- entries = readdirSync7(root, { withFileTypes: true });
6853
+ entries = readdirSync8(root, { withFileTypes: true });
6690
6854
  } catch {
6691
6855
  return [];
6692
6856
  }
6693
6857
  const names = new Set(LOCKFILE_NAMES);
6694
- const present = entries.filter((entry) => entry.isFile() && names.has(entry.name)).map((entry) => join11(root, entry.name));
6858
+ const present = entries.filter((entry) => entry.isFile() && names.has(entry.name)).map((entry) => join12(root, entry.name));
6695
6859
  if (present.length === 0)
6696
6860
  return [];
6697
6861
  try {
@@ -6700,7 +6864,7 @@ function rootLockfiles(root) {
6700
6864
  encoding: "utf8",
6701
6865
  stdio: ["ignore", "pipe", "ignore"]
6702
6866
  }).split("\x00").filter((f) => f !== ""));
6703
- return present.filter((p) => tracked.has(basename4(p)));
6867
+ return present.filter((p) => tracked.has(basename5(p)));
6704
6868
  } catch {
6705
6869
  return present;
6706
6870
  }
@@ -6716,17 +6880,17 @@ function supplyChainChecks(repoRoot) {
6716
6880
  findings.push({ kind: "lockfile-duplicate", file: lockfiles.map((f) => f.replace(`${repoRoot}/`, "")).join(", ") });
6717
6881
  violations.push(violation9("medium", "audit.supply.lockfile-duplicate", `multiple lockfiles at ${repoRoot}: ${lockfiles.join(", ")}`, "keep exactly one lockfile per package manager"));
6718
6882
  }
6719
- const workflowsDir = join11(repoRoot, ".github", "workflows");
6883
+ const workflowsDir = join12(repoRoot, ".github", "workflows");
6720
6884
  let wfEntries = [];
6721
6885
  try {
6722
- wfEntries = readdirSync7(workflowsDir, { withFileTypes: true });
6886
+ wfEntries = readdirSync8(workflowsDir, { withFileTypes: true });
6723
6887
  } catch {
6724
6888
  wfEntries = [];
6725
6889
  }
6726
6890
  for (const entry of wfEntries) {
6727
6891
  if (!entry.isFile() || !/\.(?:ya?ml)$/.test(entry.name))
6728
6892
  continue;
6729
- const wfPath = join11(workflowsDir, entry.name);
6893
+ const wfPath = join12(workflowsDir, entry.name);
6730
6894
  const relPath = `.github/workflows/${entry.name}`;
6731
6895
  let text;
6732
6896
  try {
@@ -6883,9 +7047,9 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
6883
7047
  const date = options.date ?? new Date().toISOString().slice(0, 10);
6884
7048
  const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
6885
7049
  mkdirSync8(outDir, { recursive: true });
6886
- const existingReadme = join11(outDir, "README.md");
6887
- const carried = existsSync7(existingReadme) ? extractSecurityDispositionSections(readFileSync9(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
6888
- const existing = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f));
7050
+ const existingReadme = join12(outDir, "README.md");
7051
+ const carried = existsSync8(existingReadme) ? extractSecurityDispositionSections(readFileSync9(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
7052
+ const existing = readdirSync8(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f));
6889
7053
  let next = existing.reduce((max, f) => Math.max(max, Number(f.slice(0, 3))), 0) + 1;
6890
7054
  const redactedFindings = findings.map(redactFinding);
6891
7055
  const written = [];
@@ -6901,13 +7065,13 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
6901
7065
  }
6902
7066
  usedSlugs.add(slug);
6903
7067
  const file = `${num}-${slug}.md`;
6904
- writeFileSync6(join11(outDir, file), renderPlanFile(finding, plannedAt));
7068
+ writeFileSync6(join12(outDir, file), renderPlanFile(finding, plannedAt));
6905
7069
  written.push(file);
6906
7070
  next++;
6907
7071
  }
6908
7072
  const all = [...existing, ...written].sort();
6909
7073
  const rows = all.map((file) => {
6910
- const summary = readPlanFileSummary(join11(outDir, file));
7074
+ const summary = readPlanFileSummary(join12(outDir, file));
6911
7075
  const fields = summary.fields;
6912
7076
  return {
6913
7077
  num: file.slice(0, 3),
@@ -6941,7 +7105,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
6941
7105
  });
6942
7106
  const needsVerificationLines = options.needsVerification !== undefined ? options.needsVerification.map((nv) => `- ${escapeCell(redactText(nv.lead))}: ${escapeCell(redactText(nv.how))}${nv.evidence ? ` (${escapeCell(redactText(nv.evidence))})` : ""}`) : carried.needsVerification;
6943
7107
  const hardeningCheckedLines = options.hardeningChecked !== undefined ? options.hardeningChecked.map((hc) => `- ${hc.kind}: ${escapeCell(redactText(hc.text))}`) : carried.hardeningChecked;
6944
- writeFileSync6(join11(outDir, "README.md"), renderIndex({
7108
+ writeFileSync6(join12(outDir, "README.md"), renderIndex({
6945
7109
  date,
6946
7110
  repoName: options.repoName ?? "repo",
6947
7111
  repoShortSha: options.repoShortSha ?? "unknown",
@@ -6950,7 +7114,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
6950
7114
  needsVerification: needsVerificationLines,
6951
7115
  hardeningChecked: hardeningCheckedLines
6952
7116
  }));
6953
- return { outDir: resolve10(outDir), date, files: written, nextNumber: next };
7117
+ return { outDir: resolve11(outDir), date, files: written, nextNumber: next };
6954
7118
  }
6955
7119
  async function promoteAuditPlans(outDir, selected, options) {
6956
7120
  if (selected.length === 0) {
@@ -6959,19 +7123,19 @@ async function promoteAuditPlans(outDir, selected, options) {
6959
7123
  if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
6960
7124
  throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
6961
7125
  }
6962
- const workflowId = options.workflowId ?? basename4(resolve10(outDir));
7126
+ const workflowId = options.workflowId ?? basename5(resolve11(outDir));
6963
7127
  assertSafePathComponent(workflowId, "workflow id");
6964
- const harnessDir = resolve10(options.harnessDir);
6965
- const statusPath = join11(harnessDir, "status.json");
6966
- const workflowDir = join11(harnessDir, "workflows", workflowId);
6967
- const snapshotPath = join11(workflowDir, WORKFLOW_SNAPSHOT_FILE);
7128
+ const harnessDir = resolve11(options.harnessDir);
7129
+ const statusPath = join12(harnessDir, "status.json");
7130
+ const workflowDir = join12(harnessDir, "workflows", workflowId);
7131
+ const snapshotPath = join12(workflowDir, WORKFLOW_SNAPSHOT_FILE);
6968
7132
  const planFiles = resolveSelectedPlanFiles(outDir, selected);
6969
7133
  const indexRows = readExecutionOrderIndex(outDir);
6970
7134
  const plans = planFiles.map((planFile) => {
6971
7135
  const stem = planFile.replace(/\.md$/, "");
6972
7136
  const num = stem.slice(0, 3);
6973
7137
  const indexRow = indexRows.get(num);
6974
- const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
7138
+ const title = indexRow?.title ?? readPlanFileSummary(join12(outDir, planFile)).title;
6975
7139
  return {
6976
7140
  id: stem,
6977
7141
  title,
@@ -6999,18 +7163,18 @@ async function promoteAuditPlans(outDir, selected, options) {
6999
7163
  if (!entryGate.ok) {
7000
7164
  throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
7001
7165
  }
7002
- await withStatusWriteLock(statusPath, () => {
7003
- if (existsSync7(snapshotPath)) {
7004
- 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`);
7166
+ await withStatusWriteLock(statusPath, async () => {
7167
+ if (existsSync8(snapshotPath)) {
7168
+ 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`);
7005
7169
  }
7006
7170
  mkdirSync8(workflowDir, { recursive: true });
7007
7171
  try {
7008
7172
  writeJson(snapshotPath, snapshot);
7009
- registerWorkflowEntryLocked(statusPath, entry);
7173
+ await registerWorkflowEntryLocked(statusPath, entry);
7010
7174
  } catch (error) {
7011
7175
  rmSync(snapshotPath, { force: true });
7012
7176
  try {
7013
- if (readdirSync7(workflowDir).length === 0) {
7177
+ if (readdirSync8(workflowDir).length === 0) {
7014
7178
  rmdirSync2(workflowDir);
7015
7179
  }
7016
7180
  } catch {}
@@ -7021,7 +7185,7 @@ async function promoteAuditPlans(outDir, selected, options) {
7021
7185
  return { workflowId, snapshotPath };
7022
7186
  }
7023
7187
  function resolveSelectedPlanFiles(outDir, selected) {
7024
- const files = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
7188
+ const files = readdirSync8(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
7025
7189
  const byNum = new Map;
7026
7190
  const byStem = new Map;
7027
7191
  for (const file of files) {
@@ -7036,7 +7200,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
7036
7200
  for (const id of selected) {
7037
7201
  const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
7038
7202
  if (file === undefined) {
7039
- throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve10(outDir)}`);
7203
+ throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve11(outDir)}`);
7040
7204
  }
7041
7205
  if (!seen.has(file)) {
7042
7206
  seen.add(file);
@@ -7046,7 +7210,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
7046
7210
  return resolved;
7047
7211
  }
7048
7212
  function readExecutionOrderIndex(outDir) {
7049
- const readmePath = join11(outDir, "README.md");
7213
+ const readmePath = join12(outDir, "README.md");
7050
7214
  let text;
7051
7215
  try {
7052
7216
  text = readFileSync9(readmePath, "utf8");
@@ -7075,7 +7239,7 @@ function readExecutionOrderIndex(outDir) {
7075
7239
  return rows;
7076
7240
  }
7077
7241
  function planFileRel(outDir, planFile) {
7078
- const resolved = resolve10(outDir);
7242
+ const resolved = resolve11(outDir);
7079
7243
  const parts = resolved.split(sep3);
7080
7244
  const plansIdx = parts.lastIndexOf("plans");
7081
7245
  if (plansIdx >= 0) {
@@ -7327,14 +7491,14 @@ function collectKnowledgeDocs(dir) {
7327
7491
  const current = stack.pop();
7328
7492
  let entries;
7329
7493
  try {
7330
- entries = readdirSync8(current, { withFileTypes: true });
7494
+ entries = readdirSync9(current, { withFileTypes: true });
7331
7495
  } catch {
7332
7496
  continue;
7333
7497
  }
7334
7498
  for (const entry of entries) {
7335
7499
  if (entry.isSymbolicLink())
7336
7500
  continue;
7337
- const full = join12(current, entry.name);
7501
+ const full = join13(current, entry.name);
7338
7502
  if (entry.isDirectory()) {
7339
7503
  stack.push(full);
7340
7504
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
@@ -7354,8 +7518,8 @@ function normalizeIndexRef(cell) {
7354
7518
  }
7355
7519
  function assertIndexRows(knowledgeDir) {
7356
7520
  const violations = [];
7357
- const readmePath = join12(knowledgeDir, "README.md");
7358
- if (!existsSync8(readmePath)) {
7521
+ const readmePath = join13(knowledgeDir, "README.md");
7522
+ if (!existsSync9(readmePath)) {
7359
7523
  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"));
7360
7524
  return { ok: false, violations };
7361
7525
  }
@@ -7379,12 +7543,12 @@ function assertIndexRows(knowledgeDir) {
7379
7543
  return { ok: violations.length === 0, violations };
7380
7544
  }
7381
7545
  function isFileLikeRoot(root) {
7382
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
7546
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename6(root));
7383
7547
  }
7384
7548
  function scopeGuard(path2, allowedRoots) {
7385
- const resolved = resolve11(path2);
7549
+ const resolved = resolve12(path2);
7386
7550
  for (const root of allowedRoots) {
7387
- const r = resolve11(root);
7551
+ const r = resolve12(root);
7388
7552
  if (isFileLikeRoot(r)) {
7389
7553
  if (resolved === r)
7390
7554
  return { ok: true, violations: [] };
@@ -7685,8 +7849,8 @@ function validateRoleMapping(rolesDir, options = {}) {
7685
7849
  const violations = [];
7686
7850
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
7687
7851
  for (const { agentId, reference } of mapping) {
7688
- if (!existsSync9(join13(rolesDir, reference))) {
7689
- 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`));
7852
+ if (!existsSync10(join14(rolesDir, reference))) {
7853
+ 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 ${join14(rolesDir, reference)} or fix the mapping row`));
7690
7854
  }
7691
7855
  }
7692
7856
  for (const { family, memberIds } of families) {
@@ -7912,9 +8076,135 @@ function computePrTally(input) {
7912
8076
  const scorePct = Math.max(0, 100 - 40 * mustFix - 15 * shouldFix - 3 * nit - 10 * unverified);
7913
8077
  const verdict = mustFix >= 1 ? "blocked" : shouldFix >= 1 ? "needs fixes" : "ship it";
7914
8078
  const chatHeader = `${verdict} \u00b7 ${scorePct}%
7915
- ` + `must-fix=${mustFix} should-fix=${shouldFix} nit=${nit} unverified=${unverified}`;
8079
+ must-fix=${mustFix} should-fix=${shouldFix} nit=${nit} unverified=${unverified}`;
7916
8080
  return { verdict, scorePct, tally: { mustFix, shouldFix, nit, unverified }, chatHeader };
7917
8081
  }
8082
+ var REVIEW_SCHEMA_ID = "mstar.review/v1";
8083
+ var INSPECTOR_VERDICTS = ["comment", "request_changes", "approve"];
8084
+ var INSPECTOR_SEVERITIES = ["critical", "warning", "suggestion", "info"];
8085
+ function isPlainObject7(value) {
8086
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8087
+ }
8088
+ var TALLY_COUNT_KEYS = ["mustFix", "shouldFix", "nit", "unverified"];
8089
+ function checkProvidedTallyShape(tally, violations) {
8090
+ if (typeof tally.verdict !== "string" || !PR_VERDICTS.includes(tally.verdict)) {
8091
+ violations.push(violation14("high", "review.tally-malformed", `tally.verdict "${String(tally.verdict)}" is not one of ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
8092
+ }
8093
+ if (typeof tally.scorePct !== "number" || !Number.isInteger(tally.scorePct) || tally.scorePct < 0 || tally.scorePct > 100) {
8094
+ violations.push(violation14("high", "review.tally-malformed", `tally.scorePct must be an integer in [0, 100] - got ${String(tally.scorePct)}`));
8095
+ }
8096
+ if (!isPlainObject7(tally.tally)) {
8097
+ violations.push(violation14("high", "review.tally-malformed", "tally.tally must be an object carrying the four class counts"));
8098
+ } else {
8099
+ for (const key of TALLY_COUNT_KEYS) {
8100
+ const count = tally.tally[key];
8101
+ if (typeof count !== "number" || !Number.isInteger(count) || count < 0) {
8102
+ violations.push(violation14("high", "review.tally-malformed", `tally.tally.${key} must be a non-negative integer - got ${String(count)}`));
8103
+ }
8104
+ }
8105
+ }
8106
+ if (typeof tally.chatHeader !== "string") {
8107
+ violations.push(violation14("high", "review.tally-malformed", `tally.chatHeader must be a string - got ${typeof tally.chatHeader}`));
8108
+ }
8109
+ }
8110
+ function validateMstarReviewV1(doc) {
8111
+ const violations = [];
8112
+ if (!isPlainObject7(doc)) {
8113
+ return {
8114
+ ok: false,
8115
+ violations: [violation14("high", "review.not-object", "review document must be a JSON object")]
8116
+ };
8117
+ }
8118
+ if (doc.schema === undefined) {
8119
+ violations.push(violation14("high", "review.missing-schema", "missing required field: schema"));
8120
+ } else if (doc.schema !== REVIEW_SCHEMA_ID) {
8121
+ violations.push(violation14("high", "review.invalid-schema", `schema "${String(doc.schema)}" is not "${REVIEW_SCHEMA_ID}"`));
8122
+ }
8123
+ if (doc.verdict === undefined) {
8124
+ violations.push(violation14("high", "review.missing-verdict", "missing required field: verdict"));
8125
+ } else if (typeof doc.verdict !== "string") {
8126
+ violations.push(violation14("high", "review.invalid-verdict", `verdict must be a string - got ${typeof doc.verdict}`));
8127
+ } else if (INSPECTOR_VERDICTS.includes(doc.verdict)) {
8128
+ violations.push(violation14("high", "review.inspector-vocab", `verdict "${doc.verdict}" is inspector M1 vocab - harness verdicts are ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
8129
+ } else if (!PR_VERDICTS.includes(doc.verdict)) {
8130
+ violations.push(violation14("high", "review.invalid-verdict", `verdict "${doc.verdict}" is not one of ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
8131
+ }
8132
+ if (doc.summary_md === undefined || typeof doc.summary_md !== "string" || doc.summary_md.trim() === "") {
8133
+ violations.push(violation14("high", "review.missing-summary", "summary_md must be a non-empty string"));
8134
+ }
8135
+ if (doc.findings === undefined || !Array.isArray(doc.findings)) {
8136
+ violations.push(violation14("high", "review.findings-not-array", "findings must be an array"));
8137
+ } else {
8138
+ doc.findings.forEach((finding, index) => {
8139
+ if (!isPlainObject7(finding)) {
8140
+ violations.push(violation14("high", "review.invalid-finding", `findings[${index}] must be an object`));
8141
+ return;
8142
+ }
8143
+ if (finding.severity !== undefined) {
8144
+ violations.push(violation14("high", "review.inspector-vocab", `findings[${index}] carries inspector M1 field "severity" - harness merge classes are ${JSON.stringify(MERGE_CLASSES)}`, "use mergeClass: must-fix | should-fix | nit"));
8145
+ }
8146
+ if (finding.mergeClass === undefined) {
8147
+ violations.push(violation14("high", "review.missing-merge-class", `findings[${index}] missing required field: mergeClass`));
8148
+ } else if (typeof finding.mergeClass !== "string") {
8149
+ violations.push(violation14("high", "review.invalid-merge-class", `findings[${index}].mergeClass must be a string - got ${typeof finding.mergeClass}`));
8150
+ } else if (INSPECTOR_SEVERITIES.includes(finding.mergeClass)) {
8151
+ violations.push(violation14("high", "review.inspector-vocab", `findings[${index}].mergeClass "${finding.mergeClass}" is inspector M1 severity vocab - harness merge classes are ${JSON.stringify(MERGE_CLASSES)}`, `use one of: ${MERGE_CLASSES.join(" | ")}`));
8152
+ } else if (!MERGE_CLASSES.includes(finding.mergeClass)) {
8153
+ violations.push(violation14("high", "review.invalid-merge-class", `findings[${index}].mergeClass "${finding.mergeClass}" is not one of ${JSON.stringify(MERGE_CLASSES)}`, `use one of: ${MERGE_CLASSES.join(" | ")}`));
8154
+ }
8155
+ if (finding.title === undefined || typeof finding.title !== "string" || finding.title.trim() === "") {
8156
+ violations.push(violation14("high", "review.empty-title", `findings[${index}].title must be a non-empty string`));
8157
+ }
8158
+ if (finding.body === undefined || typeof finding.body !== "string" || finding.body.trim() === "") {
8159
+ violations.push(violation14("high", "review.empty-body", `findings[${index}].body must be a non-empty string`));
8160
+ }
8161
+ if (finding.category !== undefined && typeof finding.category !== "string") {
8162
+ violations.push(violation14("high", "review.invalid-category", `findings[${index}].category must be a string`));
8163
+ }
8164
+ if (finding.file_path !== undefined && finding.file_path !== null && typeof finding.file_path !== "string") {
8165
+ violations.push(violation14("high", "review.invalid-file-path", `findings[${index}].file_path must be a string or null`));
8166
+ }
8167
+ if (finding.line_start !== undefined && finding.line_start !== null && typeof finding.line_start !== "number") {
8168
+ violations.push(violation14("high", "review.invalid-line-start", `findings[${index}].line_start must be a number or null`));
8169
+ }
8170
+ if (finding.line_end !== undefined && finding.line_end !== null && typeof finding.line_end !== "number") {
8171
+ violations.push(violation14("high", "review.invalid-line-end", `findings[${index}].line_end must be a number or null`));
8172
+ }
8173
+ if (finding.fingerprint_hint !== undefined && typeof finding.fingerprint_hint !== "string") {
8174
+ violations.push(violation14("high", "review.invalid-fingerprint-hint", `findings[${index}].fingerprint_hint must be a string`));
8175
+ }
8176
+ });
8177
+ }
8178
+ if (doc.tally !== undefined) {
8179
+ if (!isPlainObject7(doc.tally)) {
8180
+ violations.push(violation14("high", "review.invalid-tally", "tally must be a PrTallyResult object"));
8181
+ } else {
8182
+ checkProvidedTallyShape(doc.tally, violations);
8183
+ if (doc.tally.verdict !== doc.verdict) {
8184
+ violations.push(violation14("high", "review.verdict-tally-mismatch", `tally.verdict "${String(doc.tally.verdict)}" does not equal the top-level verdict "${String(doc.verdict)}" - the envelope verdict and tally must agree (consistency rule)`));
8185
+ }
8186
+ }
8187
+ }
8188
+ if (doc.target !== undefined) {
8189
+ if (!isPlainObject7(doc.target)) {
8190
+ violations.push(violation14("high", "review.invalid-target", "target must be an object"));
8191
+ } else {
8192
+ if (doc.target.owner !== undefined && typeof doc.target.owner !== "string") {
8193
+ violations.push(violation14("high", "review.invalid-target", "target.owner must be a string"));
8194
+ }
8195
+ if (doc.target.repo !== undefined && typeof doc.target.repo !== "string") {
8196
+ violations.push(violation14("high", "review.invalid-target", "target.repo must be a string"));
8197
+ }
8198
+ if (doc.target.pr !== undefined && typeof doc.target.pr !== "number") {
8199
+ violations.push(violation14("high", "review.invalid-target", "target.pr must be a number"));
8200
+ }
8201
+ if (doc.target.head_sha !== undefined && typeof doc.target.head_sha !== "string") {
8202
+ violations.push(violation14("high", "review.invalid-target", "target.head_sha must be a string"));
8203
+ }
8204
+ }
8205
+ }
8206
+ return { ok: violations.length === 0, violations };
8207
+ }
7918
8208
  var SHORT_SHA_WIDTH = 7;
7919
8209
  var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
7920
8210
  function todayString4() {
@@ -7962,7 +8252,7 @@ function prReviewReportPath(opts) {
7962
8252
  const sameStem = new RegExp(`^${escaped}(?:-r([0-9]+))?\\.md$`);
7963
8253
  let dirents;
7964
8254
  try {
7965
- dirents = readdirSync9(opts.reportsDir, { withFileTypes: true });
8255
+ dirents = readdirSync10(opts.reportsDir, { withFileTypes: true });
7966
8256
  } catch (error) {
7967
8257
  if (error.code === "ENOENT")
7968
8258
  dirents = [];
@@ -7978,7 +8268,7 @@ function prReviewReportPath(opts) {
7978
8268
  }
7979
8269
  const revision = maxRevision + 1;
7980
8270
  const name = revision === 1 ? `${finalStem}.md` : `${finalStem}-r${revision}.md`;
7981
- return join14(opts.reportsDir, name);
8271
+ return join15(opts.reportsDir, name);
7982
8272
  }
7983
8273
  var PR_TIERS = ["quick", "default", "deep"];
7984
8274
  function violation14(severity, code, message, fix) {
@@ -8248,10 +8538,10 @@ function prReviewSeatPrompt(opts) {
8248
8538
  }
8249
8539
  const skillRoot = opts.skillRoot.trim();
8250
8540
  const worktreePath = opts.worktreePath.trim();
8251
- if (!isAbsolute8(skillRoot)) {
8541
+ if (!isAbsolute9(skillRoot)) {
8252
8542
  throw new TypeError(`prReviewSeatPrompt: skillRoot must be an absolute path - got ${JSON.stringify(opts.skillRoot)}`);
8253
8543
  }
8254
- if (!isAbsolute8(worktreePath)) {
8544
+ if (!isAbsolute9(worktreePath)) {
8255
8545
  throw new TypeError(`prReviewSeatPrompt: worktreePath must be an absolute path - got ${JSON.stringify(opts.worktreePath)}`);
8256
8546
  }
8257
8547
  const slug = `${domain}-${seat}`;
@@ -8275,14 +8565,14 @@ function prReviewSeatPrompt(opts) {
8275
8565
  lines.push("");
8276
8566
  lines.push("## Read first");
8277
8567
  lines.push("");
8278
- const prReviewRef = join14(skillRoot, "references", "pr-review.md");
8568
+ const prReviewRef = join15(skillRoot, "references", "pr-review.md");
8279
8569
  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";
8280
8570
  lines.push(`1. \`${prReviewRef}\` \u2014 read at least these sections: ${sections}.`);
8281
8571
  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).`);
8282
8572
  if (opts.stage === 2) {
8283
- lines.push(`3. \`${join14(skillRoot, "references", "finding-format.md")}\` \u2014 the template every finding follows.`);
8573
+ lines.push(`3. \`${join15(skillRoot, "references", "finding-format.md")}\` \u2014 the template every finding follows.`);
8284
8574
  if (opts.securitySeat === true) {
8285
- lines.push(`4. \`${join14(skillRoot, "references", "security-review.md")}\` \u2014 the security lens.`);
8575
+ lines.push(`4. \`${join15(skillRoot, "references", "security-review.md")}\` \u2014 the security lens.`);
8286
8576
  }
8287
8577
  }
8288
8578
  lines.push("");
@@ -8452,28 +8742,30 @@ function resolvePrReviewTier(input) {
8452
8742
  }
8453
8743
 
8454
8744
  // ../engine/dist/engine.js
8455
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
8745
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
8456
8746
  import { randomUUID as randomUUID2 } from "node:crypto";
8457
- import { basename as basename6, dirname as dirname7, join as join3, resolve as resolve9 } from "node:path";
8747
+ import { basename as basename7, dirname as dirname7, join as join3, resolve as resolve10 } from "node:path";
8458
8748
  import { fileURLToPath } from "node:url";
8459
8749
  import { readFileSync as readFileSync22, statSync as statSync5 } from "node:fs";
8460
- import { dirname as dirname22, isAbsolute as isAbsolute6, join as join22, relative as relative3, resolve as resolve22 } from "node:path";
8461
- import { existsSync as existsSync52, mkdirSync as mkdirSync52, readdirSync as readdirSync42, readFileSync as readFileSync62, realpathSync as realpathSync22, statSync as statSync32, writeFileSync as writeFileSync32 } from "node:fs";
8750
+ import { dirname as dirname22, isAbsolute as isAbsolute7, join as join22, relative as relative3, resolve as resolve22 } from "node:path";
8751
+ import { existsSync as existsSync62, mkdirSync as mkdirSync52, readdirSync as readdirSync52, readFileSync as readFileSync62, realpathSync as realpathSync22, statSync as statSync32, writeFileSync as writeFileSync32 } from "node:fs";
8462
8752
  import { execFileSync as execFileSync5 } from "node:child_process";
8463
- import { basename as basename22, dirname as dirname52, isAbsolute as isAbsolute32, join as join82, relative as relative22, resolve as resolve62 } from "node:path";
8753
+ import { basename as basename32, dirname as dirname52, isAbsolute as isAbsolute42, join as join92, relative as relative22, resolve as resolve72 } from "node:path";
8464
8754
  import { mkdirSync as mkdirSync22, rmdirSync as rmdirSync3, statSync as statSync22, unlinkSync as unlinkSync22, writeFileSync as writeFileSync22 } from "node:fs";
8465
8755
  import { dirname as dirname32, isAbsolute as isAbsolute22, join as join42, resolve as resolve32 } from "node:path";
8466
8756
  import { setTimeout as sleep2 } from "node:timers/promises";
8467
8757
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
8468
- import { existsSync as existsSync32, readFileSync as readFileSync42, readdirSync as readdirSync22, realpathSync as realpathSync4 } from "node:fs";
8469
- import { dirname as dirname42, join as join62, resolve as resolve42, sep as sep2 } from "node:path";
8758
+ import { existsSync as existsSync32, readdirSync as readdirSync22, unlinkSync as unlinkSync32 } from "node:fs";
8759
+ import { isAbsolute as isAbsolute32, join as join52, resolve as resolve42 } from "node:path";
8760
+ import { existsSync as existsSync42, readFileSync as readFileSync42, readdirSync as readdirSync32, realpathSync as realpathSync4 } from "node:fs";
8761
+ import { dirname as dirname42, join as join72, resolve as resolve52, sep as sep2 } from "node:path";
8470
8762
  import { mkdirSync as mkdirSync32 } from "node:fs";
8471
- import { join as join5 } from "node:path";
8472
- import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync82, readdirSync as readdirSync6, writeFileSync as writeFileSync52 } from "node:fs";
8473
- import { dirname as dirname72, isAbsolute as isAbsolute62, join as join10, relative as relative32, resolve as resolve92, sep as sep22 } from "node:path";
8763
+ import { join as join6 } from "node:path";
8764
+ import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync82, readdirSync as readdirSync7, writeFileSync as writeFileSync52 } from "node:fs";
8765
+ import { dirname as dirname72, isAbsolute as isAbsolute72, join as join11, relative as relative32, resolve as resolve102, sep as sep22 } from "node:path";
8474
8766
  var SEVERITY_ORDER2 = ["critical", "high", "medium", "low", "nit"];
8475
8767
  function readJson2(filePath) {
8476
- if (!existsSync10(filePath))
8768
+ if (!existsSync11(filePath))
8477
8769
  return {};
8478
8770
  const content = readFileSync8(filePath, "utf8").trim();
8479
8771
  if (!content)
@@ -8487,23 +8779,23 @@ function readJson2(filePath) {
8487
8779
  function writeJson2(filePath, value) {
8488
8780
  const parent = dirname7(filePath);
8489
8781
  mkdirSync3(parent, { recursive: true });
8490
- const tmp = join3(parent, `.${basename6(filePath)}.${process.pid}.${randomUUID2()}.tmp`);
8782
+ const tmp = join3(parent, `.${basename7(filePath)}.${process.pid}.${randomUUID2()}.tmp`);
8491
8783
  try {
8492
8784
  writeFileSync5(tmp, `${JSON.stringify(value, null, 2)}
8493
8785
  `, "utf8");
8494
8786
  renameSync2(tmp, filePath);
8495
8787
  } catch (error) {
8496
8788
  try {
8497
- unlinkSync3(tmp);
8789
+ unlinkSync4(tmp);
8498
8790
  } catch {}
8499
8791
  throw error;
8500
8792
  }
8501
8793
  }
8502
8794
  function resolveProjectRoot(startDir = process.cwd()) {
8503
- const start = resolve9(startDir);
8795
+ const start = resolve10(startDir);
8504
8796
  let dir = start;
8505
8797
  for (;; ) {
8506
- if (existsSync10(join3(dir, "package.json")) || existsSync10(join3(dir, "bun.lock")))
8798
+ if (existsSync11(join3(dir, "package.json")) || existsSync11(join3(dir, "bun.lock")))
8507
8799
  return dir;
8508
8800
  const parent = dirname7(dir);
8509
8801
  if (parent === dir)
@@ -8514,7 +8806,7 @@ function resolveProjectRoot(startDir = process.cwd()) {
8514
8806
  function findRootPackageJson(startDir) {
8515
8807
  let dir = startDir;
8516
8808
  for (;; ) {
8517
- const candidate = resolve9(dir, "package.json");
8809
+ const candidate = resolve10(dir, "package.json");
8518
8810
  try {
8519
8811
  const pkg = JSON.parse(readFileSync8(candidate, "utf8"));
8520
8812
  if (pkg.name === "morning-star")
@@ -8629,7 +8921,7 @@ function loadMstarc2(startDir, boundary) {
8629
8921
  }
8630
8922
  function isAtOrBelow3(dir, root) {
8631
8923
  const rel = relative3(root, dir);
8632
- return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
8924
+ return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
8633
8925
  }
8634
8926
  function parseCompassFrontmatterText2(content, filePath) {
8635
8927
  const lines = content.split(/\r?\n/);
@@ -8838,6 +9130,137 @@ async function withStatusWriteLock2(statusPath, fn, opts = {}) {
8838
9130
  } catch {}
8839
9131
  }
8840
9132
  }
9133
+ var PLAN_SHAPED_KEY_RE2 = /^[0-9]{8}-[a-z0-9-]+$/;
9134
+ function resolveArtifactPath2(harnessRoot, ref) {
9135
+ const { kind, key } = ref;
9136
+ if (kind === "json") {
9137
+ if (!isAbsolute32(key)) {
9138
+ throw new Error(`ArtifactStore json key must be an absolute path \u2014 got ${JSON.stringify(key)}`);
9139
+ }
9140
+ if (key.split(/[\\/]+/).includes("..")) {
9141
+ throw new Error(`ArtifactStore json key must not contain ".." segments \u2014 got ${JSON.stringify(key)}`);
9142
+ }
9143
+ return key;
9144
+ }
9145
+ assertSafePathComponent2(key, "ArtifactStore key");
9146
+ if (kind === "status") {
9147
+ if (key !== "root") {
9148
+ throw new Error(`ArtifactStore status key must be "root" \u2014 got ${JSON.stringify(key)}`);
9149
+ }
9150
+ return join52(harnessRoot, "status.json");
9151
+ }
9152
+ if (kind === "snapshot") {
9153
+ return join52(resolveWorkflowDir2(harnessRoot, { harnessDir: harnessRoot }), key, "snapshot.json");
9154
+ }
9155
+ if (kind === "residuals") {
9156
+ return join52(resolveProjectDir2(harnessRoot, { harnessDir: harnessRoot }), key, "residuals.json");
9157
+ }
9158
+ if (PLAN_SHAPED_KEY_RE2.test(key)) {
9159
+ return join52(harnessRoot, "sdd", key, "review", "report.json");
9160
+ }
9161
+ return join52(harnessRoot, "sdd", "_reviews", `${key}.json`);
9162
+ }
9163
+ function listDirNames2(dir) {
9164
+ return readDirEntries2(dir).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
9165
+ }
9166
+ function listJsonKeys2(dir) {
9167
+ return readDirEntries2(dir).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length));
9168
+ }
9169
+ function readDirEntries2(dir) {
9170
+ try {
9171
+ return readdirSync22(dir, { withFileTypes: true });
9172
+ } catch (error) {
9173
+ const code = error.code;
9174
+ if (code === "ENOENT" || code === "ENOTDIR")
9175
+ return [];
9176
+ throw error;
9177
+ }
9178
+ }
9179
+ function tryResolveGetPath2(root, kind, key) {
9180
+ try {
9181
+ return resolveArtifactPath2(root, { kind, key });
9182
+ } catch {
9183
+ return;
9184
+ }
9185
+ }
9186
+ function createFsStore2(harnessRoot) {
9187
+ const root = resolve42(harnessRoot);
9188
+ return {
9189
+ root,
9190
+ async put(doc) {
9191
+ if (doc.schema !== undefined) {
9192
+ throw new Error("FsStore does not persist schema ids \u2014 omit --schema or inject a store module that persists it");
9193
+ }
9194
+ writeJson2(resolveArtifactPath2(root, doc), doc.payload);
9195
+ },
9196
+ async get(ref) {
9197
+ const filePath = resolveArtifactPath2(root, ref);
9198
+ if (!existsSync32(filePath))
9199
+ return;
9200
+ return readJson2(filePath);
9201
+ },
9202
+ async delete(ref) {
9203
+ const filePath = resolveArtifactPath2(root, ref);
9204
+ if (existsSync32(filePath))
9205
+ unlinkSync32(filePath);
9206
+ },
9207
+ async list(kind) {
9208
+ if (kind === "json") {
9209
+ throw new Error("ArtifactStore json keys are absolute paths and cannot be listed");
9210
+ }
9211
+ const keys = [];
9212
+ if (kind === "status") {
9213
+ if (existsSync32(resolveArtifactPath2(root, { kind, key: "root" })))
9214
+ keys.push("root");
9215
+ } else if (kind === "snapshot" || kind === "residuals") {
9216
+ const baseDir = kind === "snapshot" ? resolveWorkflowDir2(root, { harnessDir: root }) : resolveProjectDir2(root, { harnessDir: root });
9217
+ for (const name of listDirNames2(baseDir)) {
9218
+ const getPath = tryResolveGetPath2(root, kind, name);
9219
+ if (getPath !== undefined && existsSync32(getPath))
9220
+ keys.push(name);
9221
+ }
9222
+ } else {
9223
+ const sddDir = join52(root, "sdd");
9224
+ for (const key of listJsonKeys2(join52(sddDir, "_reviews"))) {
9225
+ if (!PLAN_SHAPED_KEY_RE2.test(key) && tryResolveGetPath2(root, kind, key) !== undefined) {
9226
+ keys.push(key);
9227
+ }
9228
+ }
9229
+ for (const name of listDirNames2(sddDir)) {
9230
+ if (PLAN_SHAPED_KEY_RE2.test(name)) {
9231
+ const getPath = tryResolveGetPath2(root, kind, name);
9232
+ if (getPath !== undefined && existsSync32(getPath))
9233
+ keys.push(name);
9234
+ }
9235
+ }
9236
+ }
9237
+ return keys.sort().map((key) => ({ kind, key }));
9238
+ }
9239
+ };
9240
+ }
9241
+ var injectedStore2;
9242
+ function setArtifactStore2(store) {
9243
+ injectedStore2 = store;
9244
+ }
9245
+ function getArtifactStore2() {
9246
+ if (injectedStore2 !== undefined)
9247
+ return injectedStore2;
9248
+ const root = resolveHarnessDir2(process.cwd());
9249
+ if (root === null) {
9250
+ throw new Error(`harness dir not found from ${resolve42(process.cwd())} \u2014 cannot create the default FsStore (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
9251
+ }
9252
+ return createFsStore2(root);
9253
+ }
9254
+ function assertFsStorePath2(store, ref, expectedPath) {
9255
+ const root = store.root;
9256
+ if (typeof root !== "string")
9257
+ return;
9258
+ const storePath = resolveArtifactPath2(root, ref);
9259
+ const expected = resolve42(expectedPath);
9260
+ if (storePath !== expected) {
9261
+ throw new Error(`routed writer path mismatch: the active FsStore resolves ${ref.kind}/${JSON.stringify(ref.key)} to ${JSON.stringify(storePath)} but the caller's target is ${JSON.stringify(expected)} \u2014 call setArtifactStore(createFsStore(<root>)) first when the write target differs from the active store's root`);
9262
+ }
9263
+ }
8841
9264
  var WORKFLOW_SNAPSHOT_FILE2 = "snapshot.json";
8842
9265
  var WORKFLOW_LIFECYCLE_STATUSES2 = ["running", "paused", "completed", "failed", "stopped"];
8843
9266
  var WORKFLOW_TERMINAL_STATUSES2 = ["completed", "failed", "stopped"];
@@ -8954,10 +9377,12 @@ async function writeWorkflowSnapshot(snapshot, dir) {
8954
9377
  const detail = gate2.violations.map((v) => v.message).join("; ");
8955
9378
  throw new Error(`refusing to write invalid workflow snapshot: ${detail}`);
8956
9379
  }
8957
- const snapshotPath = join5(dir, WORKFLOW_SNAPSHOT_FILE2);
9380
+ const snapshotPath = join6(dir, WORKFLOW_SNAPSHOT_FILE2);
9381
+ const store = getArtifactStore2();
9382
+ assertFsStorePath2(store, { kind: "snapshot", key: snapshot.id }, snapshotPath);
8958
9383
  mkdirSync32(dir, { recursive: true });
8959
- await withStatusWriteLock2(snapshotPath, () => {
8960
- writeJson2(snapshotPath, snapshot);
9384
+ await withStatusWriteLock2(snapshotPath, async () => {
9385
+ await store.put({ kind: "snapshot", key: snapshot.id, payload: snapshot });
8961
9386
  });
8962
9387
  }
8963
9388
  var DATE_RE22 = /^\d{4}-\d{2}-\d{2}$/;
@@ -9109,7 +9534,7 @@ function validateStatusV22(docOrPath, opts = {}) {
9109
9534
  if (typeof docOrPath === "string") {
9110
9535
  try {
9111
9536
  doc = readJson2(docOrPath);
9112
- harnessDir = dirname42(resolve42(docOrPath));
9537
+ harnessDir = dirname42(resolve52(docOrPath));
9113
9538
  } catch (error) {
9114
9539
  return {
9115
9540
  ok: false,
@@ -9176,8 +9601,8 @@ function validateStatusV22(docOrPath, opts = {}) {
9176
9601
  for (const entry of doc.workflows) {
9177
9602
  if (!isPlainObject42(entry) || typeof entry.dir !== "string")
9178
9603
  continue;
9179
- const relSnapshot = join62(entry.dir, WORKFLOW_SNAPSHOT_FILE2);
9180
- const snapshotPath = join62(harnessDir, relSnapshot);
9604
+ const relSnapshot = join72(entry.dir, WORKFLOW_SNAPSHOT_FILE2);
9605
+ const snapshotPath = join72(harnessDir, relSnapshot);
9181
9606
  const label = typeof entry.id === "string" ? entry.id : relSnapshot;
9182
9607
  let physical;
9183
9608
  try {
@@ -9270,19 +9695,19 @@ function validateProjectRegister2(doc) {
9270
9695
  return { ok: violations.length === 0, violations };
9271
9696
  }
9272
9697
  function resolveHarnessDir2(startDir = process.cwd(), opts = {}) {
9273
- const start = resolve62(startDir);
9698
+ const start = resolve72(startDir);
9274
9699
  const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
9275
9700
  if (explicit)
9276
- return resolve62(start, explicit);
9277
- const boundary = resolve62(start, opts.workspaceRoot ?? defaultWorkspaceRoot2(start));
9701
+ return resolve72(start, explicit);
9702
+ const boundary = resolve72(start, opts.workspaceRoot ?? defaultWorkspaceRoot2(start));
9278
9703
  const rc = loadMstarc2(start, boundary);
9279
9704
  if (rc !== null && rc.config.harnessDir)
9280
- return resolve62(rc.dir, rc.config.harnessDir);
9705
+ return resolve72(rc.dir, rc.config.harnessDir);
9281
9706
  let dir = start;
9282
9707
  for (;; ) {
9283
9708
  if (!isAtOrBelow22(dir, boundary))
9284
9709
  return null;
9285
- for (const candidate of [join82(dir, ".mstar"), join82(dir, ".agents"), join82(dir, ".plans"), join82(dir, "plans")]) {
9710
+ for (const candidate of [join92(dir, ".mstar"), join92(dir, ".agents"), join92(dir, ".plans"), join92(dir, "plans")]) {
9286
9711
  if (isDirectory3(candidate))
9287
9712
  return candidate;
9288
9713
  }
@@ -9308,19 +9733,19 @@ function defaultWorkspaceRoot2(startDir) {
9308
9733
  if (segment && segment !== ".")
9309
9734
  boundary = dirname52(boundary);
9310
9735
  }
9311
- return resolve62(boundary);
9736
+ return resolve72(boundary);
9312
9737
  } catch {}
9313
9738
  return startDir;
9314
9739
  }
9315
9740
  function isAtOrBelow22(dir, root) {
9316
9741
  const rel = relative22(root, dir);
9317
- return rel === "" || !rel.startsWith("..") && !isAbsolute32(rel);
9742
+ return rel === "" || !rel.startsWith("..") && !isAbsolute42(rel);
9318
9743
  }
9319
9744
  function mstarcDirOverride2(harnessDir, key) {
9320
- const dir = resolve62(harnessDir);
9745
+ const dir = resolve72(harnessDir);
9321
9746
  const rc = loadMstarc2(dir, dirname52(dir));
9322
9747
  const declared = rc?.config[key];
9323
- return declared ? resolve62(rc.dir, declared) : null;
9748
+ return declared ? resolve72(rc.dir, declared) : null;
9324
9749
  }
9325
9750
  function assertSafePathComponent2(value, what) {
9326
9751
  if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
@@ -9330,10 +9755,10 @@ function assertSafePathComponent2(value, what) {
9330
9755
  function resolveHarnessSubdir2(startDir, opts, key, fallback) {
9331
9756
  const harness = resolveHarnessDir2(startDir, opts);
9332
9757
  if (harness === null) {
9333
- throw new Error(`harness dir not found from ${resolve62(startDir)} \u2014 cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
9758
+ throw new Error(`harness dir not found from ${resolve72(startDir)} \u2014 cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
9334
9759
  }
9335
9760
  const declared = mstarcDirOverride2(harness, key);
9336
- return declared !== null ? declared : join82(resolve62(harness), fallback);
9761
+ return declared !== null ? declared : join92(resolve72(harness), fallback);
9337
9762
  }
9338
9763
  function resolveWorkflowDir2(startDir = process.cwd(), opts = {}) {
9339
9764
  return resolveHarnessSubdir2(startDir, opts, "workflowDir", "workflows");
@@ -9373,7 +9798,6 @@ function isDirectory3(dir) {
9373
9798
  return false;
9374
9799
  }
9375
9800
  }
9376
- var GIT_CAPTURE_MAX_BYTES2 = 64 * 1024 * 1024;
9377
9801
  var MIGRATE_STATUS_FILE = "status.json";
9378
9802
  var ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json";
9379
9803
  var NOTES_LEDGER_FILE = "notes.jsonl";
@@ -9413,18 +9837,18 @@ function todayString3() {
9413
9837
  return `${now.getFullYear()}-${month}-${day}`;
9414
9838
  }
9415
9839
  function scanCompasses(harnessDir) {
9416
- const iterationsDir = join10(harnessDir, "iterations");
9840
+ const iterationsDir = join11(harnessDir, "iterations");
9417
9841
  const out = [];
9418
9842
  let entries;
9419
9843
  try {
9420
- entries = readdirSync6(iterationsDir, { withFileTypes: true });
9844
+ entries = readdirSync7(iterationsDir, { withFileTypes: true });
9421
9845
  } catch {
9422
9846
  return out;
9423
9847
  }
9424
9848
  for (const entry of entries) {
9425
9849
  if (!entry.isDirectory())
9426
9850
  continue;
9427
- const compassPath = join10(iterationsDir, entry.name, "delivery-compass.md");
9851
+ const compassPath = join11(iterationsDir, entry.name, "delivery-compass.md");
9428
9852
  let content;
9429
9853
  try {
9430
9854
  content = readFileSync82(compassPath, "utf8");
@@ -9504,8 +9928,8 @@ function buildIterationSnapshot(compass, rowById, rootUpdatedAt) {
9504
9928
  id: compass.id,
9505
9929
  type: "iteration",
9506
9930
  status,
9507
- file: join10("workflows", compass.id, WORKFLOW_SNAPSHOT_FILE2),
9508
- source: join10("iterations", compass.id, "delivery-compass.md"),
9931
+ file: join11("workflows", compass.id, WORKFLOW_SNAPSHOT_FILE2),
9932
+ source: join11("iterations", compass.id, "delivery-compass.md"),
9509
9933
  data: snapshot
9510
9934
  };
9511
9935
  }
@@ -9540,7 +9964,7 @@ function buildStandaloneSnapshot(row, rootUpdatedAt, migrationNotes) {
9540
9964
  id,
9541
9965
  type: "plan",
9542
9966
  status,
9543
- file: join10("workflows", id, WORKFLOW_SNAPSHOT_FILE2),
9967
+ file: join11("workflows", id, WORKFLOW_SNAPSHOT_FILE2),
9544
9968
  source: "status.json plans[] row",
9545
9969
  data: snapshot
9546
9970
  };
@@ -9648,7 +10072,7 @@ function buildRegister(residualFindings, byPlan, projectId, migratedAt) {
9648
10072
  return null;
9649
10073
  const doc = { entries };
9650
10074
  return {
9651
- file: join10("projects", projectId, PROJECT_REGISTER_FILE2),
10075
+ file: join11("projects", projectId, PROJECT_REGISTER_FILE2),
9652
10076
  source: "status.json residual_findings",
9653
10077
  data: doc
9654
10078
  };
@@ -9679,7 +10103,7 @@ function collectNotesFiles(snapshots) {
9679
10103
  if (lines.length === 0)
9680
10104
  continue;
9681
10105
  out.push({
9682
- file: join10(dirname72(snapshot.file), NOTES_LEDGER_FILE),
10106
+ file: join11(dirname72(snapshot.file), NOTES_LEDGER_FILE),
9683
10107
  source,
9684
10108
  lines
9685
10109
  });
@@ -9687,11 +10111,11 @@ function collectNotesFiles(snapshots) {
9687
10111
  return out;
9688
10112
  }
9689
10113
  function migrateHarnessTree(root, opts = {}) {
9690
- const harnessDir = resolve92(root);
10114
+ const harnessDir = resolve102(root);
9691
10115
  const workflowDir = resolveWorkflowDir2(harnessDir, { harnessDir });
9692
10116
  const projectDir = resolveProjectDir2(harnessDir, { harnessDir });
9693
10117
  const projectId = opts.projectId ?? _DEFAULT_PROJECT2;
9694
- const statusPath = join10(harnessDir, MIGRATE_STATUS_FILE);
10118
+ const statusPath = join11(harnessDir, MIGRATE_STATUS_FILE);
9695
10119
  const legacy = readJson2(statusPath);
9696
10120
  if (legacy.version === 2) {
9697
10121
  const updatedAt = typeof legacy.updated_at === "string" && legacy.updated_at !== "" ? legacy.updated_at : "1970-01-01";
@@ -9787,7 +10211,7 @@ function migrateHarnessTree(root, opts = {}) {
9787
10211
  migrationNotes.push(`roadmap title sanitized for frontmatter (line breaks replaced with spaces): ${JSON.stringify(rawTitle)}`);
9788
10212
  }
9789
10213
  roadmap = {
9790
- file: join10("projects", projectId, PROJECT_ROADMAP_FILE2),
10214
+ file: join11("projects", projectId, PROJECT_ROADMAP_FILE2),
9791
10215
  source: "status.json metadata.program_roadmap",
9792
10216
  content: buildRoadmap({ ...programRoadmap, title: sanitizedTitle }, projectId, migratedAt)
9793
10217
  };
@@ -9836,19 +10260,19 @@ async function applyMigratePlan(plan) {
9836
10260
  if (plan.dryRun) {
9837
10261
  return { applied: false, message: `dry-run: ${plan.steps.length} steps planned (source \u2192 destination), zero writes` };
9838
10262
  }
9839
- const statusPath = join10(plan.root, MIGRATE_STATUS_FILE);
10263
+ const statusPath = join11(plan.root, MIGRATE_STATUS_FILE);
9840
10264
  const current = readJson2(statusPath);
9841
10265
  if (current.version === 2) {
9842
10266
  return { applied: false, message: "no-op: status.json already at schema version 2 (migrated) \u2014 nothing to do" };
9843
10267
  }
9844
- const harnessRoot = resolve92(plan.root);
9845
- const workflowRoot = resolve92(plan.workflowDir);
9846
- const projectRoot = resolve92(plan.projectDir);
9847
- if (!isAbsolute62(plan.workflowDir) || !isAbsolute62(plan.projectDir)) {
10268
+ const harnessRoot = resolve102(plan.root);
10269
+ const workflowRoot = resolve102(plan.workflowDir);
10270
+ const projectRoot = resolve102(plan.projectDir);
10271
+ if (!isAbsolute72(plan.workflowDir) || !isAbsolute72(plan.projectDir)) {
9848
10272
  throw new Error(`refusing to apply migration: plan workflowDir/projectDir must be absolute (got ${JSON.stringify(plan.workflowDir)} / ${JSON.stringify(plan.projectDir)})`);
9849
10273
  }
9850
- const workflowTargetOf = (canonicalFile) => join10(workflowRoot, relative32("workflows", canonicalFile));
9851
- const projectTargetOf = (canonicalFile) => join10(projectRoot, relative32("projects", canonicalFile));
10274
+ const workflowTargetOf = (canonicalFile) => join11(workflowRoot, relative32("workflows", canonicalFile));
10275
+ const projectTargetOf = (canonicalFile) => join11(projectRoot, relative32("projects", canonicalFile));
9852
10276
  const allDestinations = [
9853
10277
  plan.archive.file,
9854
10278
  ...plan.snapshots.map((snapshot) => snapshot.file),
@@ -9857,14 +10281,14 @@ async function applyMigratePlan(plan) {
9857
10281
  ...plan.roadmap !== null ? [plan.roadmap.file] : []
9858
10282
  ];
9859
10283
  for (const destination of allDestinations) {
9860
- const resolvedDest = resolve92(join10(plan.root, destination));
10284
+ const resolvedDest = resolve102(join11(plan.root, destination));
9861
10285
  const inside = (dir) => resolvedDest === dir || resolvedDest.startsWith(`${dir}${sep22}`);
9862
10286
  if (!inside(harnessRoot) && !inside(workflowRoot) && !inside(projectRoot)) {
9863
10287
  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)})`);
9864
10288
  }
9865
10289
  }
9866
- mkdirSync7(join10(plan.root, dirname72(plan.archive.file)), { recursive: true });
9867
- copyFileSync(statusPath, join10(plan.root, plan.archive.file));
10290
+ mkdirSync7(join11(plan.root, dirname72(plan.archive.file)), { recursive: true });
10291
+ copyFileSync(statusPath, join11(plan.root, plan.archive.file));
9868
10292
  for (const snapshot of plan.snapshots) {
9869
10293
  await writeWorkflowSnapshot(snapshot.data, dirname72(workflowTargetOf(snapshot.file)));
9870
10294
  }
@@ -9920,7 +10344,7 @@ var RISK_ENUM_RE2 = new RegExp(`^(?:${AUDIT_RISKS2.join("|")})(?:\\b|$)`);
9920
10344
  var CONFIDENCE_ENUM_RE2 = new RegExp(`^(${[...AUDIT_CONFIDENCES2, "MEDIUM"].join("|")})\\b`, "i");
9921
10345
  // src/commands/migrate.ts
9922
10346
  var import_picocolors = __toESM(require_picocolors2(), 1);
9923
- import { resolve as resolve12 } from "node:path";
10347
+ import { resolve as resolve13 } from "node:path";
9924
10348
  function validatePlannedDocs(plan) {
9925
10349
  const warnings = [];
9926
10350
  for (const snapshot of plan.snapshots) {
@@ -9942,7 +10366,8 @@ function validatePlannedDocs(plan) {
9942
10366
  return warnings;
9943
10367
  }
9944
10368
  async function runMigrateCommand(options) {
9945
- const root = options.path ? resolve12(options.path) : resolveHarnessDir2() ?? process.cwd();
10369
+ const root = options.path ? resolve13(options.path) : resolveHarnessDir2() ?? process.cwd();
10370
+ setArtifactStore2(createFsStore2(root));
9946
10371
  let plan;
9947
10372
  try {
9948
10373
  plan = migrateHarnessTree(root, { dryRun: options.dryRun === true });
@@ -10097,7 +10522,7 @@ function describeType(value) {
10097
10522
  return "array";
10098
10523
  return typeof value;
10099
10524
  }
10100
- function isPlainObject7(value) {
10525
+ function isPlainObject8(value) {
10101
10526
  return typeof value === "object" && value !== null && !Array.isArray(value);
10102
10527
  }
10103
10528
  function parseScalar3(raw) {
@@ -10141,7 +10566,7 @@ function isValidMcpUrl(raw) {
10141
10566
  return true;
10142
10567
  }
10143
10568
  function validateManifest(manifest, errors2, warnings) {
10144
- if (!isPlainObject7(manifest)) {
10569
+ if (!isPlainObject8(manifest)) {
10145
10570
  errors2.push("plugin.json: manifest must be a JSON object");
10146
10571
  return;
10147
10572
  }
@@ -10177,7 +10602,7 @@ function validateManifest(manifest, errors2, warnings) {
10177
10602
  }
10178
10603
  }
10179
10604
  if (doc.author !== undefined) {
10180
- if (!isPlainObject7(doc.author)) {
10605
+ if (!isPlainObject8(doc.author)) {
10181
10606
  errors2.push('plugin.json: "author" must be an object with optional string fields name/email/url');
10182
10607
  } else {
10183
10608
  const author = doc.author;
@@ -10200,11 +10625,11 @@ function validateManifest(manifest, errors2, warnings) {
10200
10625
  }
10201
10626
  }
10202
10627
  if (doc.extensions !== undefined) {
10203
- if (!isPlainObject7(doc.extensions)) {
10628
+ if (!isPlainObject8(doc.extensions)) {
10204
10629
  warnings.push('plugin.json: "extensions" is not an object \u2014 ignored');
10205
10630
  } else {
10206
10631
  for (const [namespace, value] of Object.entries(doc.extensions)) {
10207
- if (!isPlainObject7(value)) {
10632
+ if (!isPlainObject8(value)) {
10208
10633
  warnings.push(`plugin.json: "extensions.${namespace}" is not an object \u2014 ignored`);
10209
10634
  }
10210
10635
  }
@@ -10213,7 +10638,7 @@ function validateManifest(manifest, errors2, warnings) {
10213
10638
  }
10214
10639
  function validateMcpServer(name, entry, errors2) {
10215
10640
  const prefix = `mcp.json: mcpServers.${name}`;
10216
- if (!isPlainObject7(entry)) {
10641
+ if (!isPlainObject8(entry)) {
10217
10642
  errors2.push(`${prefix} must be an object`);
10218
10643
  return;
10219
10644
  }
@@ -10247,7 +10672,7 @@ function validateMcpServer(name, entry, errors2) {
10247
10672
  }
10248
10673
  }
10249
10674
  if (server.env !== undefined) {
10250
- if (!isPlainObject7(server.env)) {
10675
+ if (!isPlainObject8(server.env)) {
10251
10676
  errors2.push(`${prefix}: "env" must be an object of strings`);
10252
10677
  } else {
10253
10678
  for (const [key, value] of Object.entries(server.env)) {
@@ -10286,7 +10711,7 @@ function validateMcpServer(name, entry, errors2) {
10286
10711
  errors2.push(`${prefix}: "url" must be an absolute http(s) URL without user info or fragment; non-loopback endpoints must use https`);
10287
10712
  }
10288
10713
  if (server.headers !== undefined) {
10289
- if (!isPlainObject7(server.headers)) {
10714
+ if (!isPlainObject8(server.headers)) {
10290
10715
  errors2.push(`${prefix}: "headers" must be an object of strings`);
10291
10716
  } else {
10292
10717
  const seen = new Set;
@@ -10323,7 +10748,7 @@ function validateMcp(root, manifestSchema, errors2) {
10323
10748
  errors2.push(`mcp.json: ${error.message}`);
10324
10749
  return;
10325
10750
  }
10326
- if (!isPlainObject7(parsed)) {
10751
+ if (!isPlainObject8(parsed)) {
10327
10752
  errors2.push("mcp.json: configuration must be a JSON object");
10328
10753
  return;
10329
10754
  }
@@ -10346,7 +10771,7 @@ function validateMcp(root, manifestSchema, errors2) {
10346
10771
  }
10347
10772
  }
10348
10773
  const servers = doc.mcpServers;
10349
- if (!isPlainObject7(servers)) {
10774
+ if (!isPlainObject8(servers)) {
10350
10775
  errors2.push('mcp.json: "mcpServers" is required and must be an object');
10351
10776
  return;
10352
10777
  }
@@ -10431,7 +10856,7 @@ function validateAgentPlugin(root) {
10431
10856
  return { ok: false, errors: errors2, warnings };
10432
10857
  }
10433
10858
  validateManifest(manifest, errors2, warnings);
10434
- const manifestSchema = isPlainObject7(manifest) ? manifest["$schema"] : undefined;
10859
+ const manifestSchema = isPlainObject8(manifest) ? manifest["$schema"] : undefined;
10435
10860
  validateMcp(root, manifestSchema, errors2);
10436
10861
  validateSkills(root, errors2, warnings);
10437
10862
  return { ok: errors2.length === 0, errors: errors2, warnings };
@@ -11706,6 +12131,68 @@ function getAdapter(target) {
11706
12131
  return adapter;
11707
12132
  }
11708
12133
 
12134
+ // src/global-cli.ts
12135
+ import { execFileSync as execFileSync9 } from "node:child_process";
12136
+ var CLI_NAME = "mstar-harness";
12137
+ var NPM_INSTALL_TIMEOUT_MS = 300000;
12138
+ var NPM_INSTALL_TIMEOUT_ENV = "MSTAR_CLI_INSTALL_TIMEOUT_MS";
12139
+ function installTimeoutMs() {
12140
+ const raw = process.env[NPM_INSTALL_TIMEOUT_ENV];
12141
+ if (raw === undefined || raw.trim() === "")
12142
+ return NPM_INSTALL_TIMEOUT_MS;
12143
+ const parsed = Number(raw);
12144
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : NPM_INSTALL_TIMEOUT_MS;
12145
+ }
12146
+ function defaultDetectVersion() {
12147
+ try {
12148
+ const out = execFileSync9(CLI_NAME, ["--version"], { encoding: "utf8", env: process.env });
12149
+ return out.trim();
12150
+ } catch {
12151
+ return null;
12152
+ }
12153
+ }
12154
+ function defaultInstall(spec) {
12155
+ runCliCommand(["npm", "i", "-g", spec], { timeoutMs: installTimeoutMs() });
12156
+ }
12157
+ function ensureGlobalCli(opts) {
12158
+ const spec = `@mstar-harness/cli@${opts.version}`;
12159
+ const log = opts.log ?? ((line) => console.log(line));
12160
+ if (opts.noGlobalCli) {
12161
+ log("Skipping global CLI install (--no-global-cli).");
12162
+ return { action: "skipped", reason: "flag" };
12163
+ }
12164
+ if (opts.dryRun) {
12165
+ log(`Would run: npm i -g ${spec}`);
12166
+ return { action: "skipped", reason: "dry-run" };
12167
+ }
12168
+ const current = (opts.detectVersion ?? defaultDetectVersion)();
12169
+ if (current === opts.version) {
12170
+ log(`${CLI_NAME} ${current} already on PATH.`);
12171
+ return { action: "skipped", reason: "already-matching" };
12172
+ }
12173
+ try {
12174
+ (opts.install ?? defaultInstall)(spec);
12175
+ log(`Installed ${spec} globally.`);
12176
+ return { action: "installed", spec };
12177
+ } catch (err) {
12178
+ const message = err instanceof Error ? err.message : String(err);
12179
+ const kind = /EACCES|EPERM/.test(message) ? "permission" : "npm";
12180
+ log(`Global CLI install failed: ${message}`);
12181
+ log(`Failure kind: ${kind} (retry: npm i -g ${spec})`);
12182
+ log(`Hint: run \`mstar-harness doctor\` to check the CLI on PATH.`);
12183
+ return { action: "failed", spec, message };
12184
+ }
12185
+ }
12186
+ function formatCliDoctorNote(detected, expected) {
12187
+ if (detected === null) {
12188
+ return `CLI on PATH: ${CLI_NAME} not found (expected version ${expected})`;
12189
+ }
12190
+ if (detected !== expected) {
12191
+ return `CLI on PATH: ${CLI_NAME} ${detected} (expected ${expected})`;
12192
+ }
12193
+ return `CLI on PATH: ${CLI_NAME} ${detected} (matching)`;
12194
+ }
12195
+
11709
12196
  // src/types.ts
11710
12197
  var SUPPORTED_TARGETS = ["opencode", "cursor", "codex", "zcode", "omp", "dsh"];
11711
12198
 
@@ -11811,6 +12298,7 @@ async function runInit5(options) {
11811
12298
  for (const note of installResult.notes) {
11812
12299
  console.log(` - ${note}`);
11813
12300
  }
12301
+ ensureGlobalCli({ version: packageVersion, dryRun: !!options.dryRun, noGlobalCli: !!options.noGlobalCli });
11814
12302
  return;
11815
12303
  }
11816
12304
  const useExplicitModels = hasExplicitModelFlags(options);
@@ -11854,12 +12342,14 @@ async function runInit5(options) {
11854
12342
  console.log(` - ${roleId}: ${modelId}`);
11855
12343
  }
11856
12344
  }
12345
+ ensureGlobalCli({ version: packageVersion, dryRun: !!options.dryRun, noGlobalCli: !!options.noGlobalCli });
11857
12346
  }
11858
12347
  function runDoctor5(options) {
11859
12348
  const target = options.target || "opencode";
11860
12349
  const adapter = getAdapter(target);
11861
12350
  const scope = options.scope || "project";
11862
12351
  console.log(`Target: ${target}`);
12352
+ console.log(formatCliDoctorNote(defaultDetectVersion(), packageVersion));
11863
12353
  if (adapter.mode === "install") {
11864
12354
  const result = adapter.runInstallDoctor?.(scope);
11865
12355
  if (!result) {
@@ -11947,7 +12437,7 @@ var CANONICAL_NEGATIONS = {
11947
12437
  };
11948
12438
  function gitWorkspaceRoot(startDir) {
11949
12439
  try {
11950
- const cdup = execFileSync9("git", ["rev-parse", "--show-cdup"], {
12440
+ const cdup = execFileSync10("git", ["rev-parse", "--show-cdup"], {
11951
12441
  cwd: startDir,
11952
12442
  encoding: "utf8",
11953
12443
  stdio: ["ignore", "pipe", "ignore"]
@@ -12196,8 +12686,12 @@ function runScaffold(pathArg) {
12196
12686
  console.log(` skipped: ${item}`);
12197
12687
  }
12198
12688
  program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
12199
- program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--no-fallbacks", "Skip installing the dsh-llm-fallbacks plugin (dsh target only)").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
12200
- await runInit5({ ...options, noFallbacks: options.fallbacks === false });
12689
+ program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--no-fallbacks", "Skip installing the dsh-llm-fallbacks plugin (dsh target only)").option("--no-global-cli", "Skip installing the matching-version @mstar-harness/cli globally after init").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
12690
+ await runInit5({
12691
+ ...options,
12692
+ noFallbacks: options.fallbacks === false,
12693
+ noGlobalCli: options.globalCli === false
12694
+ });
12201
12695
  });
12202
12696
  var harnessCommand = program2.command("harness").description("harness bootstrap / dir-resolution helpers (engine-backed)");
12203
12697
  harnessCommand.command("scaffold").description("One-shot harness bootstrap: create the harness dir (default .mstar/, honoring .mstarc harness_dir/project_dir) " + "with dirs + v2 status.json + projects/_default/, append the canonical .gitignore snippet when absent " + "(skipped for non-.mstar layouts), and write a minimal {HARNESS_DIR}/AGENTS.md when absent").argument("[path]", "Root to scaffold (default: cwd)").action((pathArg) => {
@@ -12342,6 +12836,8 @@ statusCommand.command("backlog-register").description("Register deferred-PR back
12342
12836
  }
12343
12837
  const projectId = sanitizeProjectId(options.project ?? _DEFAULT_PROJECT);
12344
12838
  const projectRoot = resolveProjectDir(process.cwd(), options.harness ? { harnessDir: options.harness } : {});
12839
+ if (options.harness)
12840
+ setArtifactStore(createFsStore(options.harness));
12345
12841
  const projectDir = path12.join(projectRoot, projectId);
12346
12842
  const registeredAt = todayString5();
12347
12843
  const entries = entriesRaw.map((raw, index) => {
@@ -12367,6 +12863,8 @@ statusCommand.command("backlog-close").description("Close one project-register b
12367
12863
  try {
12368
12864
  const projectId = sanitizeProjectId(options.project ?? _DEFAULT_PROJECT);
12369
12865
  const projectRoot = resolveProjectDir(process.cwd(), options.harness ? { harnessDir: options.harness } : {});
12866
+ if (options.harness)
12867
+ setArtifactStore(createFsStore(options.harness));
12370
12868
  const projectDir = path12.join(projectRoot, projectId);
12371
12869
  await closeProjectRegisterEntry({
12372
12870
  projectDir,
@@ -12387,6 +12885,143 @@ statusCommand.command("archive-residuals").description("Removed in v3 \u2014 res
12387
12885
  var migrateCommand = program2.command("migrate").description("Migrate a v1 {HARNESS_DIR} status.json tree to v2 (engine-backed; exit 0 ok/idempotent no-op, 1 plan-invalid, 2 apply-failure)").option("--dry-run", "Print the migration step plan (source \u2192 destination) + planned-document validation warnings without writing anything").option("--path <root>", "Harness root to migrate (default: resolved {HARNESS_DIR}, else cwd)").option("--json", "Machine-readable JSON output").action(async (options) => {
12388
12886
  await runMigrateCommand(options);
12389
12887
  });
12888
+ var persistCommand = program2.command("persist").description("Persist one JSON coordination doc through the ArtifactStore (status / snapshot / residuals / review / json). " + "Faces: put (default), get [--validate], list (keys only, no header), delete (idempotent). " + "The default FsStore resolves the harness dir from the cwd / MSTAR_HARNESS_DIR; --store <module> or " + "MSTAR_STORE_MODULE injects a module-backed store (filesystem paths only) for the process.");
12889
+ var PERSIST_KINDS = ["status", "snapshot", "residuals", "review", "json"];
12890
+ function parsePersistKind(kind) {
12891
+ if (PERSIST_KINDS.includes(kind))
12892
+ return kind;
12893
+ throw new SddScriptError(`usage: persist <kind> --key <key> [--file <path>|--stdin] [--store <module>] [--schema <id>]
12894
+ ` + ` persist get <kind> --key <key> [--validate] [--store <module>]
12895
+ ` + ` persist list <kind> [--store <module>]
12896
+ ` + ` persist delete <kind> --key <key> [--store <module>]
12897
+ ` + ` unknown kind ${JSON.stringify(kind)} \u2014 expected status | snapshot | residuals | review | json`, 2);
12898
+ }
12899
+ async function resolvePersistStore(storeFlag) {
12900
+ const modulePath = storeFlag ?? process.env.MSTAR_STORE_MODULE;
12901
+ if (modulePath === undefined)
12902
+ return;
12903
+ setArtifactStore(await loadStoreModule(modulePath));
12904
+ }
12905
+ function readPersistPayload(options) {
12906
+ if (options.file !== undefined && options.stdin === true) {
12907
+ throw new SddScriptError("usage: persist --file <path> and --stdin are mutually exclusive", 2);
12908
+ }
12909
+ if (options.file !== undefined) {
12910
+ const filePath = path12.resolve(options.file);
12911
+ if (!fs8.existsSync(filePath)) {
12912
+ throw new Error(`persist payload file not found: ${filePath}`);
12913
+ }
12914
+ return fs8.readFileSync(filePath, "utf8");
12915
+ }
12916
+ return fs8.readFileSync(0, "utf8");
12917
+ }
12918
+ function validatePersistPayload(kind, payload) {
12919
+ let gate2;
12920
+ if (kind === "status")
12921
+ gate2 = validateStatusV2(payload);
12922
+ else if (kind === "snapshot")
12923
+ gate2 = validateWorkflowSnapshot(payload);
12924
+ else if (kind === "residuals")
12925
+ gate2 = validateProjectRegister(payload);
12926
+ else if (kind === "review")
12927
+ gate2 = validateMstarReviewV1(payload);
12928
+ else
12929
+ return;
12930
+ if (gate2.ok)
12931
+ return;
12932
+ const detail = gate2.violations.map((v) => `[${v.severity}] ${v.code}: ${v.message}`).join("; ");
12933
+ throw new Error(`refusing to persist invalid ${kind} document: ${detail}`);
12934
+ }
12935
+ persistCommand.argument("<kind>", "status | snapshot | residuals | review | json").option("--key <key>", 'Stable key inside the kind (status: "root"; json: absolute file path)').option("--file <path>", "Payload JSON file (default: read stdin)").option("--stdin", "Read the payload JSON from stdin").option("--store <module>", "Store module path (filesystem only; overrides MSTAR_STORE_MODULE)").option("--schema <id>", "Optional schema id stored on the artifact doc (e.g. mstar.review/v1); stored only by store modules that persist it \u2014 the default FsStore rejects it (exit 1)").action(async (kind, options) => {
12936
+ try {
12937
+ const parsedKind = parsePersistKind(kind);
12938
+ if (options.key === undefined) {
12939
+ throw new SddScriptError("usage: persist <kind> --key <key> [--file <path>|--stdin] [--store <module>] [--schema <id>]", 2);
12940
+ }
12941
+ const raw = readPersistPayload(options);
12942
+ let payload;
12943
+ try {
12944
+ payload = JSON.parse(raw);
12945
+ } catch (error) {
12946
+ throw new Error(`persist payload is not valid JSON: ${error.message}`);
12947
+ }
12948
+ validatePersistPayload(parsedKind, payload);
12949
+ await resolvePersistStore(options.store);
12950
+ await getArtifactStore().put({
12951
+ kind: parsedKind,
12952
+ key: options.key,
12953
+ payload,
12954
+ ...options.schema !== undefined ? { schema: options.schema } : {}
12955
+ });
12956
+ console.log(import_picocolors2.default.green(`persist ${parsedKind}/${options.key}: OK`));
12957
+ } catch (error) {
12958
+ failScript(error, "persist");
12959
+ }
12960
+ });
12961
+ persistCommand.command("get").description("Print the stored payload JSON for <kind>/<key>, or exit 1 when absent").argument("<kind>", "status | snapshot | residuals | review | json").option("--validate", "Run the kind's validator on the fetched payload (notes on stderr; invalid \u2192 exit 1)").action(async (kind, options, command) => {
12962
+ try {
12963
+ const parsedKind = parsePersistKind(kind);
12964
+ const parentOpts = command.parent?.opts() ?? {};
12965
+ const key = typeof parentOpts.key === "string" ? parentOpts.key : undefined;
12966
+ const store = typeof parentOpts.store === "string" ? parentOpts.store : undefined;
12967
+ if (key === undefined) {
12968
+ throw new SddScriptError("usage: persist get <kind> --key <key> [--validate] [--store <module>]", 2);
12969
+ }
12970
+ await resolvePersistStore(store);
12971
+ const payload = await getArtifactStore().get({ kind: parsedKind, key });
12972
+ if (payload === undefined) {
12973
+ throw new Error(`persist get ${parsedKind}/${key}: no stored document`);
12974
+ }
12975
+ if (options.validate === true) {
12976
+ validatePersistPayload(parsedKind, payload);
12977
+ console.error(parsedKind === "json" ? "json: parse-only" : "validation: ok");
12978
+ }
12979
+ console.log(JSON.stringify(payload, null, 2));
12980
+ } catch (error) {
12981
+ failScript(error, "persist get");
12982
+ }
12983
+ });
12984
+ persistCommand.command("list").description("Print the stored keys for <kind>, one per line, ascending, no header (json is not listable)").argument("<kind>", "status | snapshot | residuals | review | json").action(async (kind, _options, command) => {
12985
+ try {
12986
+ const parsedKind = parsePersistKind(kind);
12987
+ if (parsedKind === "json") {
12988
+ throw new SddScriptError("ArtifactStore json keys are absolute paths and cannot be listed", 2);
12989
+ }
12990
+ const parentOpts = command.parent?.opts() ?? {};
12991
+ const store = typeof parentOpts.store === "string" ? parentOpts.store : undefined;
12992
+ await resolvePersistStore(store);
12993
+ const artifactStore = getArtifactStore();
12994
+ if (typeof artifactStore.list !== "function") {
12995
+ throw new SddScriptError("store does not support list", 2);
12996
+ }
12997
+ const refs = await artifactStore.list(parsedKind);
12998
+ for (const key of refs.map((ref) => ref.key).sort()) {
12999
+ console.log(key);
13000
+ }
13001
+ } catch (error) {
13002
+ failScript(error, "persist list");
13003
+ }
13004
+ });
13005
+ persistCommand.command("delete").description("Delete the stored document for <kind>/<key> (idempotent: absent is a no-op; no prompt)").argument("<kind>", "status | snapshot | residuals | review | json").action(async (kind, _options, command) => {
13006
+ try {
13007
+ const parsedKind = parsePersistKind(kind);
13008
+ const parentOpts = command.parent?.opts() ?? {};
13009
+ const key = typeof parentOpts.key === "string" ? parentOpts.key : undefined;
13010
+ const store = typeof parentOpts.store === "string" ? parentOpts.store : undefined;
13011
+ if (key === undefined) {
13012
+ throw new SddScriptError("usage: persist delete <kind> --key <key> [--store <module>]", 2);
13013
+ }
13014
+ await resolvePersistStore(store);
13015
+ const artifactStore = getArtifactStore();
13016
+ if (typeof artifactStore.delete !== "function") {
13017
+ throw new SddScriptError("store does not support delete", 2);
13018
+ }
13019
+ await artifactStore.delete({ kind: parsedKind, key });
13020
+ console.log(`deleted ${parsedKind}/${key}`);
13021
+ } catch (error) {
13022
+ failScript(error, "persist delete");
13023
+ }
13024
+ });
12390
13025
  var leaseCommand = program2.command("lease").description("execution_lease / integration_merge_lease checks (engine-backed)");
12391
13026
  function resolveLeaseHarnessDir(harnessArg) {
12392
13027
  if (harnessArg)
@@ -13098,7 +13733,7 @@ function resolveAuditShortSha(cwd, override) {
13098
13733
  if (override !== undefined && override !== "")
13099
13734
  return override;
13100
13735
  try {
13101
- const out = execFileSync9("git", ["rev-parse", "--short", "HEAD"], {
13736
+ const out = execFileSync10("git", ["rev-parse", "--short", "HEAD"], {
13102
13737
  cwd,
13103
13738
  encoding: "utf8",
13104
13739
  stdio: ["ignore", "pipe", "ignore"]
@@ -13154,6 +13789,7 @@ auditCommand.command("promote").description("Promote selected audit plans into t
13154
13789
  throw new Error(`audit dir not found: ${outDir}`);
13155
13790
  }
13156
13791
  const harnessDir = resolveLeaseHarnessDir(options.harness);
13792
+ setArtifactStore(createFsStore(harnessDir));
13157
13793
  const result = await promoteAuditPlans(outDir, selected, {
13158
13794
  harnessDir,
13159
13795
  ...options.workflow !== undefined ? { workflowId: options.workflow } : {}
@@ -13167,7 +13803,7 @@ auditCommand.command("promote").description("Promote selected audit plans into t
13167
13803
  function listTrackedFiles(root) {
13168
13804
  let out;
13169
13805
  try {
13170
- out = execFileSync9("git", ["ls-files", "-z", "--", "."], {
13806
+ out = execFileSync10("git", ["ls-files", "-z", "--", "."], {
13171
13807
  cwd: root,
13172
13808
  encoding: "utf8",
13173
13809
  stdio: ["ignore", "pipe", "ignore"]
@@ -13515,17 +14151,17 @@ prReviewCommand.command("validate-report").description("Validate a saved local P
13515
14151
  }
13516
14152
  });
13517
14153
  function gitSync(args, cwd) {
13518
- return execFileSync9("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
14154
+ return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
13519
14155
  }
13520
14156
  function ghSync(args, input) {
13521
- return execFileSync9("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...input !== undefined ? { input } : {} }).trim();
14157
+ return execFileSync10("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...input !== undefined ? { input } : {} }).trim();
13522
14158
  }
13523
14159
  function cliToday() {
13524
14160
  const iso = new Date().toISOString();
13525
14161
  return iso.slice(0, 10);
13526
14162
  }
13527
14163
  function listLocalBranches(cwd) {
13528
- const out = execFileSync9("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads"], {
14164
+ const out = execFileSync10("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads"], {
13529
14165
  cwd,
13530
14166
  encoding: "utf8",
13531
14167
  stdio: ["ignore", "pipe", "pipe"]
@@ -13534,7 +14170,7 @@ function listLocalBranches(cwd) {
13534
14170
  }
13535
14171
  function refResolves(ref, cwd) {
13536
14172
  try {
13537
- execFileSync9("git", ["rev-parse", "--verify", "--quiet", ref], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
14173
+ execFileSync10("git", ["rev-parse", "--verify", "--quiet", ref], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
13538
14174
  return true;
13539
14175
  } catch {
13540
14176
  return false;
@@ -13547,7 +14183,7 @@ function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
13547
14183
  return false;
13548
14184
  let untracked = "";
13549
14185
  try {
13550
- untracked = execFileSync9("git", ["ls-files", "--others", "--exclude-standard"], {
14186
+ untracked = execFileSync10("git", ["ls-files", "--others", "--exclude-standard"], {
13551
14187
  cwd: worktreePath,
13552
14188
  encoding: "utf8",
13553
14189
  stdio: ["ignore", "pipe", "ignore"]
@@ -13565,7 +14201,7 @@ function probeChangesetEmpty(diffCmdArgs, cwd, worktreePath) {
13565
14201
  }
13566
14202
  function gitIf(args, cwd) {
13567
14203
  try {
13568
- return execFileSync9("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
14204
+ return execFileSync10("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
13569
14205
  } catch {
13570
14206
  return "";
13571
14207
  }
@@ -13690,7 +14326,7 @@ function foldIntoBody(body, entries) {
13690
14326
  `);
13691
14327
  }
13692
14328
  function ghApi(apiPath, payload) {
13693
- return execFileSync9("gh", ["api", "--method", "POST", apiPath, "--input", "-"], {
14329
+ return execFileSync10("gh", ["api", "--method", "POST", apiPath, "--input", "-"], {
13694
14330
  encoding: "utf8",
13695
14331
  stdio: ["ignore", "pipe", "pipe"],
13696
14332
  input: payload
@@ -13744,7 +14380,7 @@ prReviewCommand.command("worktree-setup").description("Create the isolated revie
13744
14380
  let headSpec = "";
13745
14381
  if (mode === "pr") {
13746
14382
  prNumber = Number(options.pr);
13747
- baseRef = execFileSync9("gh", ["pr", "view", String(prNumber), "--json", "baseRefName", "--jq", ".baseRefName"], {
14383
+ baseRef = execFileSync10("gh", ["pr", "view", String(prNumber), "--json", "baseRefName", "--jq", ".baseRefName"], {
13748
14384
  encoding: "utf8",
13749
14385
  stdio: ["ignore", "pipe", "pipe"]
13750
14386
  }).trim();