@mstar-harness/engine 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.js CHANGED
@@ -170,13 +170,13 @@ function isAtOrBelow(dir, root) {
170
170
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
171
171
  }
172
172
  // src/path.ts
173
- 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";
173
+ 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";
174
174
  import { execFileSync } from "node:child_process";
175
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve6 } from "node:path";
175
+ import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
176
176
 
177
177
  // src/project.ts
178
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "node:fs";
179
- import { join as join7, resolve as resolve5 } from "node:path";
178
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
179
+ import { basename as basename2, join as join8, resolve as resolve6 } from "node:path";
180
180
 
181
181
  // src/iteration.ts
182
182
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
@@ -757,9 +757,167 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
757
757
  }
758
758
  }
759
759
 
760
+ // src/store.ts
761
+ import { existsSync as existsSync3, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
762
+ import { isAbsolute as isAbsolute3, join as join5, resolve as resolve4 } from "node:path";
763
+ var PLAN_SHAPED_KEY_RE = /^[0-9]{8}-[a-z0-9-]+$/;
764
+ function resolveArtifactPath(harnessRoot, ref) {
765
+ const { kind, key } = ref;
766
+ if (kind === "json") {
767
+ if (!isAbsolute3(key)) {
768
+ throw new Error(`ArtifactStore json key must be an absolute path — got ${JSON.stringify(key)}`);
769
+ }
770
+ if (key.split(/[\\/]+/).includes("..")) {
771
+ throw new Error(`ArtifactStore json key must not contain ".." segments — got ${JSON.stringify(key)}`);
772
+ }
773
+ return key;
774
+ }
775
+ assertSafePathComponent(key, "ArtifactStore key");
776
+ if (kind === "status") {
777
+ if (key !== "root") {
778
+ throw new Error(`ArtifactStore status key must be "root" — got ${JSON.stringify(key)}`);
779
+ }
780
+ return join5(harnessRoot, "status.json");
781
+ }
782
+ if (kind === "snapshot") {
783
+ return join5(resolveWorkflowDir(harnessRoot, { harnessDir: harnessRoot }), key, "snapshot.json");
784
+ }
785
+ if (kind === "residuals") {
786
+ return join5(resolveProjectDir(harnessRoot, { harnessDir: harnessRoot }), key, "residuals.json");
787
+ }
788
+ if (PLAN_SHAPED_KEY_RE.test(key)) {
789
+ return join5(harnessRoot, "sdd", key, "review", "report.json");
790
+ }
791
+ return join5(harnessRoot, "sdd", "_reviews", `${key}.json`);
792
+ }
793
+ function listDirNames(dir) {
794
+ return readDirEntries(dir).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
795
+ }
796
+ function listJsonKeys(dir) {
797
+ return readDirEntries(dir).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length));
798
+ }
799
+ function readDirEntries(dir) {
800
+ try {
801
+ return readdirSync2(dir, { withFileTypes: true });
802
+ } catch (error) {
803
+ const code = error.code;
804
+ if (code === "ENOENT" || code === "ENOTDIR")
805
+ return [];
806
+ throw error;
807
+ }
808
+ }
809
+ function tryResolveGetPath(root, kind, key) {
810
+ try {
811
+ return resolveArtifactPath(root, { kind, key });
812
+ } catch {
813
+ return;
814
+ }
815
+ }
816
+ function createFsStore(harnessRoot) {
817
+ const root = resolve4(harnessRoot);
818
+ return {
819
+ root,
820
+ async put(doc) {
821
+ if (doc.schema !== undefined) {
822
+ throw new Error("FsStore does not persist schema ids — omit --schema or inject a store module that persists it");
823
+ }
824
+ writeJson(resolveArtifactPath(root, doc), doc.payload);
825
+ },
826
+ async get(ref) {
827
+ const filePath = resolveArtifactPath(root, ref);
828
+ if (!existsSync3(filePath))
829
+ return;
830
+ return readJson(filePath);
831
+ },
832
+ async delete(ref) {
833
+ const filePath = resolveArtifactPath(root, ref);
834
+ if (existsSync3(filePath))
835
+ unlinkSync3(filePath);
836
+ },
837
+ async list(kind) {
838
+ if (kind === "json") {
839
+ throw new Error("ArtifactStore json keys are absolute paths and cannot be listed");
840
+ }
841
+ const keys = [];
842
+ if (kind === "status") {
843
+ if (existsSync3(resolveArtifactPath(root, { kind, key: "root" })))
844
+ keys.push("root");
845
+ } else if (kind === "snapshot" || kind === "residuals") {
846
+ const baseDir = kind === "snapshot" ? resolveWorkflowDir(root, { harnessDir: root }) : resolveProjectDir(root, { harnessDir: root });
847
+ for (const name of listDirNames(baseDir)) {
848
+ const getPath = tryResolveGetPath(root, kind, name);
849
+ if (getPath !== undefined && existsSync3(getPath))
850
+ keys.push(name);
851
+ }
852
+ } else {
853
+ const sddDir = join5(root, "sdd");
854
+ for (const key of listJsonKeys(join5(sddDir, "_reviews"))) {
855
+ if (!PLAN_SHAPED_KEY_RE.test(key) && tryResolveGetPath(root, kind, key) !== undefined) {
856
+ keys.push(key);
857
+ }
858
+ }
859
+ for (const name of listDirNames(sddDir)) {
860
+ if (PLAN_SHAPED_KEY_RE.test(name)) {
861
+ const getPath = tryResolveGetPath(root, kind, name);
862
+ if (getPath !== undefined && existsSync3(getPath))
863
+ keys.push(name);
864
+ }
865
+ }
866
+ }
867
+ return keys.sort().map((key) => ({ kind, key }));
868
+ }
869
+ };
870
+ }
871
+ var injectedStore;
872
+ function setArtifactStore(store) {
873
+ injectedStore = store;
874
+ }
875
+ function getArtifactStore() {
876
+ if (injectedStore !== undefined)
877
+ return injectedStore;
878
+ const root = resolveHarnessDir(process.cwd());
879
+ if (root === null) {
880
+ throw new Error(`harness dir not found from ${resolve4(process.cwd())} — cannot create the default FsStore (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
881
+ }
882
+ return createFsStore(root);
883
+ }
884
+ function assertFsStorePath(store, ref, expectedPath) {
885
+ const root = store.root;
886
+ if (typeof root !== "string")
887
+ return;
888
+ const storePath = resolveArtifactPath(root, ref);
889
+ const expected = resolve4(expectedPath);
890
+ if (storePath !== expected) {
891
+ 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)} — call setArtifactStore(createFsStore(<root>)) first when the write target differs from the active store's root`);
892
+ }
893
+ }
894
+ var URI_SCHEME_RE = /^[A-Za-z][A-Za-z0-9+.-]*:/;
895
+ function isArtifactStore(value) {
896
+ return typeof value === "object" && value !== null && typeof value.put === "function" && typeof value.get === "function";
897
+ }
898
+ async function loadStoreModule(modulePath) {
899
+ if (modulePath === "") {
900
+ throw new Error("loadStoreModule: module path must not be empty");
901
+ }
902
+ if (URI_SCHEME_RE.test(modulePath)) {
903
+ throw new Error(`loadStoreModule: only filesystem paths are allowed — got ${JSON.stringify(modulePath)} (URI schemes such as http:/https:/file: are rejected)`);
904
+ }
905
+ const resolved = resolve4(modulePath);
906
+ if (!existsSync3(resolved)) {
907
+ throw new Error(`loadStoreModule: module file not found — ${JSON.stringify(resolved)}`);
908
+ }
909
+ const mod = await import(resolved);
910
+ const candidate = mod.createArtifactStore ?? mod.default ?? mod;
911
+ const store = typeof candidate === "function" ? await candidate() : candidate;
912
+ if (!isArtifactStore(store)) {
913
+ throw new Error(`loadStoreModule: module ${JSON.stringify(resolved)} does not export an ArtifactStore — expected a createArtifactStore named export, a default factory, or a default object with put() and get() functions`);
914
+ }
915
+ return store;
916
+ }
917
+
760
918
  // src/status.ts
761
- import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "node:fs";
762
- import { dirname as dirname4, join as join6, resolve as resolve4, sep } from "node:path";
919
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync } from "node:fs";
920
+ import { dirname as dirname4, join as join7, resolve as resolve5, sep } from "node:path";
763
921
 
764
922
  // src/dispatch.ts
765
923
  var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"';
@@ -965,8 +1123,10 @@ function composeDispatchGate(text, opts = {}) {
965
1123
  const violations = [];
966
1124
  const writable = opts.writable !== false;
967
1125
  violations.push(...validateAssignmentFields(text, { writable }).violations);
968
- const agent = opts.agent ?? "";
969
- violations.push(...antiRecursionPrecheck(agent, parseAssignmentFields(text).executeAs ?? "").violations);
1126
+ const caller = opts.caller ?? "";
1127
+ if (caller.trim() !== "" || opts.callerRequired === true) {
1128
+ violations.push(...antiRecursionPrecheck(caller, parseAssignmentFields(text).executeAs ?? "").violations);
1129
+ }
970
1130
  if (writable) {
971
1131
  const forms = parseAssignmentBranchForms(text);
972
1132
  const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
@@ -989,7 +1149,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
989
1149
  return {
990
1150
  ok: false,
991
1151
  violations: [
992
- violation3("critical", "dispatch.anti-recursion.empty-binding", `empty host role binding — the host cannot report which agent is calling, so anti-recursion cannot be proven (a dispatch could silently recurse)`, "set the host role-binding field (omp task entry `agent` / opencode `subagent` / cursor `subagent_type` / dsh `dispatchBinding`) before dispatching")
1152
+ violation3("critical", "dispatch.anti-recursion.empty-binding", `empty caller role binding — the host cannot report which agent is calling, so anti-recursion cannot be proven (a dispatch could silently recurse)`, "declare the dispatching agent's own role binding (dsh Config `dispatchBinding`) before dispatching")
993
1153
  ]
994
1154
  };
995
1155
  }
@@ -1006,7 +1166,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
1006
1166
 
1007
1167
  // src/workflow.ts
1008
1168
  import { mkdirSync as mkdirSync3 } from "node:fs";
1009
- import { join as join5 } from "node:path";
1169
+ import { join as join6 } from "node:path";
1010
1170
  var WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
1011
1171
  var WORKFLOW_LIFECYCLE_STATUSES = ["running", "paused", "completed", "failed", "stopped"];
1012
1172
  var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
@@ -1123,10 +1283,12 @@ async function writeWorkflowSnapshot(snapshot, dir) {
1123
1283
  const detail = gate.violations.map((v) => v.message).join("; ");
1124
1284
  throw new Error(`refusing to write invalid workflow snapshot: ${detail}`);
1125
1285
  }
1126
- const snapshotPath = join5(dir, WORKFLOW_SNAPSHOT_FILE);
1286
+ const snapshotPath = join6(dir, WORKFLOW_SNAPSHOT_FILE);
1287
+ const store = getArtifactStore();
1288
+ assertFsStorePath(store, { kind: "snapshot", key: snapshot.id }, snapshotPath);
1127
1289
  mkdirSync3(dir, { recursive: true });
1128
- await withStatusWriteLock(snapshotPath, () => {
1129
- writeJson(snapshotPath, snapshot);
1290
+ await withStatusWriteLock(snapshotPath, async () => {
1291
+ await store.put({ kind: "snapshot", key: snapshot.id, payload: snapshot });
1130
1292
  });
1131
1293
  }
1132
1294
 
@@ -1293,7 +1455,7 @@ function validateStatusV2(docOrPath, opts = {}) {
1293
1455
  if (typeof docOrPath === "string") {
1294
1456
  try {
1295
1457
  doc = readJson(docOrPath);
1296
- harnessDir = dirname4(resolve4(docOrPath));
1458
+ harnessDir = dirname4(resolve5(docOrPath));
1297
1459
  } catch (error) {
1298
1460
  return {
1299
1461
  ok: false,
@@ -1360,8 +1522,8 @@ function validateStatusV2(docOrPath, opts = {}) {
1360
1522
  for (const entry of doc.workflows) {
1361
1523
  if (!isPlainObject4(entry) || typeof entry.dir !== "string")
1362
1524
  continue;
1363
- const relSnapshot = join6(entry.dir, WORKFLOW_SNAPSHOT_FILE);
1364
- const snapshotPath = join6(harnessDir, relSnapshot);
1525
+ const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
1526
+ const snapshotPath = join7(harnessDir, relSnapshot);
1365
1527
  const label = typeof entry.id === "string" ? entry.id : relSnapshot;
1366
1528
  let physical;
1367
1529
  try {
@@ -1395,8 +1557,10 @@ function validateStatusV2(docOrPath, opts = {}) {
1395
1557
  return { ok: violations.length === 0, violations };
1396
1558
  }
1397
1559
  var validateStatus = validateStatusV2;
1398
- function registerWorkflowEntryLocked(statusPath, entry) {
1560
+ async function registerWorkflowEntryLocked(statusPath, entry) {
1399
1561
  const harnessDir = dirname4(statusPath);
1562
+ const store = getArtifactStore();
1563
+ assertFsStorePath(store, { kind: "status", key: "root" }, statusPath);
1400
1564
  const current = readJson(statusPath);
1401
1565
  const fresh = Object.keys(current).length === 0;
1402
1566
  const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
@@ -1414,7 +1578,7 @@ function registerWorkflowEntryLocked(statusPath, entry) {
1414
1578
  if (!gate.ok) {
1415
1579
  throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
1416
1580
  }
1417
- writeJson(statusPath, doc);
1581
+ await store.put({ kind: "status", key: "root", payload: doc });
1418
1582
  return doc;
1419
1583
  }
1420
1584
  async function registerWorkflow(root, entry) {
@@ -1422,16 +1586,18 @@ async function registerWorkflow(root, entry) {
1422
1586
  if (!entryGate.ok) {
1423
1587
  throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
1424
1588
  }
1425
- const statusPath = resolve4(root);
1589
+ const statusPath = resolve5(root);
1426
1590
  return withStatusWriteLock(statusPath, () => registerWorkflowEntryLocked(statusPath, entry));
1427
1591
  }
1428
1592
  async function unregisterWorkflow(root, id) {
1429
1593
  if (typeof id !== "string" || id.trim() === "") {
1430
1594
  throw new Error("refusing to unregister workflow: id must be a non-empty string");
1431
1595
  }
1432
- const statusPath = resolve4(root);
1596
+ const statusPath = resolve5(root);
1597
+ const store = getArtifactStore();
1598
+ assertFsStorePath(store, { kind: "status", key: "root" }, statusPath);
1433
1599
  const harnessDir = dirname4(statusPath);
1434
- return withStatusWriteLock(statusPath, () => {
1600
+ return withStatusWriteLock(statusPath, async () => {
1435
1601
  const current = readJson(statusPath);
1436
1602
  if (Object.keys(current).length === 0) {
1437
1603
  return { version: 2, updated_at: todayString(), workflows: [] };
@@ -1450,25 +1616,25 @@ async function unregisterWorkflow(root, id) {
1450
1616
  if (!gate.ok) {
1451
1617
  throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
1452
1618
  }
1453
- writeJson(statusPath, doc);
1619
+ await store.put({ kind: "status", key: "root", payload: doc });
1454
1620
  return doc;
1455
1621
  });
1456
1622
  }
1457
1623
  function resolveCompassEnforcement(harnessDir) {
1458
1624
  const iterationsDir = resolveIterationDir(harnessDir);
1459
- if (!existsSync3(iterationsDir))
1625
+ if (!existsSync4(iterationsDir))
1460
1626
  return { hard: false, source: "none" };
1461
1627
  let entries;
1462
1628
  try {
1463
- entries = readdirSync2(iterationsDir, { withFileTypes: true });
1629
+ entries = readdirSync3(iterationsDir, { withFileTypes: true });
1464
1630
  } catch {
1465
1631
  return { hard: false, source: "none" };
1466
1632
  }
1467
1633
  for (const entry of entries) {
1468
1634
  if (!entry.isDirectory())
1469
1635
  continue;
1470
- const compassPath = join6(iterationsDir, entry.name, "delivery-compass.md");
1471
- if (!existsSync3(compassPath))
1636
+ const compassPath = join7(iterationsDir, entry.name, "delivery-compass.md");
1637
+ if (!existsSync4(compassPath))
1472
1638
  continue;
1473
1639
  let content;
1474
1640
  try {
@@ -1487,7 +1653,7 @@ function resolveCompassEnforcement(harnessDir) {
1487
1653
  return { hard: false, source: "none" };
1488
1654
  }
1489
1655
  function resolveMstarcEnforcement(harnessDir) {
1490
- const dir = resolve4(harnessDir);
1656
+ const dir = resolve5(harnessDir);
1491
1657
  const rc = loadMstarc(dir, dirname4(dir));
1492
1658
  const value = rc?.config.enforcement;
1493
1659
  if (value === "hard")
@@ -1639,9 +1805,12 @@ async function appendProjectRegisterEntries(opts) {
1639
1805
  if (opts.entries.length === 0) {
1640
1806
  throw new Error("refusing to append residual entries: entries must not be empty");
1641
1807
  }
1642
- const registerPath = resolve5(join7(opts.projectDir, PROJECT_REGISTER_FILE));
1808
+ const registerPath = resolve6(join8(opts.projectDir, PROJECT_REGISTER_FILE));
1809
+ const projectKey = basename2(resolve6(opts.projectDir));
1810
+ const store = getArtifactStore();
1811
+ assertFsStorePath(store, { kind: "residuals", key: projectKey }, registerPath);
1643
1812
  mkdirSync4(opts.projectDir, { recursive: true });
1644
- return withStatusWriteLock(registerPath, () => {
1813
+ return withStatusWriteLock(registerPath, async () => {
1645
1814
  const doc = readJson(registerPath);
1646
1815
  const entriesMap = doc.entries ?? {};
1647
1816
  let key = opts.basePlanKey;
@@ -1676,14 +1845,17 @@ async function appendProjectRegisterEntries(opts) {
1676
1845
  if (!gate.ok) {
1677
1846
  throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
1678
1847
  }
1679
- writeJson(registerPath, register);
1848
+ await store.put({ kind: "residuals", key: projectKey, payload: register });
1680
1849
  return { ok: true, key };
1681
1850
  });
1682
1851
  }
1683
1852
  async function closeProjectRegisterEntry(opts) {
1684
- const registerPath = resolve5(join7(opts.projectDir, PROJECT_REGISTER_FILE));
1853
+ const registerPath = resolve6(join8(opts.projectDir, PROJECT_REGISTER_FILE));
1854
+ const projectKey = basename2(resolve6(opts.projectDir));
1855
+ const store = getArtifactStore();
1856
+ assertFsStorePath(store, { kind: "residuals", key: projectKey }, registerPath);
1685
1857
  mkdirSync4(opts.projectDir, { recursive: true });
1686
- return withStatusWriteLock(registerPath, () => {
1858
+ return withStatusWriteLock(registerPath, async () => {
1687
1859
  const doc = readJson(registerPath);
1688
1860
  const planEntries = doc.entries?.[opts.planKey];
1689
1861
  if (!Array.isArray(planEntries)) {
@@ -1703,7 +1875,7 @@ async function closeProjectRegisterEntry(opts) {
1703
1875
  if (!gate.ok) {
1704
1876
  throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
1705
1877
  }
1706
- writeJson(registerPath, register);
1878
+ await store.put({ kind: "residuals", key: projectKey, payload: register });
1707
1879
  return { ok: true };
1708
1880
  });
1709
1881
  }
@@ -1756,15 +1928,15 @@ function techDebtRollup(projectDir) {
1756
1928
  const items = [];
1757
1929
  let entries;
1758
1930
  try {
1759
- entries = readdirSync3(projectDir, { withFileTypes: true });
1931
+ entries = readdirSync4(projectDir, { withFileTypes: true });
1760
1932
  } catch {
1761
1933
  entries = [];
1762
1934
  }
1763
1935
  for (const project of entries) {
1764
1936
  if (!project.isDirectory())
1765
1937
  continue;
1766
- const registerPath = join7(projectDir, project.name, PROJECT_REGISTER_FILE);
1767
- if (!existsSync4(registerPath))
1938
+ const registerPath = join8(projectDir, project.name, PROJECT_REGISTER_FILE);
1939
+ if (!existsSync5(registerPath))
1768
1940
  continue;
1769
1941
  let register;
1770
1942
  try {
@@ -1800,10 +1972,10 @@ function techDebtRollup(projectDir) {
1800
1972
  return { computed, stored, checks, overall };
1801
1973
  }
1802
1974
  function listProjectReferenceFiles(projectDir) {
1803
- const root = join7(projectDir, PROJECT_REFERENCES_DIR);
1975
+ const root = join8(projectDir, PROJECT_REFERENCES_DIR);
1804
1976
  let entries;
1805
1977
  try {
1806
- entries = readdirSync3(root, { withFileTypes: true });
1978
+ entries = readdirSync4(root, { withFileTypes: true });
1807
1979
  } catch {
1808
1980
  return [];
1809
1981
  }
@@ -1816,7 +1988,7 @@ function listProjectReferenceFiles(projectDir) {
1816
1988
  } else if (entry.isDirectory()) {
1817
1989
  let nested;
1818
1990
  try {
1819
- nested = readdirSync3(join7(root, entry.name), { withFileTypes: true });
1991
+ nested = readdirSync4(join8(root, entry.name), { withFileTypes: true });
1820
1992
  } catch {
1821
1993
  continue;
1822
1994
  }
@@ -1831,19 +2003,19 @@ function listProjectReferenceFiles(projectDir) {
1831
2003
 
1832
2004
  // src/path.ts
1833
2005
  function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
1834
- const start = resolve6(startDir);
2006
+ const start = resolve7(startDir);
1835
2007
  const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
1836
2008
  if (explicit)
1837
- return resolve6(start, explicit);
1838
- const boundary = resolve6(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
2009
+ return resolve7(start, explicit);
2010
+ const boundary = resolve7(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
1839
2011
  const rc = loadMstarc(start, boundary);
1840
2012
  if (rc !== null && rc.config.harnessDir)
1841
- return resolve6(rc.dir, rc.config.harnessDir);
2013
+ return resolve7(rc.dir, rc.config.harnessDir);
1842
2014
  let dir = start;
1843
2015
  for (;; ) {
1844
2016
  if (!isAtOrBelow2(dir, boundary))
1845
2017
  return null;
1846
- for (const candidate of [join8(dir, ".mstar"), join8(dir, ".agents"), join8(dir, ".plans"), join8(dir, "plans")]) {
2018
+ for (const candidate of [join9(dir, ".mstar"), join9(dir, ".agents"), join9(dir, ".plans"), join9(dir, "plans")]) {
1847
2019
  if (isDirectory(candidate))
1848
2020
  return candidate;
1849
2021
  }
@@ -1869,19 +2041,19 @@ function defaultWorkspaceRoot(startDir) {
1869
2041
  if (segment && segment !== ".")
1870
2042
  boundary = dirname5(boundary);
1871
2043
  }
1872
- return resolve6(boundary);
2044
+ return resolve7(boundary);
1873
2045
  } catch {}
1874
2046
  return startDir;
1875
2047
  }
1876
2048
  function isAtOrBelow2(dir, root) {
1877
2049
  const rel = relative2(root, dir);
1878
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
2050
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
1879
2051
  }
1880
2052
  function mstarcDirOverride(harnessDir, key) {
1881
- const dir = resolve6(harnessDir);
2053
+ const dir = resolve7(harnessDir);
1882
2054
  const rc = loadMstarc(dir, dirname5(dir));
1883
2055
  const declared = rc?.config[key];
1884
- return declared ? resolve6(rc.dir, declared) : null;
2056
+ return declared ? resolve7(rc.dir, declared) : null;
1885
2057
  }
1886
2058
  function resolveSpecsDir(harnessDir, opts = {}) {
1887
2059
  const declared = mstarcDirOverride(harnessDir, "specsDir");
@@ -1890,20 +2062,20 @@ function resolveSpecsDir(harnessDir, opts = {}) {
1890
2062
  mkdirSync5(declared, { recursive: true });
1891
2063
  return declared;
1892
2064
  }
1893
- const harness = resolve6(harnessDir);
2065
+ const harness = resolve7(harnessDir);
1894
2066
  const repoRoot = dirname5(harness);
1895
2067
  const candidates = [
1896
- join8(harness, "specs"),
1897
- join8(repoRoot, "docs", "specs"),
1898
- join8(repoRoot, "specs"),
1899
- join8(harness, "designs"),
1900
- join8(repoRoot, "designs")
2068
+ join9(harness, "specs"),
2069
+ join9(repoRoot, "docs", "specs"),
2070
+ join9(repoRoot, "specs"),
2071
+ join9(harness, "designs"),
2072
+ join9(repoRoot, "designs")
1901
2073
  ];
1902
2074
  for (const candidate of candidates) {
1903
2075
  if (isDirectory(candidate) && hasFiles(candidate))
1904
2076
  return candidate;
1905
2077
  }
1906
- const fallback = join8(harness, "specs");
2078
+ const fallback = join9(harness, "specs");
1907
2079
  if (opts.create !== false)
1908
2080
  mkdirSync5(fallback, { recursive: true });
1909
2081
  return fallback;
@@ -1912,11 +2084,11 @@ function resolvePlanDir(harnessDir) {
1912
2084
  const declared = mstarcDirOverride(harnessDir, "planDir");
1913
2085
  if (declared !== null)
1914
2086
  return declared;
1915
- const dir = resolve6(harnessDir);
1916
- const name = basename2(dir);
2087
+ const dir = resolve7(harnessDir);
2088
+ const name = basename3(dir);
1917
2089
  if (name === ".plans" || name === "plans")
1918
2090
  return dir;
1919
- return join8(dir, "plans");
2091
+ return join9(dir, "plans");
1920
2092
  }
1921
2093
  function assertSafePathComponent(value, what) {
1922
2094
  if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
@@ -1925,30 +2097,30 @@ function assertSafePathComponent(value, what) {
1925
2097
  }
1926
2098
  function resolveSddDir(harnessDir, planId) {
1927
2099
  assertSafePathComponent(planId, "planId");
1928
- const base = resolve6(harnessDir);
2100
+ const base = resolve7(harnessDir);
1929
2101
  const declared = mstarcDirOverride(base, "sddDir");
1930
- const sddBase = declared !== null ? declared : join8(base, "sdd");
1931
- return join8(sddBase, planId);
2102
+ const sddBase = declared !== null ? declared : join9(base, "sdd");
2103
+ return join9(sddBase, planId);
1932
2104
  }
1933
2105
  function resolveIterationDir(harnessDir) {
1934
2106
  const declared = mstarcDirOverride(harnessDir, "iterationDir");
1935
2107
  if (declared !== null)
1936
2108
  return declared;
1937
- return join8(resolve6(harnessDir), "iterations");
2109
+ return join9(resolve7(harnessDir), "iterations");
1938
2110
  }
1939
2111
  function resolveKnowledgeDir(harnessDir) {
1940
2112
  const declared = mstarcDirOverride(harnessDir, "knowledgeDir");
1941
2113
  if (declared !== null)
1942
2114
  return declared;
1943
- return join8(resolve6(harnessDir), "knowledge");
2115
+ return join9(resolve7(harnessDir), "knowledge");
1944
2116
  }
1945
2117
  function resolveHarnessSubdir(startDir, opts, key, fallback) {
1946
2118
  const harness = resolveHarnessDir(startDir, opts);
1947
2119
  if (harness === null) {
1948
- throw new Error(`harness dir not found from ${resolve6(startDir)} — cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
2120
+ throw new Error(`harness dir not found from ${resolve7(startDir)} — cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
1949
2121
  }
1950
2122
  const declared = mstarcDirOverride(harness, key);
1951
- return declared !== null ? declared : join8(resolve6(harness), fallback);
2123
+ return declared !== null ? declared : join9(resolve7(harness), fallback);
1952
2124
  }
1953
2125
  function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
1954
2126
  return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
@@ -1963,13 +2135,13 @@ var EMPTY_STATUS_TEMPLATE = {
1963
2135
  };
1964
2136
  var SCAFFOLD_DIRS = ["plans", "iterations", "knowledge", "specs", "sdd"];
1965
2137
  function resolveScaffoldDirs(root) {
1966
- const start = resolve6(root);
1967
- const boundary = resolve6(start, defaultWorkspaceRoot(start));
2138
+ const start = resolve7(root);
2139
+ const boundary = resolve7(start, defaultWorkspaceRoot(start));
1968
2140
  const rc = loadMstarc(start, boundary);
1969
2141
  const explicit = process.env.MSTAR_HARNESS_DIR;
1970
- const harnessDir = explicit ? resolve6(start, explicit) : rc !== null && rc.config.harnessDir ? resolve6(rc.dir, rc.config.harnessDir) : join8(start, ".mstar");
2142
+ const harnessDir = explicit ? resolve7(start, explicit) : rc !== null && rc.config.harnessDir ? resolve7(rc.dir, rc.config.harnessDir) : join9(start, ".mstar");
1971
2143
  const declaredProjectDir = mstarcDirOverride(harnessDir, "projectDir");
1972
- const projectDir = declaredProjectDir !== null ? declaredProjectDir : join8(harnessDir, "projects");
2144
+ const projectDir = declaredProjectDir !== null ? declaredProjectDir : join9(harnessDir, "projects");
1973
2145
  return { harnessDir, projectDir };
1974
2146
  }
1975
2147
  var ROADMAP_TEMPLATE = `---
@@ -1991,18 +2163,18 @@ var EMPTY_REGISTER_TEMPLATE = {
1991
2163
  function scaffoldHarness(root) {
1992
2164
  const { harnessDir, projectDir } = resolveScaffoldDirs(root);
1993
2165
  for (const dir of SCAFFOLD_DIRS)
1994
- mkdirSync5(join8(harnessDir, dir), { recursive: true });
1995
- const statusPath = join8(harnessDir, "status.json");
2166
+ mkdirSync5(join9(harnessDir, dir), { recursive: true });
2167
+ const statusPath = join9(harnessDir, "status.json");
1996
2168
  if (Object.keys(readJson(statusPath)).length === 0)
1997
2169
  writeJson(statusPath, EMPTY_STATUS_TEMPLATE);
1998
- const defaultProjectDir = join8(projectDir, _DEFAULT_PROJECT);
2170
+ const defaultProjectDir = join9(projectDir, _DEFAULT_PROJECT);
1999
2171
  mkdirSync5(defaultProjectDir, { recursive: true });
2000
- const roadmapPath = join8(defaultProjectDir, PROJECT_ROADMAP_FILE);
2001
- if (!existsSync5(roadmapPath)) {
2172
+ const roadmapPath = join9(defaultProjectDir, PROJECT_ROADMAP_FILE);
2173
+ if (!existsSync6(roadmapPath)) {
2002
2174
  const created = new Date().toISOString().slice(0, 10);
2003
2175
  writeFileSync3(roadmapPath, ROADMAP_TEMPLATE.replace("{created_at}", created), "utf8");
2004
2176
  }
2005
- const registerPath = join8(defaultProjectDir, PROJECT_REGISTER_FILE);
2177
+ const registerPath = join9(defaultProjectDir, PROJECT_REGISTER_FILE);
2006
2178
  if (Object.keys(readJson(registerPath)).length === 0)
2007
2179
  writeJson(registerPath, EMPTY_REGISTER_TEMPLATE);
2008
2180
  return harnessDir;
@@ -2040,7 +2212,7 @@ function emitGitignoreSnippet(kind) {
2040
2212
  return `${GITIGNORE_SNIPPET}${GITIGNORE_SNIPPET_AGENTS}`;
2041
2213
  }
2042
2214
  function validateGitignore(root) {
2043
- const gitignorePath = join8(resolve6(root), ".gitignore");
2215
+ const gitignorePath = join9(resolve7(root), ".gitignore");
2044
2216
  const kind = detectHarnessKind(resolveHarnessDir(root));
2045
2217
  let content;
2046
2218
  try {
@@ -2088,7 +2260,7 @@ function validateGitignore(root) {
2088
2260
  function detectHarnessKind(harnessDir) {
2089
2261
  if (!harnessDir)
2090
2262
  return null;
2091
- const name = basename2(resolve6(harnessDir));
2263
+ const name = basename3(resolve7(harnessDir));
2092
2264
  if (name === ".mstar")
2093
2265
  return "mstar";
2094
2266
  if (name === ".agents")
@@ -2096,7 +2268,7 @@ function detectHarnessKind(harnessDir) {
2096
2268
  return null;
2097
2269
  }
2098
2270
  function assertPlanWritingPath(planPath, harnessDir) {
2099
- const planAbs = resolve6(planPath);
2271
+ const planAbs = resolve7(planPath);
2100
2272
  if (!harnessDir) {
2101
2273
  return {
2102
2274
  ok: false,
@@ -2108,7 +2280,7 @@ function assertPlanWritingPath(planPath, harnessDir) {
2108
2280
  }
2109
2281
  const planDir = resolvePlanDir(harnessDir);
2110
2282
  const rel = relative2(planDir, planAbs);
2111
- const inside = rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
2283
+ const inside = rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
2112
2284
  if (!inside) {
2113
2285
  return {
2114
2286
  ok: false,
@@ -2118,12 +2290,12 @@ function assertPlanWritingPath(planPath, harnessDir) {
2118
2290
  fix: `write the plan under ${planDir}`
2119
2291
  };
2120
2292
  }
2121
- if (existsSync5(planAbs)) {
2293
+ if (existsSync6(planAbs)) {
2122
2294
  try {
2123
2295
  const canonicalPlan = realpathSync2(planAbs);
2124
- const canonicalPlanDir = existsSync5(planDir) ? realpathSync2(planDir) : resolve6(planDir);
2296
+ const canonicalPlanDir = existsSync6(planDir) ? realpathSync2(planDir) : resolve7(planDir);
2125
2297
  const canonicalRel = relative2(canonicalPlanDir, canonicalPlan);
2126
- const canonicalInside = canonicalRel === "" || !canonicalRel.startsWith("..") && !isAbsolute3(canonicalRel);
2298
+ const canonicalInside = canonicalRel === "" || !canonicalRel.startsWith("..") && !isAbsolute4(canonicalRel);
2127
2299
  if (!canonicalInside) {
2128
2300
  return {
2129
2301
  ok: false,
@@ -2151,9 +2323,9 @@ function isDirectory(dir) {
2151
2323
  }
2152
2324
  function hasFiles(dir) {
2153
2325
  try {
2154
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
2326
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2155
2327
  if (entry.isDirectory()) {
2156
- if (hasFiles(join8(dir, entry.name)))
2328
+ if (hasFiles(join9(dir, entry.name)))
2157
2329
  return true;
2158
2330
  } else if (entry.isFile()) {
2159
2331
  return true;
@@ -2166,8 +2338,8 @@ function hasFiles(dir) {
2166
2338
  }
2167
2339
  // src/worktree.ts
2168
2340
  import { execFileSync as execFileSync2 } from "node:child_process";
2169
- import { existsSync as existsSync6 } from "node:fs";
2170
- import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
2341
+ import { existsSync as existsSync7 } from "node:fs";
2342
+ import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
2171
2343
  var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
2172
2344
  function probeTimeoutMs() {
2173
2345
  const raw = process.env.MSTAR_GIT_PROBE_TIMEOUT_MS;
@@ -2218,10 +2390,10 @@ function l1PreDispatchCheck(input, opts = {}) {
2218
2390
  if (leaseWorkingBranch.trim() === "") {
2219
2391
  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"));
2220
2392
  }
2221
- if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve7(controlWorktreePath) === resolve7(leaseWorktreePath)) {
2393
+ if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
2222
2394
  violations.push(violation7("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path — 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"));
2223
2395
  }
2224
- if (leaseWorktreePath !== "" && !existsSync6(leaseWorktreePath)) {
2396
+ if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
2225
2397
  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>`));
2226
2398
  } else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
2227
2399
  const probe = probeBranch(leaseWorktreePath, opts);
@@ -2245,17 +2417,17 @@ function l2PreDispatchCheck(input, opts = {}) {
2245
2417
  violations.push(violation7("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
2246
2418
  return;
2247
2419
  }
2248
- if (!isAbsolute4(track.worktreePath)) {
2420
+ if (!isAbsolute5(track.worktreePath)) {
2249
2421
  violations.push(violation7("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path — 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>)`));
2250
2422
  return;
2251
2423
  }
2252
- const normalized = resolve7(track.worktreePath);
2424
+ const normalized = resolve8(track.worktreePath);
2253
2425
  if (seenPaths.has(normalized)) {
2254
2426
  violations.push(violation7("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks — L2 parallel-writable isolation requires a distinct absolute Worktree path per track (N parallel invokes ≠ isolation)`, "give every parallel track its own git worktree checkout"));
2255
2427
  return;
2256
2428
  }
2257
2429
  seenPaths.add(normalized);
2258
- if (!existsSync6(track.worktreePath)) {
2430
+ if (!existsSync7(track.worktreePath)) {
2259
2431
  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}`));
2260
2432
  return;
2261
2433
  }
@@ -2270,7 +2442,7 @@ function l2PreDispatchCheck(input, opts = {}) {
2270
2442
  }
2271
2443
  function assertControlVsFeaturePath(controlWorktreePath, featureWorktreePath) {
2272
2444
  const violations = [];
2273
- const samePath = controlWorktreePath === "" && featureWorktreePath === "" || controlWorktreePath !== "" && featureWorktreePath !== "" && resolve7(controlWorktreePath) === resolve7(featureWorktreePath);
2445
+ const samePath = controlWorktreePath === "" && featureWorktreePath === "" || controlWorktreePath !== "" && featureWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(featureWorktreePath);
2274
2446
  if (samePath) {
2275
2447
  violations.push(violation7("critical", "worktree.control-feature.same", `control worktree path equals feature/lease worktree path "${controlWorktreePath}" — execution_lease.worktree_path MUST differ from metadata.control_worktree_path`, "use a distinct feature worktree for the plan's product edits"));
2276
2448
  }
@@ -2318,8 +2490,8 @@ function singleReviewSnapshot(assignments) {
2318
2490
  }
2319
2491
  // src/sdd.ts
2320
2492
  import { execFileSync as execFileSync3 } from "node:child_process";
2321
- import { mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
2322
- import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5, join as join9, resolve as resolve8 } from "node:path";
2493
+ import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
2494
+ import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute6, join as join10, resolve as resolve9 } from "node:path";
2323
2495
  class SddScriptError extends Error {
2324
2496
  exitCode;
2325
2497
  constructor(message, exitCode) {
@@ -2356,14 +2528,14 @@ function gitOut(cwd, args) {
2356
2528
  }
2357
2529
  }
2358
2530
  function probeHarnessWithStatus(root) {
2359
- if (isFile2(join9(root, ".mstar", "status.json")))
2360
- return join9(root, ".mstar");
2361
- if (isFile2(join9(root, ".agents", "status.json")))
2362
- return join9(root, ".agents");
2363
- if (hasWorkflowSnapshot(join9(root, ".mstar")))
2364
- return join9(root, ".mstar");
2365
- if (hasWorkflowSnapshot(join9(root, ".agents")))
2366
- return join9(root, ".agents");
2531
+ if (isFile2(join10(root, ".mstar", "status.json")))
2532
+ return join10(root, ".mstar");
2533
+ if (isFile2(join10(root, ".agents", "status.json")))
2534
+ return join10(root, ".agents");
2535
+ if (hasWorkflowSnapshot(join10(root, ".mstar")))
2536
+ return join10(root, ".mstar");
2537
+ if (hasWorkflowSnapshot(join10(root, ".agents")))
2538
+ return join10(root, ".agents");
2367
2539
  return null;
2368
2540
  }
2369
2541
  function hasWorkflowSnapshot(harnessDir) {
@@ -2371,13 +2543,13 @@ function hasWorkflowSnapshot(harnessDir) {
2371
2543
  try {
2372
2544
  workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
2373
2545
  } catch {
2374
- workflowsDir = join9(harnessDir, "workflows");
2546
+ workflowsDir = join10(harnessDir, "workflows");
2375
2547
  }
2376
2548
  if (!isDirectory2(workflowsDir))
2377
2549
  return false;
2378
2550
  try {
2379
- for (const entry of readdirSync5(workflowsDir, { withFileTypes: true })) {
2380
- if (entry.isDirectory() && isFile2(join9(workflowsDir, entry.name, "snapshot.json")))
2551
+ for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
2552
+ if (entry.isDirectory() && isFile2(join10(workflowsDir, entry.name, "snapshot.json")))
2381
2553
  return true;
2382
2554
  }
2383
2555
  } catch {
@@ -2390,14 +2562,14 @@ function isLinkedWorktree(root) {
2390
2562
  const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
2391
2563
  if (gitDirRaw === null || commonRaw === null)
2392
2564
  return false;
2393
- const gitDir = isAbsolute5(gitDirRaw) ? gitDirRaw : join9(root, gitDirRaw);
2394
- const common = isAbsolute5(commonRaw) ? commonRaw : join9(root, commonRaw);
2565
+ const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join10(root, gitDirRaw);
2566
+ const common = isAbsolute6(commonRaw) ? commonRaw : join10(root, commonRaw);
2395
2567
  if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
2396
2568
  return true;
2397
2569
  try {
2398
2570
  const gdParent = realpathSync3(dirname6(gitDir));
2399
2571
  const cmAbs = realpathSync3(common);
2400
- return join9(gdParent, basename3(gitDir)) !== cmAbs && gitDir !== cmAbs;
2572
+ return join10(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
2401
2573
  } catch {
2402
2574
  return false;
2403
2575
  }
@@ -2428,28 +2600,28 @@ function sddWorkspace(planId, opts = {}) {
2428
2600
  const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
2429
2601
  let harnessDir;
2430
2602
  if (harnessOverride) {
2431
- harnessDir = resolve8(root, harnessOverride);
2603
+ harnessDir = resolve9(root, harnessOverride);
2432
2604
  } else {
2433
2605
  const rc = findMstarc(root, root);
2434
2606
  const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
2435
2607
  if (rcHarnessDir) {
2436
- harnessDir = resolve8(rc !== null ? dirname6(rc) : root, rcHarnessDir);
2608
+ harnessDir = resolve9(rc !== null ? dirname6(rc) : root, rcHarnessDir);
2437
2609
  } else {
2438
2610
  const probed = probeHarnessWithStatus(root);
2439
2611
  if (probed) {
2440
2612
  harnessDir = probed;
2441
- } else if (isDirectory2(join9(root, ".mstar"))) {
2442
- harnessDir = join9(root, ".mstar");
2443
- } else if (isDirectory2(join9(root, ".agents"))) {
2444
- harnessDir = join9(root, ".agents");
2613
+ } else if (isDirectory2(join10(root, ".mstar"))) {
2614
+ harnessDir = join10(root, ".mstar");
2615
+ } else if (isDirectory2(join10(root, ".agents"))) {
2616
+ harnessDir = join10(root, ".agents");
2445
2617
  } else {
2446
- harnessDir = join9(root, ".mstar");
2618
+ harnessDir = join10(root, ".mstar");
2447
2619
  }
2448
2620
  }
2449
2621
  }
2450
2622
  const sddDir = resolveSddDir(harnessDir, planId);
2451
2623
  mkdirSync6(sddDir, { recursive: true });
2452
- writeFileSync4(join9(sddDir, ".gitignore"), `*
2624
+ writeFileSync4(join10(sddDir, ".gitignore"), `*
2453
2625
  `);
2454
2626
  return realpathSync3(sddDir);
2455
2627
  }
@@ -2472,7 +2644,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2472
2644
  throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
2473
2645
  }
2474
2646
  mkdirSync6(sddDir, { recursive: true });
2475
- out = join9(sddDir, `task-${taskN}-brief.md`);
2647
+ out = join10(sddDir, `task-${taskN}-brief.md`);
2476
2648
  }
2477
2649
  const records = content.endsWith(`
2478
2650
  `) ? content.split(`
@@ -2525,7 +2697,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
2525
2697
  mkdirSync6(sddDir, { recursive: true });
2526
2698
  const shortBase = gitOut(cwd, ["rev-parse", "--short", base]) ?? base;
2527
2699
  const shortHead = gitOut(cwd, ["rev-parse", "--short", head]) ?? head;
2528
- out = join9(sddDir, `review-${shortBase}..${shortHead}.diff`);
2700
+ out = join10(sddDir, `review-${shortBase}..${shortHead}.diff`);
2529
2701
  }
2530
2702
  const run = (args) => execFileSync3("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
2531
2703
  const parts = [
@@ -2561,7 +2733,7 @@ function assertBaseSha(ref, opts = {}) {
2561
2733
  }
2562
2734
  function taskReportExists(sddDir, taskN) {
2563
2735
  try {
2564
- const st = statSync4(join9(sddDir, `task-${taskN}-report.md`));
2736
+ const st = statSync4(join10(sddDir, `task-${taskN}-report.md`));
2565
2737
  return st.isFile() && st.size > 0;
2566
2738
  } catch {
2567
2739
  return false;
@@ -2570,7 +2742,7 @@ function taskReportExists(sddDir, taskN) {
2570
2742
  function readProgressLedger(sddDir) {
2571
2743
  let content;
2572
2744
  try {
2573
- content = readFileSync7(join9(sddDir, "progress.md"), "utf8");
2745
+ content = readFileSync7(join10(sddDir, "progress.md"), "utf8");
2574
2746
  } catch {
2575
2747
  return [];
2576
2748
  }
@@ -2603,8 +2775,8 @@ function implementerSessionStickyRules(input) {
2603
2775
  return { resume: true, reason: `sticky resume OK: host_agent_id ${session.host_agent_id}, next task ${nextTask}` };
2604
2776
  }
2605
2777
  // src/migrate.ts
2606
- import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
2607
- import { dirname as dirname7, isAbsolute as isAbsolute6, join as join10, relative as relative3, resolve as resolve9, sep as sep2 } from "node:path";
2778
+ import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
2779
+ import { dirname as dirname7, isAbsolute as isAbsolute7, join as join11, relative as relative3, resolve as resolve10, sep as sep2 } from "node:path";
2608
2780
  var MIGRATE_STATUS_FILE = "status.json";
2609
2781
  var ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json";
2610
2782
  var NOTES_LEDGER_FILE = "notes.jsonl";
@@ -2644,18 +2816,18 @@ function todayString3() {
2644
2816
  return `${now.getFullYear()}-${month}-${day}`;
2645
2817
  }
2646
2818
  function scanCompasses(harnessDir) {
2647
- const iterationsDir = join10(harnessDir, "iterations");
2819
+ const iterationsDir = join11(harnessDir, "iterations");
2648
2820
  const out = [];
2649
2821
  let entries;
2650
2822
  try {
2651
- entries = readdirSync6(iterationsDir, { withFileTypes: true });
2823
+ entries = readdirSync7(iterationsDir, { withFileTypes: true });
2652
2824
  } catch {
2653
2825
  return out;
2654
2826
  }
2655
2827
  for (const entry of entries) {
2656
2828
  if (!entry.isDirectory())
2657
2829
  continue;
2658
- const compassPath = join10(iterationsDir, entry.name, "delivery-compass.md");
2830
+ const compassPath = join11(iterationsDir, entry.name, "delivery-compass.md");
2659
2831
  let content;
2660
2832
  try {
2661
2833
  content = readFileSync8(compassPath, "utf8");
@@ -2735,8 +2907,8 @@ function buildIterationSnapshot(compass, rowById, rootUpdatedAt) {
2735
2907
  id: compass.id,
2736
2908
  type: "iteration",
2737
2909
  status,
2738
- file: join10("workflows", compass.id, WORKFLOW_SNAPSHOT_FILE),
2739
- source: join10("iterations", compass.id, "delivery-compass.md"),
2910
+ file: join11("workflows", compass.id, WORKFLOW_SNAPSHOT_FILE),
2911
+ source: join11("iterations", compass.id, "delivery-compass.md"),
2740
2912
  data: snapshot
2741
2913
  };
2742
2914
  }
@@ -2771,7 +2943,7 @@ function buildStandaloneSnapshot(row, rootUpdatedAt, migrationNotes) {
2771
2943
  id,
2772
2944
  type: "plan",
2773
2945
  status,
2774
- file: join10("workflows", id, WORKFLOW_SNAPSHOT_FILE),
2946
+ file: join11("workflows", id, WORKFLOW_SNAPSHOT_FILE),
2775
2947
  source: "status.json plans[] row",
2776
2948
  data: snapshot
2777
2949
  };
@@ -2879,7 +3051,7 @@ function buildRegister(residualFindings, byPlan, projectId, migratedAt) {
2879
3051
  return null;
2880
3052
  const doc = { entries };
2881
3053
  return {
2882
- file: join10("projects", projectId, PROJECT_REGISTER_FILE),
3054
+ file: join11("projects", projectId, PROJECT_REGISTER_FILE),
2883
3055
  source: "status.json residual_findings",
2884
3056
  data: doc
2885
3057
  };
@@ -2910,7 +3082,7 @@ function collectNotesFiles(snapshots) {
2910
3082
  if (lines.length === 0)
2911
3083
  continue;
2912
3084
  out.push({
2913
- file: join10(dirname7(snapshot.file), NOTES_LEDGER_FILE),
3085
+ file: join11(dirname7(snapshot.file), NOTES_LEDGER_FILE),
2914
3086
  source,
2915
3087
  lines
2916
3088
  });
@@ -2918,11 +3090,11 @@ function collectNotesFiles(snapshots) {
2918
3090
  return out;
2919
3091
  }
2920
3092
  function migrateHarnessTree(root, opts = {}) {
2921
- const harnessDir = resolve9(root);
3093
+ const harnessDir = resolve10(root);
2922
3094
  const workflowDir = resolveWorkflowDir(harnessDir, { harnessDir });
2923
3095
  const projectDir = resolveProjectDir(harnessDir, { harnessDir });
2924
3096
  const projectId = opts.projectId ?? _DEFAULT_PROJECT;
2925
- const statusPath = join10(harnessDir, MIGRATE_STATUS_FILE);
3097
+ const statusPath = join11(harnessDir, MIGRATE_STATUS_FILE);
2926
3098
  const legacy = readJson(statusPath);
2927
3099
  if (legacy.version === 2) {
2928
3100
  const updatedAt = typeof legacy.updated_at === "string" && legacy.updated_at !== "" ? legacy.updated_at : "1970-01-01";
@@ -3018,7 +3190,7 @@ function migrateHarnessTree(root, opts = {}) {
3018
3190
  migrationNotes.push(`roadmap title sanitized for frontmatter (line breaks replaced with spaces): ${JSON.stringify(rawTitle)}`);
3019
3191
  }
3020
3192
  roadmap = {
3021
- file: join10("projects", projectId, PROJECT_ROADMAP_FILE),
3193
+ file: join11("projects", projectId, PROJECT_ROADMAP_FILE),
3022
3194
  source: "status.json metadata.program_roadmap",
3023
3195
  content: buildRoadmap({ ...programRoadmap, title: sanitizedTitle }, projectId, migratedAt)
3024
3196
  };
@@ -3067,19 +3239,19 @@ async function applyMigratePlan(plan) {
3067
3239
  if (plan.dryRun) {
3068
3240
  return { applied: false, message: `dry-run: ${plan.steps.length} steps planned (source → destination), zero writes` };
3069
3241
  }
3070
- const statusPath = join10(plan.root, MIGRATE_STATUS_FILE);
3242
+ const statusPath = join11(plan.root, MIGRATE_STATUS_FILE);
3071
3243
  const current = readJson(statusPath);
3072
3244
  if (current.version === 2) {
3073
3245
  return { applied: false, message: "no-op: status.json already at schema version 2 (migrated) — nothing to do" };
3074
3246
  }
3075
- const harnessRoot = resolve9(plan.root);
3076
- const workflowRoot = resolve9(plan.workflowDir);
3077
- const projectRoot = resolve9(plan.projectDir);
3078
- if (!isAbsolute6(plan.workflowDir) || !isAbsolute6(plan.projectDir)) {
3247
+ const harnessRoot = resolve10(plan.root);
3248
+ const workflowRoot = resolve10(plan.workflowDir);
3249
+ const projectRoot = resolve10(plan.projectDir);
3250
+ if (!isAbsolute7(plan.workflowDir) || !isAbsolute7(plan.projectDir)) {
3079
3251
  throw new Error(`refusing to apply migration: plan workflowDir/projectDir must be absolute (got ${JSON.stringify(plan.workflowDir)} / ${JSON.stringify(plan.projectDir)})`);
3080
3252
  }
3081
- const workflowTargetOf = (canonicalFile) => join10(workflowRoot, relative3("workflows", canonicalFile));
3082
- const projectTargetOf = (canonicalFile) => join10(projectRoot, relative3("projects", canonicalFile));
3253
+ const workflowTargetOf = (canonicalFile) => join11(workflowRoot, relative3("workflows", canonicalFile));
3254
+ const projectTargetOf = (canonicalFile) => join11(projectRoot, relative3("projects", canonicalFile));
3083
3255
  const allDestinations = [
3084
3256
  plan.archive.file,
3085
3257
  ...plan.snapshots.map((snapshot) => snapshot.file),
@@ -3088,14 +3260,14 @@ async function applyMigratePlan(plan) {
3088
3260
  ...plan.roadmap !== null ? [plan.roadmap.file] : []
3089
3261
  ];
3090
3262
  for (const destination of allDestinations) {
3091
- const resolvedDest = resolve9(join10(plan.root, destination));
3263
+ const resolvedDest = resolve10(join11(plan.root, destination));
3092
3264
  const inside = (dir) => resolvedDest === dir || resolvedDest.startsWith(`${dir}${sep2}`);
3093
3265
  if (!inside(harnessRoot) && !inside(workflowRoot) && !inside(projectRoot)) {
3094
3266
  throw new Error(`refusing to apply migration: destination escapes the harness dir (${JSON.stringify(destination)}) — every write must stay under ${JSON.stringify(plan.root)}, the workflow dir (${JSON.stringify(plan.workflowDir)}) or the project dir (${JSON.stringify(plan.projectDir)})`);
3095
3267
  }
3096
3268
  }
3097
- mkdirSync7(join10(plan.root, dirname7(plan.archive.file)), { recursive: true });
3098
- copyFileSync(statusPath, join10(plan.root, plan.archive.file));
3269
+ mkdirSync7(join11(plan.root, dirname7(plan.archive.file)), { recursive: true });
3270
+ copyFileSync(statusPath, join11(plan.root, plan.archive.file));
3099
3271
  for (const snapshot of plan.snapshots) {
3100
3272
  await writeWorkflowSnapshot(snapshot.data, dirname7(workflowTargetOf(snapshot.file)));
3101
3273
  }
@@ -3544,8 +3716,8 @@ function completenessLevel(frontmatterText, checklist) {
3544
3716
  }
3545
3717
  // src/audit.ts
3546
3718
  import { execFileSync as execFileSync4 } from "node:child_process";
3547
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
3548
- import { basename as basename4, join as join11, resolve as resolve10, sep as sep3 } from "node:path";
3719
+ import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
3720
+ import { basename as basename5, join as join12, resolve as resolve11, sep as sep3 } from "node:path";
3549
3721
  function violation9(severity, code, message, fix) {
3550
3722
  return { ok: false, severity, code, message, fix };
3551
3723
  }
@@ -3846,7 +4018,7 @@ function scanSecrets(files) {
3846
4018
  unreadableFiles++;
3847
4019
  continue;
3848
4020
  }
3849
- const base = basename4(file);
4021
+ const base = basename5(file);
3850
4022
  for (const entry of NEVER_COMMIT_FILENAMES) {
3851
4023
  if (entry.re.test(base))
3852
4024
  findings.push({ file, line: 1, type: entry.type });
@@ -3898,12 +4070,12 @@ var LOCKFILE_NAMES = [
3898
4070
  function rootLockfiles(root) {
3899
4071
  let entries;
3900
4072
  try {
3901
- entries = readdirSync7(root, { withFileTypes: true });
4073
+ entries = readdirSync8(root, { withFileTypes: true });
3902
4074
  } catch {
3903
4075
  return [];
3904
4076
  }
3905
4077
  const names = new Set(LOCKFILE_NAMES);
3906
- const present = entries.filter((entry) => entry.isFile() && names.has(entry.name)).map((entry) => join11(root, entry.name));
4078
+ const present = entries.filter((entry) => entry.isFile() && names.has(entry.name)).map((entry) => join12(root, entry.name));
3907
4079
  if (present.length === 0)
3908
4080
  return [];
3909
4081
  try {
@@ -3912,7 +4084,7 @@ function rootLockfiles(root) {
3912
4084
  encoding: "utf8",
3913
4085
  stdio: ["ignore", "pipe", "ignore"]
3914
4086
  }).split("\x00").filter((f) => f !== ""));
3915
- return present.filter((p) => tracked.has(basename4(p)));
4087
+ return present.filter((p) => tracked.has(basename5(p)));
3916
4088
  } catch {
3917
4089
  return present;
3918
4090
  }
@@ -3928,17 +4100,17 @@ function supplyChainChecks(repoRoot) {
3928
4100
  findings.push({ kind: "lockfile-duplicate", file: lockfiles.map((f) => f.replace(`${repoRoot}/`, "")).join(", ") });
3929
4101
  violations.push(violation9("medium", "audit.supply.lockfile-duplicate", `multiple lockfiles at ${repoRoot}: ${lockfiles.join(", ")}`, "keep exactly one lockfile per package manager"));
3930
4102
  }
3931
- const workflowsDir = join11(repoRoot, ".github", "workflows");
4103
+ const workflowsDir = join12(repoRoot, ".github", "workflows");
3932
4104
  let wfEntries = [];
3933
4105
  try {
3934
- wfEntries = readdirSync7(workflowsDir, { withFileTypes: true });
4106
+ wfEntries = readdirSync8(workflowsDir, { withFileTypes: true });
3935
4107
  } catch {
3936
4108
  wfEntries = [];
3937
4109
  }
3938
4110
  for (const entry of wfEntries) {
3939
4111
  if (!entry.isFile() || !/\.(?:ya?ml)$/.test(entry.name))
3940
4112
  continue;
3941
- const wfPath = join11(workflowsDir, entry.name);
4113
+ const wfPath = join12(workflowsDir, entry.name);
3942
4114
  const relPath = `.github/workflows/${entry.name}`;
3943
4115
  let text;
3944
4116
  try {
@@ -4095,9 +4267,9 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4095
4267
  const date = options.date ?? new Date().toISOString().slice(0, 10);
4096
4268
  const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
4097
4269
  mkdirSync8(outDir, { recursive: true });
4098
- const existingReadme = join11(outDir, "README.md");
4099
- const carried = existsSync7(existingReadme) ? extractSecurityDispositionSections(readFileSync9(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
4100
- const existing = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f));
4270
+ const existingReadme = join12(outDir, "README.md");
4271
+ const carried = existsSync8(existingReadme) ? extractSecurityDispositionSections(readFileSync9(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
4272
+ const existing = readdirSync8(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f));
4101
4273
  let next = existing.reduce((max, f) => Math.max(max, Number(f.slice(0, 3))), 0) + 1;
4102
4274
  const redactedFindings = findings.map(redactFinding);
4103
4275
  const written = [];
@@ -4113,13 +4285,13 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4113
4285
  }
4114
4286
  usedSlugs.add(slug);
4115
4287
  const file = `${num}-${slug}.md`;
4116
- writeFileSync6(join11(outDir, file), renderPlanFile(finding, plannedAt));
4288
+ writeFileSync6(join12(outDir, file), renderPlanFile(finding, plannedAt));
4117
4289
  written.push(file);
4118
4290
  next++;
4119
4291
  }
4120
4292
  const all = [...existing, ...written].sort();
4121
4293
  const rows = all.map((file) => {
4122
- const summary = readPlanFileSummary(join11(outDir, file));
4294
+ const summary = readPlanFileSummary(join12(outDir, file));
4123
4295
  const fields = summary.fields;
4124
4296
  return {
4125
4297
  num: file.slice(0, 3),
@@ -4153,7 +4325,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4153
4325
  });
4154
4326
  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;
4155
4327
  const hardeningCheckedLines = options.hardeningChecked !== undefined ? options.hardeningChecked.map((hc) => `- ${hc.kind}: ${escapeCell(redactText(hc.text))}`) : carried.hardeningChecked;
4156
- writeFileSync6(join11(outDir, "README.md"), renderIndex({
4328
+ writeFileSync6(join12(outDir, "README.md"), renderIndex({
4157
4329
  date,
4158
4330
  repoName: options.repoName ?? "repo",
4159
4331
  repoShortSha: options.repoShortSha ?? "unknown",
@@ -4162,7 +4334,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4162
4334
  needsVerification: needsVerificationLines,
4163
4335
  hardeningChecked: hardeningCheckedLines
4164
4336
  }));
4165
- return { outDir: resolve10(outDir), date, files: written, nextNumber: next };
4337
+ return { outDir: resolve11(outDir), date, files: written, nextNumber: next };
4166
4338
  }
4167
4339
  async function promoteAuditPlans(outDir, selected, options) {
4168
4340
  if (selected.length === 0) {
@@ -4171,19 +4343,19 @@ async function promoteAuditPlans(outDir, selected, options) {
4171
4343
  if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
4172
4344
  throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
4173
4345
  }
4174
- const workflowId = options.workflowId ?? basename4(resolve10(outDir));
4346
+ const workflowId = options.workflowId ?? basename5(resolve11(outDir));
4175
4347
  assertSafePathComponent(workflowId, "workflow id");
4176
- const harnessDir = resolve10(options.harnessDir);
4177
- const statusPath = join11(harnessDir, "status.json");
4178
- const workflowDir = join11(harnessDir, "workflows", workflowId);
4179
- const snapshotPath = join11(workflowDir, WORKFLOW_SNAPSHOT_FILE);
4348
+ const harnessDir = resolve11(options.harnessDir);
4349
+ const statusPath = join12(harnessDir, "status.json");
4350
+ const workflowDir = join12(harnessDir, "workflows", workflowId);
4351
+ const snapshotPath = join12(workflowDir, WORKFLOW_SNAPSHOT_FILE);
4180
4352
  const planFiles = resolveSelectedPlanFiles(outDir, selected);
4181
4353
  const indexRows = readExecutionOrderIndex(outDir);
4182
4354
  const plans = planFiles.map((planFile) => {
4183
4355
  const stem = planFile.replace(/\.md$/, "");
4184
4356
  const num = stem.slice(0, 3);
4185
4357
  const indexRow = indexRows.get(num);
4186
- const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
4358
+ const title = indexRow?.title ?? readPlanFileSummary(join12(outDir, planFile)).title;
4187
4359
  return {
4188
4360
  id: stem,
4189
4361
  title,
@@ -4211,18 +4383,18 @@ async function promoteAuditPlans(outDir, selected, options) {
4211
4383
  if (!entryGate.ok) {
4212
4384
  throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
4213
4385
  }
4214
- await withStatusWriteLock(statusPath, () => {
4215
- if (existsSync7(snapshotPath)) {
4386
+ await withStatusWriteLock(statusPath, async () => {
4387
+ if (existsSync8(snapshotPath)) {
4216
4388
  throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists ` + `(snapshot at ${snapshotPath}) — re-promote would drop its registered plan rows; ` + `remove that workflow before promoting again`);
4217
4389
  }
4218
4390
  mkdirSync8(workflowDir, { recursive: true });
4219
4391
  try {
4220
4392
  writeJson(snapshotPath, snapshot);
4221
- registerWorkflowEntryLocked(statusPath, entry);
4393
+ await registerWorkflowEntryLocked(statusPath, entry);
4222
4394
  } catch (error) {
4223
4395
  rmSync(snapshotPath, { force: true });
4224
4396
  try {
4225
- if (readdirSync7(workflowDir).length === 0) {
4397
+ if (readdirSync8(workflowDir).length === 0) {
4226
4398
  rmdirSync2(workflowDir);
4227
4399
  }
4228
4400
  } catch {}
@@ -4233,7 +4405,7 @@ async function promoteAuditPlans(outDir, selected, options) {
4233
4405
  return { workflowId, snapshotPath };
4234
4406
  }
4235
4407
  function resolveSelectedPlanFiles(outDir, selected) {
4236
- const files = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
4408
+ const files = readdirSync8(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
4237
4409
  const byNum = new Map;
4238
4410
  const byStem = new Map;
4239
4411
  for (const file of files) {
@@ -4248,7 +4420,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
4248
4420
  for (const id of selected) {
4249
4421
  const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
4250
4422
  if (file === undefined) {
4251
- throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve10(outDir)}`);
4423
+ throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve11(outDir)}`);
4252
4424
  }
4253
4425
  if (!seen.has(file)) {
4254
4426
  seen.add(file);
@@ -4258,7 +4430,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
4258
4430
  return resolved;
4259
4431
  }
4260
4432
  function readExecutionOrderIndex(outDir) {
4261
- const readmePath = join11(outDir, "README.md");
4433
+ const readmePath = join12(outDir, "README.md");
4262
4434
  let text;
4263
4435
  try {
4264
4436
  text = readFileSync9(readmePath, "utf8");
@@ -4287,7 +4459,7 @@ function readExecutionOrderIndex(outDir) {
4287
4459
  return rows;
4288
4460
  }
4289
4461
  function planFileRel(outDir, planFile) {
4290
- const resolved = resolve10(outDir);
4462
+ const resolved = resolve11(outDir);
4291
4463
  const parts = resolved.split(sep3);
4292
4464
  const plansIdx = parts.lastIndexOf("plans");
4293
4465
  if (plansIdx >= 0) {
@@ -4296,8 +4468,8 @@ function planFileRel(outDir, planFile) {
4296
4468
  return planFile;
4297
4469
  }
4298
4470
  // src/compound.ts
4299
- import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
4300
- import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve11, sep as sep4 } from "node:path";
4471
+ import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync10 } from "node:fs";
4472
+ import { basename as basename6, isAbsolute as isAbsolute8, join as join13, relative as relative4, resolve as resolve12, sep as sep4 } from "node:path";
4301
4473
  function violation10(severity, code, message, fix) {
4302
4474
  return { ok: false, severity, code, message, fix };
4303
4475
  }
@@ -4564,7 +4736,7 @@ function referenceExists(repoRoot, docText) {
4564
4736
  if (ref === "" || seen.has(ref))
4565
4737
  continue;
4566
4738
  seen.add(ref);
4567
- if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") || isAbsolute7(ref)) {
4739
+ if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") || isAbsolute8(ref)) {
4568
4740
  continue;
4569
4741
  }
4570
4742
  if (ref.includes("/") || REF_EXT_RE.test(ref)) {
@@ -4583,7 +4755,7 @@ function referenceExists(repoRoot, docText) {
4583
4755
  const dir = stack.pop();
4584
4756
  let entries;
4585
4757
  try {
4586
- entries = readdirSync8(dir, { withFileTypes: true });
4758
+ entries = readdirSync9(dir, { withFileTypes: true });
4587
4759
  } catch {
4588
4760
  continue;
4589
4761
  }
@@ -4592,7 +4764,7 @@ function referenceExists(repoRoot, docText) {
4592
4764
  break;
4593
4765
  if (entry.isDirectory()) {
4594
4766
  if (!WALK_SKIP_DIRS.has(entry.name))
4595
- stack.push(join12(dir, entry.name));
4767
+ stack.push(join13(dir, entry.name));
4596
4768
  } else if (!entry.isSymbolicLink()) {
4597
4769
  const base = entry.name.replace(/\.(?:ts|tsx|js|jsx|mjs|cjs)$/, "");
4598
4770
  if (moduleNames.has(base))
@@ -4604,7 +4776,7 @@ function referenceExists(repoRoot, docText) {
4604
4776
  for (const { ref, isSymbol, module } of refs) {
4605
4777
  if (!isSymbol || module === undefined) {
4606
4778
  const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
4607
- if (existsSync8(resolve11(repoRoot, candidate))) {
4779
+ if (existsSync9(resolve12(repoRoot, candidate))) {
4608
4780
  checked++;
4609
4781
  } else {
4610
4782
  violations.push(violation10("medium", "compound.reference.missing-file", `referenced path \`${ref}\` does not exist under ${repoRoot} (compound-refresh Phase 2: referenced code still exists?)`, "update the doc to reference an existing path, or delete the stale reference"));
@@ -4624,14 +4796,14 @@ function collectKnowledgeDocs(dir) {
4624
4796
  const current = stack.pop();
4625
4797
  let entries;
4626
4798
  try {
4627
- entries = readdirSync8(current, { withFileTypes: true });
4799
+ entries = readdirSync9(current, { withFileTypes: true });
4628
4800
  } catch {
4629
4801
  continue;
4630
4802
  }
4631
4803
  for (const entry of entries) {
4632
4804
  if (entry.isSymbolicLink())
4633
4805
  continue;
4634
- const full = join12(current, entry.name);
4806
+ const full = join13(current, entry.name);
4635
4807
  if (entry.isDirectory()) {
4636
4808
  stack.push(full);
4637
4809
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
@@ -4651,8 +4823,8 @@ function normalizeIndexRef(cell) {
4651
4823
  }
4652
4824
  function assertIndexRows(knowledgeDir) {
4653
4825
  const violations = [];
4654
- const readmePath = join12(knowledgeDir, "README.md");
4655
- if (!existsSync8(readmePath)) {
4826
+ const readmePath = join13(knowledgeDir, "README.md");
4827
+ if (!existsSync9(readmePath)) {
4656
4828
  violations.push(violation10("medium", "compound.index.missing-readme", `missing ${readmePath} — 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"));
4657
4829
  return { ok: false, violations };
4658
4830
  }
@@ -4677,19 +4849,19 @@ function assertIndexRows(knowledgeDir) {
4677
4849
  }
4678
4850
  function compoundRefreshScope(harnessDir, projectRoot) {
4679
4851
  return [
4680
- join12(harnessDir, "knowledge"),
4681
- join12(harnessDir, "knowledge", "README.md"),
4682
- join12(projectRoot, "CONCEPTS.md"),
4683
- join12(harnessDir, "status.json")
4852
+ join13(harnessDir, "knowledge"),
4853
+ join13(harnessDir, "knowledge", "README.md"),
4854
+ join13(projectRoot, "CONCEPTS.md"),
4855
+ join13(harnessDir, "status.json")
4684
4856
  ];
4685
4857
  }
4686
4858
  function isFileLikeRoot(root) {
4687
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
4859
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename6(root));
4688
4860
  }
4689
4861
  function scopeGuard(path, allowedRoots) {
4690
- const resolved = resolve11(path);
4862
+ const resolved = resolve12(path);
4691
4863
  for (const root of allowedRoots) {
4692
- const r = resolve11(root);
4864
+ const r = resolve12(root);
4693
4865
  if (isFileLikeRoot(r)) {
4694
4866
  if (resolved === r)
4695
4867
  return { ok: true, violations: [] };
@@ -4937,8 +5109,8 @@ function lintStrategySections(docText) {
4937
5109
  return { ok: violations.length === 0, violations };
4938
5110
  }
4939
5111
  // src/roles.ts
4940
- import { existsSync as existsSync9 } from "node:fs";
4941
- import { join as join13 } from "node:path";
5112
+ import { existsSync as existsSync10 } from "node:fs";
5113
+ import { join as join14 } from "node:path";
4942
5114
  function violation12(severity, code, message, fix) {
4943
5115
  return { ok: false, severity, code, message, fix };
4944
5116
  }
@@ -4994,8 +5166,8 @@ function validateRoleMapping(rolesDir, options = {}) {
4994
5166
  const violations = [];
4995
5167
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
4996
5168
  for (const { agentId, reference } of mapping) {
4997
- if (!existsSync9(join13(rolesDir, reference))) {
4998
- violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join13(rolesDir, reference)} or fix the mapping row`));
5169
+ if (!existsSync10(join14(rolesDir, reference))) {
5170
+ violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join14(rolesDir, reference)} or fix the mapping row`));
4999
5171
  }
5000
5172
  }
5001
5173
  for (const { family, memberIds } of families) {
@@ -5200,8 +5372,8 @@ function resolveAssetPath(skillName, relPath, host) {
5200
5372
  return `skill \`${skillName}\` → ${relPath} (${resolveSkillRoot(host, { skill: skillName, rel: relPath })})`;
5201
5373
  }
5202
5374
  // src/prreview.ts
5203
- import { readdirSync as readdirSync9 } from "node:fs";
5204
- import { isAbsolute as isAbsolute8, join as join14 } from "node:path";
5375
+ import { readdirSync as readdirSync10 } from "node:fs";
5376
+ import { isAbsolute as isAbsolute9, join as join15 } from "node:path";
5205
5377
  var MERGE_CLASSES = ["must-fix", "should-fix", "nit"];
5206
5378
  var PR_VERDICTS = ["ship it", "needs fixes", "blocked"];
5207
5379
  var REVIEW_EMOJI = {
@@ -5238,6 +5410,162 @@ function computePrTally(input) {
5238
5410
  ` + `must-fix=${mustFix} should-fix=${shouldFix} nit=${nit} unverified=${unverified}`;
5239
5411
  return { verdict, scorePct, tally: { mustFix, shouldFix, nit, unverified }, chatHeader };
5240
5412
  }
5413
+ var REVIEW_SCHEMA_ID = "mstar.review/v1";
5414
+ var INSPECTOR_VERDICTS = ["comment", "request_changes", "approve"];
5415
+ var INSPECTOR_SEVERITIES = ["critical", "warning", "suggestion", "info"];
5416
+ function isPlainObject7(value) {
5417
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5418
+ }
5419
+ var TALLY_COUNT_KEYS = ["mustFix", "shouldFix", "nit", "unverified"];
5420
+ function checkProvidedTallyShape(tally, violations) {
5421
+ if (typeof tally.verdict !== "string" || !PR_VERDICTS.includes(tally.verdict)) {
5422
+ 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(" | ")}`));
5423
+ }
5424
+ if (typeof tally.scorePct !== "number" || !Number.isInteger(tally.scorePct) || tally.scorePct < 0 || tally.scorePct > 100) {
5425
+ violations.push(violation14("high", "review.tally-malformed", `tally.scorePct must be an integer in [0, 100] - got ${String(tally.scorePct)}`));
5426
+ }
5427
+ if (!isPlainObject7(tally.tally)) {
5428
+ violations.push(violation14("high", "review.tally-malformed", "tally.tally must be an object carrying the four class counts"));
5429
+ } else {
5430
+ for (const key of TALLY_COUNT_KEYS) {
5431
+ const count = tally.tally[key];
5432
+ if (typeof count !== "number" || !Number.isInteger(count) || count < 0) {
5433
+ violations.push(violation14("high", "review.tally-malformed", `tally.tally.${key} must be a non-negative integer - got ${String(count)}`));
5434
+ }
5435
+ }
5436
+ }
5437
+ if (typeof tally.chatHeader !== "string") {
5438
+ violations.push(violation14("high", "review.tally-malformed", `tally.chatHeader must be a string - got ${typeof tally.chatHeader}`));
5439
+ }
5440
+ }
5441
+ function validateMstarReviewV1(doc) {
5442
+ const violations = [];
5443
+ if (!isPlainObject7(doc)) {
5444
+ return {
5445
+ ok: false,
5446
+ violations: [violation14("high", "review.not-object", "review document must be a JSON object")]
5447
+ };
5448
+ }
5449
+ if (doc.schema === undefined) {
5450
+ violations.push(violation14("high", "review.missing-schema", "missing required field: schema"));
5451
+ } else if (doc.schema !== REVIEW_SCHEMA_ID) {
5452
+ violations.push(violation14("high", "review.invalid-schema", `schema "${String(doc.schema)}" is not "${REVIEW_SCHEMA_ID}"`));
5453
+ }
5454
+ if (doc.verdict === undefined) {
5455
+ violations.push(violation14("high", "review.missing-verdict", "missing required field: verdict"));
5456
+ } else if (typeof doc.verdict !== "string") {
5457
+ violations.push(violation14("high", "review.invalid-verdict", `verdict must be a string - got ${typeof doc.verdict}`));
5458
+ } else if (INSPECTOR_VERDICTS.includes(doc.verdict)) {
5459
+ 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(" | ")}`));
5460
+ } else if (!PR_VERDICTS.includes(doc.verdict)) {
5461
+ violations.push(violation14("high", "review.invalid-verdict", `verdict "${doc.verdict}" is not one of ${JSON.stringify(PR_VERDICTS)}`, `use one of: ${PR_VERDICTS.join(" | ")}`));
5462
+ }
5463
+ if (doc.summary_md === undefined || typeof doc.summary_md !== "string" || doc.summary_md.trim() === "") {
5464
+ violations.push(violation14("high", "review.missing-summary", "summary_md must be a non-empty string"));
5465
+ }
5466
+ if (doc.findings === undefined || !Array.isArray(doc.findings)) {
5467
+ violations.push(violation14("high", "review.findings-not-array", "findings must be an array"));
5468
+ } else {
5469
+ doc.findings.forEach((finding, index) => {
5470
+ if (!isPlainObject7(finding)) {
5471
+ violations.push(violation14("high", "review.invalid-finding", `findings[${index}] must be an object`));
5472
+ return;
5473
+ }
5474
+ if (finding.severity !== undefined) {
5475
+ 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"));
5476
+ }
5477
+ if (finding.mergeClass === undefined) {
5478
+ violations.push(violation14("high", "review.missing-merge-class", `findings[${index}] missing required field: mergeClass`));
5479
+ } else if (typeof finding.mergeClass !== "string") {
5480
+ violations.push(violation14("high", "review.invalid-merge-class", `findings[${index}].mergeClass must be a string - got ${typeof finding.mergeClass}`));
5481
+ } else if (INSPECTOR_SEVERITIES.includes(finding.mergeClass)) {
5482
+ 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(" | ")}`));
5483
+ } else if (!MERGE_CLASSES.includes(finding.mergeClass)) {
5484
+ 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(" | ")}`));
5485
+ }
5486
+ if (finding.title === undefined || typeof finding.title !== "string" || finding.title.trim() === "") {
5487
+ violations.push(violation14("high", "review.empty-title", `findings[${index}].title must be a non-empty string`));
5488
+ }
5489
+ if (finding.body === undefined || typeof finding.body !== "string" || finding.body.trim() === "") {
5490
+ violations.push(violation14("high", "review.empty-body", `findings[${index}].body must be a non-empty string`));
5491
+ }
5492
+ if (finding.category !== undefined && typeof finding.category !== "string") {
5493
+ violations.push(violation14("high", "review.invalid-category", `findings[${index}].category must be a string`));
5494
+ }
5495
+ if (finding.file_path !== undefined && finding.file_path !== null && typeof finding.file_path !== "string") {
5496
+ violations.push(violation14("high", "review.invalid-file-path", `findings[${index}].file_path must be a string or null`));
5497
+ }
5498
+ if (finding.line_start !== undefined && finding.line_start !== null && typeof finding.line_start !== "number") {
5499
+ violations.push(violation14("high", "review.invalid-line-start", `findings[${index}].line_start must be a number or null`));
5500
+ }
5501
+ if (finding.line_end !== undefined && finding.line_end !== null && typeof finding.line_end !== "number") {
5502
+ violations.push(violation14("high", "review.invalid-line-end", `findings[${index}].line_end must be a number or null`));
5503
+ }
5504
+ if (finding.fingerprint_hint !== undefined && typeof finding.fingerprint_hint !== "string") {
5505
+ violations.push(violation14("high", "review.invalid-fingerprint-hint", `findings[${index}].fingerprint_hint must be a string`));
5506
+ }
5507
+ });
5508
+ }
5509
+ if (doc.tally !== undefined) {
5510
+ if (!isPlainObject7(doc.tally)) {
5511
+ violations.push(violation14("high", "review.invalid-tally", "tally must be a PrTallyResult object"));
5512
+ } else {
5513
+ checkProvidedTallyShape(doc.tally, violations);
5514
+ if (doc.tally.verdict !== doc.verdict) {
5515
+ 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)`));
5516
+ }
5517
+ }
5518
+ }
5519
+ if (doc.target !== undefined) {
5520
+ if (!isPlainObject7(doc.target)) {
5521
+ violations.push(violation14("high", "review.invalid-target", "target must be an object"));
5522
+ } else {
5523
+ if (doc.target.owner !== undefined && typeof doc.target.owner !== "string") {
5524
+ violations.push(violation14("high", "review.invalid-target", "target.owner must be a string"));
5525
+ }
5526
+ if (doc.target.repo !== undefined && typeof doc.target.repo !== "string") {
5527
+ violations.push(violation14("high", "review.invalid-target", "target.repo must be a string"));
5528
+ }
5529
+ if (doc.target.pr !== undefined && typeof doc.target.pr !== "number") {
5530
+ violations.push(violation14("high", "review.invalid-target", "target.pr must be a number"));
5531
+ }
5532
+ if (doc.target.head_sha !== undefined && typeof doc.target.head_sha !== "string") {
5533
+ violations.push(violation14("high", "review.invalid-target", "target.head_sha must be a string"));
5534
+ }
5535
+ }
5536
+ }
5537
+ return { ok: violations.length === 0, violations };
5538
+ }
5539
+ function defaultReviewSummary(tally, findings) {
5540
+ const lines = [
5541
+ `## Verdict: ${tally.verdict} · ${tally.scorePct}%`,
5542
+ "",
5543
+ `must-fix=${tally.tally.mustFix} should-fix=${tally.tally.shouldFix} nit=${tally.tally.nit} unverified=${tally.tally.unverified}`
5544
+ ];
5545
+ if (findings.length > 0) {
5546
+ lines.push("");
5547
+ for (const finding of findings) {
5548
+ lines.push(`- ${finding.mergeClass}: ${finding.title}`);
5549
+ }
5550
+ }
5551
+ return lines.join(`
5552
+ `);
5553
+ }
5554
+ function synthesizeReview(input) {
5555
+ const tally = computePrTally({
5556
+ findings: input.findings,
5557
+ unverifiedCount: input.unverifiedCount,
5558
+ unmetAc: input.unmetAc
5559
+ });
5560
+ return {
5561
+ schema: "mstar.review/v1",
5562
+ verdict: tally.verdict,
5563
+ summary_md: input.summary_md ?? defaultReviewSummary(tally, input.findings),
5564
+ tally,
5565
+ findings: input.findings,
5566
+ ...input.target !== undefined ? { target: input.target } : {}
5567
+ };
5568
+ }
5241
5569
  var SHORT_SHA_WIDTH = 7;
5242
5570
  var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
5243
5571
  function todayString4() {
@@ -5285,7 +5613,7 @@ function prReviewReportPath(opts) {
5285
5613
  const sameStem = new RegExp(`^${escaped}(?:-r([0-9]+))?\\.md$`);
5286
5614
  let dirents;
5287
5615
  try {
5288
- dirents = readdirSync9(opts.reportsDir, { withFileTypes: true });
5616
+ dirents = readdirSync10(opts.reportsDir, { withFileTypes: true });
5289
5617
  } catch (error) {
5290
5618
  if (error.code === "ENOENT")
5291
5619
  dirents = [];
@@ -5301,7 +5629,7 @@ function prReviewReportPath(opts) {
5301
5629
  }
5302
5630
  const revision = maxRevision + 1;
5303
5631
  const name = revision === 1 ? `${finalStem}.md` : `${finalStem}-r${revision}.md`;
5304
- return join14(opts.reportsDir, name);
5632
+ return join15(opts.reportsDir, name);
5305
5633
  }
5306
5634
  var PR_TIERS = ["quick", "default", "deep"];
5307
5635
  function violation14(severity, code, message, fix) {
@@ -5571,10 +5899,10 @@ function prReviewSeatPrompt(opts) {
5571
5899
  }
5572
5900
  const skillRoot = opts.skillRoot.trim();
5573
5901
  const worktreePath = opts.worktreePath.trim();
5574
- if (!isAbsolute8(skillRoot)) {
5902
+ if (!isAbsolute9(skillRoot)) {
5575
5903
  throw new TypeError(`prReviewSeatPrompt: skillRoot must be an absolute path - got ${JSON.stringify(opts.skillRoot)}`);
5576
5904
  }
5577
- if (!isAbsolute8(worktreePath)) {
5905
+ if (!isAbsolute9(worktreePath)) {
5578
5906
  throw new TypeError(`prReviewSeatPrompt: worktreePath must be an absolute path - got ${JSON.stringify(opts.worktreePath)}`);
5579
5907
  }
5580
5908
  const slug = `${domain}-${seat}`;
@@ -5598,14 +5926,14 @@ function prReviewSeatPrompt(opts) {
5598
5926
  lines.push("");
5599
5927
  lines.push("## Read first");
5600
5928
  lines.push("");
5601
- const prReviewRef = join14(skillRoot, "references", "pr-review.md");
5929
+ const prReviewRef = join15(skillRoot, "references", "pr-review.md");
5602
5930
  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";
5603
5931
  lines.push(`1. \`${prReviewRef}\` — read at least these sections: ${sections}.`);
5604
5932
  lines.push(`2. The review worktree: \`${worktreePath}\` — your ONLY working directory this session; read-only (no edits, no fixes, no stash, no commits, no posts).`);
5605
5933
  if (opts.stage === 2) {
5606
- lines.push(`3. \`${join14(skillRoot, "references", "finding-format.md")}\` — the template every finding follows.`);
5934
+ lines.push(`3. \`${join15(skillRoot, "references", "finding-format.md")}\` — the template every finding follows.`);
5607
5935
  if (opts.securitySeat === true) {
5608
- lines.push(`4. \`${join14(skillRoot, "references", "security-review.md")}\` — the security lens.`);
5936
+ lines.push(`4. \`${join15(skillRoot, "references", "security-review.md")}\` — the security lens.`);
5609
5937
  }
5610
5938
  }
5611
5939
  lines.push("");
@@ -5789,6 +6117,7 @@ export {
5789
6117
  validateProjectRegister,
5790
6118
  validatePrReviewReport,
5791
6119
  validatePlanRow,
6120
+ validateMstarReviewV1,
5792
6121
  validateIntegrationMergeLease,
5793
6122
  validateGitignore,
5794
6123
  validateFindingDoc,
@@ -5801,9 +6130,11 @@ export {
5801
6130
  techDebtRollup,
5802
6131
  taskReportExists,
5803
6132
  taskBrief,
6133
+ synthesizeReview,
5804
6134
  supplyChainChecks,
5805
6135
  stripFrontmatter,
5806
6136
  singleReviewSnapshot,
6137
+ setArtifactStore,
5807
6138
  sddWorkspace,
5808
6139
  scopeGuard,
5809
6140
  scanSecrets,
@@ -5827,6 +6158,7 @@ export {
5827
6158
  resolveHarnessDir,
5828
6159
  resolveCompassEnforcement,
5829
6160
  resolveAssetPath,
6161
+ resolveArtifactPath,
5830
6162
  releaseLease,
5831
6163
  registerWorkflow,
5832
6164
  referenceExists,
@@ -5853,6 +6185,7 @@ export {
5853
6185
  parseAssignmentBranchForms,
5854
6186
  normalizeSeverity,
5855
6187
  migrateHarnessTree,
6188
+ loadStoreModule,
5856
6189
  listProjectReferenceFiles,
5857
6190
  lintStrategySections,
5858
6191
  lintSkillFrontmatter,
@@ -5863,6 +6196,7 @@ export {
5863
6196
  l1PreDispatchCheck,
5864
6197
  isReadOnlyAssignmentRole,
5865
6198
  implementerSessionStickyRules,
6199
+ getArtifactStore,
5866
6200
  findingsCleanupGate,
5867
6201
  findTemporaryMarkers,
5868
6202
  findSimplifyMarkers,
@@ -5873,6 +6207,7 @@ export {
5873
6207
  emitGitignoreSnippet,
5874
6208
  detectHost,
5875
6209
  detectHarnessKind,
6210
+ createFsStore,
5876
6211
  computePrTally,
5877
6212
  compoundRefreshScope,
5878
6213
  composeDispatchGate,
@@ -5888,6 +6223,7 @@ export {
5888
6223
  assertLightDarkParity,
5889
6224
  assertIndexRows,
5890
6225
  assertIndexRowObligations,
6226
+ assertFsStorePath,
5891
6227
  assertDefaultBranchProtected,
5892
6228
  assertControlVsFeaturePath,
5893
6229
  assertBranchAlignment,