@mstar-harness/engine 3.4.1 → 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>"';
@@ -1008,7 +1166,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
1008
1166
 
1009
1167
  // src/workflow.ts
1010
1168
  import { mkdirSync as mkdirSync3 } from "node:fs";
1011
- import { join as join5 } from "node:path";
1169
+ import { join as join6 } from "node:path";
1012
1170
  var WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
1013
1171
  var WORKFLOW_LIFECYCLE_STATUSES = ["running", "paused", "completed", "failed", "stopped"];
1014
1172
  var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
@@ -1125,10 +1283,12 @@ async function writeWorkflowSnapshot(snapshot, dir) {
1125
1283
  const detail = gate.violations.map((v) => v.message).join("; ");
1126
1284
  throw new Error(`refusing to write invalid workflow snapshot: ${detail}`);
1127
1285
  }
1128
- 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);
1129
1289
  mkdirSync3(dir, { recursive: true });
1130
- await withStatusWriteLock(snapshotPath, () => {
1131
- writeJson(snapshotPath, snapshot);
1290
+ await withStatusWriteLock(snapshotPath, async () => {
1291
+ await store.put({ kind: "snapshot", key: snapshot.id, payload: snapshot });
1132
1292
  });
1133
1293
  }
1134
1294
 
@@ -1295,7 +1455,7 @@ function validateStatusV2(docOrPath, opts = {}) {
1295
1455
  if (typeof docOrPath === "string") {
1296
1456
  try {
1297
1457
  doc = readJson(docOrPath);
1298
- harnessDir = dirname4(resolve4(docOrPath));
1458
+ harnessDir = dirname4(resolve5(docOrPath));
1299
1459
  } catch (error) {
1300
1460
  return {
1301
1461
  ok: false,
@@ -1362,8 +1522,8 @@ function validateStatusV2(docOrPath, opts = {}) {
1362
1522
  for (const entry of doc.workflows) {
1363
1523
  if (!isPlainObject4(entry) || typeof entry.dir !== "string")
1364
1524
  continue;
1365
- const relSnapshot = join6(entry.dir, WORKFLOW_SNAPSHOT_FILE);
1366
- const snapshotPath = join6(harnessDir, relSnapshot);
1525
+ const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
1526
+ const snapshotPath = join7(harnessDir, relSnapshot);
1367
1527
  const label = typeof entry.id === "string" ? entry.id : relSnapshot;
1368
1528
  let physical;
1369
1529
  try {
@@ -1397,8 +1557,10 @@ function validateStatusV2(docOrPath, opts = {}) {
1397
1557
  return { ok: violations.length === 0, violations };
1398
1558
  }
1399
1559
  var validateStatus = validateStatusV2;
1400
- function registerWorkflowEntryLocked(statusPath, entry) {
1560
+ async function registerWorkflowEntryLocked(statusPath, entry) {
1401
1561
  const harnessDir = dirname4(statusPath);
1562
+ const store = getArtifactStore();
1563
+ assertFsStorePath(store, { kind: "status", key: "root" }, statusPath);
1402
1564
  const current = readJson(statusPath);
1403
1565
  const fresh = Object.keys(current).length === 0;
1404
1566
  const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
@@ -1416,7 +1578,7 @@ function registerWorkflowEntryLocked(statusPath, entry) {
1416
1578
  if (!gate.ok) {
1417
1579
  throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
1418
1580
  }
1419
- writeJson(statusPath, doc);
1581
+ await store.put({ kind: "status", key: "root", payload: doc });
1420
1582
  return doc;
1421
1583
  }
1422
1584
  async function registerWorkflow(root, entry) {
@@ -1424,16 +1586,18 @@ async function registerWorkflow(root, entry) {
1424
1586
  if (!entryGate.ok) {
1425
1587
  throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
1426
1588
  }
1427
- const statusPath = resolve4(root);
1589
+ const statusPath = resolve5(root);
1428
1590
  return withStatusWriteLock(statusPath, () => registerWorkflowEntryLocked(statusPath, entry));
1429
1591
  }
1430
1592
  async function unregisterWorkflow(root, id) {
1431
1593
  if (typeof id !== "string" || id.trim() === "") {
1432
1594
  throw new Error("refusing to unregister workflow: id must be a non-empty string");
1433
1595
  }
1434
- const statusPath = resolve4(root);
1596
+ const statusPath = resolve5(root);
1597
+ const store = getArtifactStore();
1598
+ assertFsStorePath(store, { kind: "status", key: "root" }, statusPath);
1435
1599
  const harnessDir = dirname4(statusPath);
1436
- return withStatusWriteLock(statusPath, () => {
1600
+ return withStatusWriteLock(statusPath, async () => {
1437
1601
  const current = readJson(statusPath);
1438
1602
  if (Object.keys(current).length === 0) {
1439
1603
  return { version: 2, updated_at: todayString(), workflows: [] };
@@ -1452,25 +1616,25 @@ async function unregisterWorkflow(root, id) {
1452
1616
  if (!gate.ok) {
1453
1617
  throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
1454
1618
  }
1455
- writeJson(statusPath, doc);
1619
+ await store.put({ kind: "status", key: "root", payload: doc });
1456
1620
  return doc;
1457
1621
  });
1458
1622
  }
1459
1623
  function resolveCompassEnforcement(harnessDir) {
1460
1624
  const iterationsDir = resolveIterationDir(harnessDir);
1461
- if (!existsSync3(iterationsDir))
1625
+ if (!existsSync4(iterationsDir))
1462
1626
  return { hard: false, source: "none" };
1463
1627
  let entries;
1464
1628
  try {
1465
- entries = readdirSync2(iterationsDir, { withFileTypes: true });
1629
+ entries = readdirSync3(iterationsDir, { withFileTypes: true });
1466
1630
  } catch {
1467
1631
  return { hard: false, source: "none" };
1468
1632
  }
1469
1633
  for (const entry of entries) {
1470
1634
  if (!entry.isDirectory())
1471
1635
  continue;
1472
- const compassPath = join6(iterationsDir, entry.name, "delivery-compass.md");
1473
- if (!existsSync3(compassPath))
1636
+ const compassPath = join7(iterationsDir, entry.name, "delivery-compass.md");
1637
+ if (!existsSync4(compassPath))
1474
1638
  continue;
1475
1639
  let content;
1476
1640
  try {
@@ -1489,7 +1653,7 @@ function resolveCompassEnforcement(harnessDir) {
1489
1653
  return { hard: false, source: "none" };
1490
1654
  }
1491
1655
  function resolveMstarcEnforcement(harnessDir) {
1492
- const dir = resolve4(harnessDir);
1656
+ const dir = resolve5(harnessDir);
1493
1657
  const rc = loadMstarc(dir, dirname4(dir));
1494
1658
  const value = rc?.config.enforcement;
1495
1659
  if (value === "hard")
@@ -1641,9 +1805,12 @@ async function appendProjectRegisterEntries(opts) {
1641
1805
  if (opts.entries.length === 0) {
1642
1806
  throw new Error("refusing to append residual entries: entries must not be empty");
1643
1807
  }
1644
- 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);
1645
1812
  mkdirSync4(opts.projectDir, { recursive: true });
1646
- return withStatusWriteLock(registerPath, () => {
1813
+ return withStatusWriteLock(registerPath, async () => {
1647
1814
  const doc = readJson(registerPath);
1648
1815
  const entriesMap = doc.entries ?? {};
1649
1816
  let key = opts.basePlanKey;
@@ -1678,14 +1845,17 @@ async function appendProjectRegisterEntries(opts) {
1678
1845
  if (!gate.ok) {
1679
1846
  throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
1680
1847
  }
1681
- writeJson(registerPath, register);
1848
+ await store.put({ kind: "residuals", key: projectKey, payload: register });
1682
1849
  return { ok: true, key };
1683
1850
  });
1684
1851
  }
1685
1852
  async function closeProjectRegisterEntry(opts) {
1686
- 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);
1687
1857
  mkdirSync4(opts.projectDir, { recursive: true });
1688
- return withStatusWriteLock(registerPath, () => {
1858
+ return withStatusWriteLock(registerPath, async () => {
1689
1859
  const doc = readJson(registerPath);
1690
1860
  const planEntries = doc.entries?.[opts.planKey];
1691
1861
  if (!Array.isArray(planEntries)) {
@@ -1705,7 +1875,7 @@ async function closeProjectRegisterEntry(opts) {
1705
1875
  if (!gate.ok) {
1706
1876
  throw new Error(`refusing to write invalid project register: ${gate.violations.map((v) => v.message).join("; ")}`);
1707
1877
  }
1708
- writeJson(registerPath, register);
1878
+ await store.put({ kind: "residuals", key: projectKey, payload: register });
1709
1879
  return { ok: true };
1710
1880
  });
1711
1881
  }
@@ -1758,15 +1928,15 @@ function techDebtRollup(projectDir) {
1758
1928
  const items = [];
1759
1929
  let entries;
1760
1930
  try {
1761
- entries = readdirSync3(projectDir, { withFileTypes: true });
1931
+ entries = readdirSync4(projectDir, { withFileTypes: true });
1762
1932
  } catch {
1763
1933
  entries = [];
1764
1934
  }
1765
1935
  for (const project of entries) {
1766
1936
  if (!project.isDirectory())
1767
1937
  continue;
1768
- const registerPath = join7(projectDir, project.name, PROJECT_REGISTER_FILE);
1769
- if (!existsSync4(registerPath))
1938
+ const registerPath = join8(projectDir, project.name, PROJECT_REGISTER_FILE);
1939
+ if (!existsSync5(registerPath))
1770
1940
  continue;
1771
1941
  let register;
1772
1942
  try {
@@ -1802,10 +1972,10 @@ function techDebtRollup(projectDir) {
1802
1972
  return { computed, stored, checks, overall };
1803
1973
  }
1804
1974
  function listProjectReferenceFiles(projectDir) {
1805
- const root = join7(projectDir, PROJECT_REFERENCES_DIR);
1975
+ const root = join8(projectDir, PROJECT_REFERENCES_DIR);
1806
1976
  let entries;
1807
1977
  try {
1808
- entries = readdirSync3(root, { withFileTypes: true });
1978
+ entries = readdirSync4(root, { withFileTypes: true });
1809
1979
  } catch {
1810
1980
  return [];
1811
1981
  }
@@ -1818,7 +1988,7 @@ function listProjectReferenceFiles(projectDir) {
1818
1988
  } else if (entry.isDirectory()) {
1819
1989
  let nested;
1820
1990
  try {
1821
- nested = readdirSync3(join7(root, entry.name), { withFileTypes: true });
1991
+ nested = readdirSync4(join8(root, entry.name), { withFileTypes: true });
1822
1992
  } catch {
1823
1993
  continue;
1824
1994
  }
@@ -1833,19 +2003,19 @@ function listProjectReferenceFiles(projectDir) {
1833
2003
 
1834
2004
  // src/path.ts
1835
2005
  function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
1836
- const start = resolve6(startDir);
2006
+ const start = resolve7(startDir);
1837
2007
  const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
1838
2008
  if (explicit)
1839
- return resolve6(start, explicit);
1840
- const boundary = resolve6(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
2009
+ return resolve7(start, explicit);
2010
+ const boundary = resolve7(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
1841
2011
  const rc = loadMstarc(start, boundary);
1842
2012
  if (rc !== null && rc.config.harnessDir)
1843
- return resolve6(rc.dir, rc.config.harnessDir);
2013
+ return resolve7(rc.dir, rc.config.harnessDir);
1844
2014
  let dir = start;
1845
2015
  for (;; ) {
1846
2016
  if (!isAtOrBelow2(dir, boundary))
1847
2017
  return null;
1848
- 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")]) {
1849
2019
  if (isDirectory(candidate))
1850
2020
  return candidate;
1851
2021
  }
@@ -1871,19 +2041,19 @@ function defaultWorkspaceRoot(startDir) {
1871
2041
  if (segment && segment !== ".")
1872
2042
  boundary = dirname5(boundary);
1873
2043
  }
1874
- return resolve6(boundary);
2044
+ return resolve7(boundary);
1875
2045
  } catch {}
1876
2046
  return startDir;
1877
2047
  }
1878
2048
  function isAtOrBelow2(dir, root) {
1879
2049
  const rel = relative2(root, dir);
1880
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
2050
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
1881
2051
  }
1882
2052
  function mstarcDirOverride(harnessDir, key) {
1883
- const dir = resolve6(harnessDir);
2053
+ const dir = resolve7(harnessDir);
1884
2054
  const rc = loadMstarc(dir, dirname5(dir));
1885
2055
  const declared = rc?.config[key];
1886
- return declared ? resolve6(rc.dir, declared) : null;
2056
+ return declared ? resolve7(rc.dir, declared) : null;
1887
2057
  }
1888
2058
  function resolveSpecsDir(harnessDir, opts = {}) {
1889
2059
  const declared = mstarcDirOverride(harnessDir, "specsDir");
@@ -1892,20 +2062,20 @@ function resolveSpecsDir(harnessDir, opts = {}) {
1892
2062
  mkdirSync5(declared, { recursive: true });
1893
2063
  return declared;
1894
2064
  }
1895
- const harness = resolve6(harnessDir);
2065
+ const harness = resolve7(harnessDir);
1896
2066
  const repoRoot = dirname5(harness);
1897
2067
  const candidates = [
1898
- join8(harness, "specs"),
1899
- join8(repoRoot, "docs", "specs"),
1900
- join8(repoRoot, "specs"),
1901
- join8(harness, "designs"),
1902
- join8(repoRoot, "designs")
2068
+ join9(harness, "specs"),
2069
+ join9(repoRoot, "docs", "specs"),
2070
+ join9(repoRoot, "specs"),
2071
+ join9(harness, "designs"),
2072
+ join9(repoRoot, "designs")
1903
2073
  ];
1904
2074
  for (const candidate of candidates) {
1905
2075
  if (isDirectory(candidate) && hasFiles(candidate))
1906
2076
  return candidate;
1907
2077
  }
1908
- const fallback = join8(harness, "specs");
2078
+ const fallback = join9(harness, "specs");
1909
2079
  if (opts.create !== false)
1910
2080
  mkdirSync5(fallback, { recursive: true });
1911
2081
  return fallback;
@@ -1914,11 +2084,11 @@ function resolvePlanDir(harnessDir) {
1914
2084
  const declared = mstarcDirOverride(harnessDir, "planDir");
1915
2085
  if (declared !== null)
1916
2086
  return declared;
1917
- const dir = resolve6(harnessDir);
1918
- const name = basename2(dir);
2087
+ const dir = resolve7(harnessDir);
2088
+ const name = basename3(dir);
1919
2089
  if (name === ".plans" || name === "plans")
1920
2090
  return dir;
1921
- return join8(dir, "plans");
2091
+ return join9(dir, "plans");
1922
2092
  }
1923
2093
  function assertSafePathComponent(value, what) {
1924
2094
  if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
@@ -1927,30 +2097,30 @@ function assertSafePathComponent(value, what) {
1927
2097
  }
1928
2098
  function resolveSddDir(harnessDir, planId) {
1929
2099
  assertSafePathComponent(planId, "planId");
1930
- const base = resolve6(harnessDir);
2100
+ const base = resolve7(harnessDir);
1931
2101
  const declared = mstarcDirOverride(base, "sddDir");
1932
- const sddBase = declared !== null ? declared : join8(base, "sdd");
1933
- return join8(sddBase, planId);
2102
+ const sddBase = declared !== null ? declared : join9(base, "sdd");
2103
+ return join9(sddBase, planId);
1934
2104
  }
1935
2105
  function resolveIterationDir(harnessDir) {
1936
2106
  const declared = mstarcDirOverride(harnessDir, "iterationDir");
1937
2107
  if (declared !== null)
1938
2108
  return declared;
1939
- return join8(resolve6(harnessDir), "iterations");
2109
+ return join9(resolve7(harnessDir), "iterations");
1940
2110
  }
1941
2111
  function resolveKnowledgeDir(harnessDir) {
1942
2112
  const declared = mstarcDirOverride(harnessDir, "knowledgeDir");
1943
2113
  if (declared !== null)
1944
2114
  return declared;
1945
- return join8(resolve6(harnessDir), "knowledge");
2115
+ return join9(resolve7(harnessDir), "knowledge");
1946
2116
  }
1947
2117
  function resolveHarnessSubdir(startDir, opts, key, fallback) {
1948
2118
  const harness = resolveHarnessDir(startDir, opts);
1949
2119
  if (harness === null) {
1950
- 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)`);
1951
2121
  }
1952
2122
  const declared = mstarcDirOverride(harness, key);
1953
- return declared !== null ? declared : join8(resolve6(harness), fallback);
2123
+ return declared !== null ? declared : join9(resolve7(harness), fallback);
1954
2124
  }
1955
2125
  function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
1956
2126
  return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
@@ -1965,13 +2135,13 @@ var EMPTY_STATUS_TEMPLATE = {
1965
2135
  };
1966
2136
  var SCAFFOLD_DIRS = ["plans", "iterations", "knowledge", "specs", "sdd"];
1967
2137
  function resolveScaffoldDirs(root) {
1968
- const start = resolve6(root);
1969
- const boundary = resolve6(start, defaultWorkspaceRoot(start));
2138
+ const start = resolve7(root);
2139
+ const boundary = resolve7(start, defaultWorkspaceRoot(start));
1970
2140
  const rc = loadMstarc(start, boundary);
1971
2141
  const explicit = process.env.MSTAR_HARNESS_DIR;
1972
- 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");
1973
2143
  const declaredProjectDir = mstarcDirOverride(harnessDir, "projectDir");
1974
- const projectDir = declaredProjectDir !== null ? declaredProjectDir : join8(harnessDir, "projects");
2144
+ const projectDir = declaredProjectDir !== null ? declaredProjectDir : join9(harnessDir, "projects");
1975
2145
  return { harnessDir, projectDir };
1976
2146
  }
1977
2147
  var ROADMAP_TEMPLATE = `---
@@ -1993,18 +2163,18 @@ var EMPTY_REGISTER_TEMPLATE = {
1993
2163
  function scaffoldHarness(root) {
1994
2164
  const { harnessDir, projectDir } = resolveScaffoldDirs(root);
1995
2165
  for (const dir of SCAFFOLD_DIRS)
1996
- mkdirSync5(join8(harnessDir, dir), { recursive: true });
1997
- const statusPath = join8(harnessDir, "status.json");
2166
+ mkdirSync5(join9(harnessDir, dir), { recursive: true });
2167
+ const statusPath = join9(harnessDir, "status.json");
1998
2168
  if (Object.keys(readJson(statusPath)).length === 0)
1999
2169
  writeJson(statusPath, EMPTY_STATUS_TEMPLATE);
2000
- const defaultProjectDir = join8(projectDir, _DEFAULT_PROJECT);
2170
+ const defaultProjectDir = join9(projectDir, _DEFAULT_PROJECT);
2001
2171
  mkdirSync5(defaultProjectDir, { recursive: true });
2002
- const roadmapPath = join8(defaultProjectDir, PROJECT_ROADMAP_FILE);
2003
- if (!existsSync5(roadmapPath)) {
2172
+ const roadmapPath = join9(defaultProjectDir, PROJECT_ROADMAP_FILE);
2173
+ if (!existsSync6(roadmapPath)) {
2004
2174
  const created = new Date().toISOString().slice(0, 10);
2005
2175
  writeFileSync3(roadmapPath, ROADMAP_TEMPLATE.replace("{created_at}", created), "utf8");
2006
2176
  }
2007
- const registerPath = join8(defaultProjectDir, PROJECT_REGISTER_FILE);
2177
+ const registerPath = join9(defaultProjectDir, PROJECT_REGISTER_FILE);
2008
2178
  if (Object.keys(readJson(registerPath)).length === 0)
2009
2179
  writeJson(registerPath, EMPTY_REGISTER_TEMPLATE);
2010
2180
  return harnessDir;
@@ -2042,7 +2212,7 @@ function emitGitignoreSnippet(kind) {
2042
2212
  return `${GITIGNORE_SNIPPET}${GITIGNORE_SNIPPET_AGENTS}`;
2043
2213
  }
2044
2214
  function validateGitignore(root) {
2045
- const gitignorePath = join8(resolve6(root), ".gitignore");
2215
+ const gitignorePath = join9(resolve7(root), ".gitignore");
2046
2216
  const kind = detectHarnessKind(resolveHarnessDir(root));
2047
2217
  let content;
2048
2218
  try {
@@ -2090,7 +2260,7 @@ function validateGitignore(root) {
2090
2260
  function detectHarnessKind(harnessDir) {
2091
2261
  if (!harnessDir)
2092
2262
  return null;
2093
- const name = basename2(resolve6(harnessDir));
2263
+ const name = basename3(resolve7(harnessDir));
2094
2264
  if (name === ".mstar")
2095
2265
  return "mstar";
2096
2266
  if (name === ".agents")
@@ -2098,7 +2268,7 @@ function detectHarnessKind(harnessDir) {
2098
2268
  return null;
2099
2269
  }
2100
2270
  function assertPlanWritingPath(planPath, harnessDir) {
2101
- const planAbs = resolve6(planPath);
2271
+ const planAbs = resolve7(planPath);
2102
2272
  if (!harnessDir) {
2103
2273
  return {
2104
2274
  ok: false,
@@ -2110,7 +2280,7 @@ function assertPlanWritingPath(planPath, harnessDir) {
2110
2280
  }
2111
2281
  const planDir = resolvePlanDir(harnessDir);
2112
2282
  const rel = relative2(planDir, planAbs);
2113
- const inside = rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
2283
+ const inside = rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
2114
2284
  if (!inside) {
2115
2285
  return {
2116
2286
  ok: false,
@@ -2120,12 +2290,12 @@ function assertPlanWritingPath(planPath, harnessDir) {
2120
2290
  fix: `write the plan under ${planDir}`
2121
2291
  };
2122
2292
  }
2123
- if (existsSync5(planAbs)) {
2293
+ if (existsSync6(planAbs)) {
2124
2294
  try {
2125
2295
  const canonicalPlan = realpathSync2(planAbs);
2126
- const canonicalPlanDir = existsSync5(planDir) ? realpathSync2(planDir) : resolve6(planDir);
2296
+ const canonicalPlanDir = existsSync6(planDir) ? realpathSync2(planDir) : resolve7(planDir);
2127
2297
  const canonicalRel = relative2(canonicalPlanDir, canonicalPlan);
2128
- const canonicalInside = canonicalRel === "" || !canonicalRel.startsWith("..") && !isAbsolute3(canonicalRel);
2298
+ const canonicalInside = canonicalRel === "" || !canonicalRel.startsWith("..") && !isAbsolute4(canonicalRel);
2129
2299
  if (!canonicalInside) {
2130
2300
  return {
2131
2301
  ok: false,
@@ -2153,9 +2323,9 @@ function isDirectory(dir) {
2153
2323
  }
2154
2324
  function hasFiles(dir) {
2155
2325
  try {
2156
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
2326
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2157
2327
  if (entry.isDirectory()) {
2158
- if (hasFiles(join8(dir, entry.name)))
2328
+ if (hasFiles(join9(dir, entry.name)))
2159
2329
  return true;
2160
2330
  } else if (entry.isFile()) {
2161
2331
  return true;
@@ -2168,8 +2338,8 @@ function hasFiles(dir) {
2168
2338
  }
2169
2339
  // src/worktree.ts
2170
2340
  import { execFileSync as execFileSync2 } from "node:child_process";
2171
- import { existsSync as existsSync6 } from "node:fs";
2172
- 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";
2173
2343
  var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
2174
2344
  function probeTimeoutMs() {
2175
2345
  const raw = process.env.MSTAR_GIT_PROBE_TIMEOUT_MS;
@@ -2220,10 +2390,10 @@ function l1PreDispatchCheck(input, opts = {}) {
2220
2390
  if (leaseWorkingBranch.trim() === "") {
2221
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"));
2222
2392
  }
2223
- if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve7(controlWorktreePath) === resolve7(leaseWorktreePath)) {
2393
+ if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
2224
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"));
2225
2395
  }
2226
- if (leaseWorktreePath !== "" && !existsSync6(leaseWorktreePath)) {
2396
+ if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
2227
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>`));
2228
2398
  } else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
2229
2399
  const probe = probeBranch(leaseWorktreePath, opts);
@@ -2247,17 +2417,17 @@ function l2PreDispatchCheck(input, opts = {}) {
2247
2417
  violations.push(violation7("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
2248
2418
  return;
2249
2419
  }
2250
- if (!isAbsolute4(track.worktreePath)) {
2420
+ if (!isAbsolute5(track.worktreePath)) {
2251
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>)`));
2252
2422
  return;
2253
2423
  }
2254
- const normalized = resolve7(track.worktreePath);
2424
+ const normalized = resolve8(track.worktreePath);
2255
2425
  if (seenPaths.has(normalized)) {
2256
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"));
2257
2427
  return;
2258
2428
  }
2259
2429
  seenPaths.add(normalized);
2260
- if (!existsSync6(track.worktreePath)) {
2430
+ if (!existsSync7(track.worktreePath)) {
2261
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}`));
2262
2432
  return;
2263
2433
  }
@@ -2272,7 +2442,7 @@ function l2PreDispatchCheck(input, opts = {}) {
2272
2442
  }
2273
2443
  function assertControlVsFeaturePath(controlWorktreePath, featureWorktreePath) {
2274
2444
  const violations = [];
2275
- const samePath = controlWorktreePath === "" && featureWorktreePath === "" || controlWorktreePath !== "" && featureWorktreePath !== "" && resolve7(controlWorktreePath) === resolve7(featureWorktreePath);
2445
+ const samePath = controlWorktreePath === "" && featureWorktreePath === "" || controlWorktreePath !== "" && featureWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(featureWorktreePath);
2276
2446
  if (samePath) {
2277
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"));
2278
2448
  }
@@ -2320,8 +2490,8 @@ function singleReviewSnapshot(assignments) {
2320
2490
  }
2321
2491
  // src/sdd.ts
2322
2492
  import { execFileSync as execFileSync3 } from "node:child_process";
2323
- import { mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
2324
- 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";
2325
2495
  class SddScriptError extends Error {
2326
2496
  exitCode;
2327
2497
  constructor(message, exitCode) {
@@ -2358,14 +2528,14 @@ function gitOut(cwd, args) {
2358
2528
  }
2359
2529
  }
2360
2530
  function probeHarnessWithStatus(root) {
2361
- if (isFile2(join9(root, ".mstar", "status.json")))
2362
- return join9(root, ".mstar");
2363
- if (isFile2(join9(root, ".agents", "status.json")))
2364
- return join9(root, ".agents");
2365
- if (hasWorkflowSnapshot(join9(root, ".mstar")))
2366
- return join9(root, ".mstar");
2367
- if (hasWorkflowSnapshot(join9(root, ".agents")))
2368
- 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");
2369
2539
  return null;
2370
2540
  }
2371
2541
  function hasWorkflowSnapshot(harnessDir) {
@@ -2373,13 +2543,13 @@ function hasWorkflowSnapshot(harnessDir) {
2373
2543
  try {
2374
2544
  workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
2375
2545
  } catch {
2376
- workflowsDir = join9(harnessDir, "workflows");
2546
+ workflowsDir = join10(harnessDir, "workflows");
2377
2547
  }
2378
2548
  if (!isDirectory2(workflowsDir))
2379
2549
  return false;
2380
2550
  try {
2381
- for (const entry of readdirSync5(workflowsDir, { withFileTypes: true })) {
2382
- 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")))
2383
2553
  return true;
2384
2554
  }
2385
2555
  } catch {
@@ -2392,14 +2562,14 @@ function isLinkedWorktree(root) {
2392
2562
  const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
2393
2563
  if (gitDirRaw === null || commonRaw === null)
2394
2564
  return false;
2395
- const gitDir = isAbsolute5(gitDirRaw) ? gitDirRaw : join9(root, gitDirRaw);
2396
- 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);
2397
2567
  if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
2398
2568
  return true;
2399
2569
  try {
2400
2570
  const gdParent = realpathSync3(dirname6(gitDir));
2401
2571
  const cmAbs = realpathSync3(common);
2402
- return join9(gdParent, basename3(gitDir)) !== cmAbs && gitDir !== cmAbs;
2572
+ return join10(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
2403
2573
  } catch {
2404
2574
  return false;
2405
2575
  }
@@ -2430,28 +2600,28 @@ function sddWorkspace(planId, opts = {}) {
2430
2600
  const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
2431
2601
  let harnessDir;
2432
2602
  if (harnessOverride) {
2433
- harnessDir = resolve8(root, harnessOverride);
2603
+ harnessDir = resolve9(root, harnessOverride);
2434
2604
  } else {
2435
2605
  const rc = findMstarc(root, root);
2436
2606
  const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
2437
2607
  if (rcHarnessDir) {
2438
- harnessDir = resolve8(rc !== null ? dirname6(rc) : root, rcHarnessDir);
2608
+ harnessDir = resolve9(rc !== null ? dirname6(rc) : root, rcHarnessDir);
2439
2609
  } else {
2440
2610
  const probed = probeHarnessWithStatus(root);
2441
2611
  if (probed) {
2442
2612
  harnessDir = probed;
2443
- } else if (isDirectory2(join9(root, ".mstar"))) {
2444
- harnessDir = join9(root, ".mstar");
2445
- } else if (isDirectory2(join9(root, ".agents"))) {
2446
- 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");
2447
2617
  } else {
2448
- harnessDir = join9(root, ".mstar");
2618
+ harnessDir = join10(root, ".mstar");
2449
2619
  }
2450
2620
  }
2451
2621
  }
2452
2622
  const sddDir = resolveSddDir(harnessDir, planId);
2453
2623
  mkdirSync6(sddDir, { recursive: true });
2454
- writeFileSync4(join9(sddDir, ".gitignore"), `*
2624
+ writeFileSync4(join10(sddDir, ".gitignore"), `*
2455
2625
  `);
2456
2626
  return realpathSync3(sddDir);
2457
2627
  }
@@ -2474,7 +2644,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2474
2644
  throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
2475
2645
  }
2476
2646
  mkdirSync6(sddDir, { recursive: true });
2477
- out = join9(sddDir, `task-${taskN}-brief.md`);
2647
+ out = join10(sddDir, `task-${taskN}-brief.md`);
2478
2648
  }
2479
2649
  const records = content.endsWith(`
2480
2650
  `) ? content.split(`
@@ -2527,7 +2697,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
2527
2697
  mkdirSync6(sddDir, { recursive: true });
2528
2698
  const shortBase = gitOut(cwd, ["rev-parse", "--short", base]) ?? base;
2529
2699
  const shortHead = gitOut(cwd, ["rev-parse", "--short", head]) ?? head;
2530
- out = join9(sddDir, `review-${shortBase}..${shortHead}.diff`);
2700
+ out = join10(sddDir, `review-${shortBase}..${shortHead}.diff`);
2531
2701
  }
2532
2702
  const run = (args) => execFileSync3("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
2533
2703
  const parts = [
@@ -2563,7 +2733,7 @@ function assertBaseSha(ref, opts = {}) {
2563
2733
  }
2564
2734
  function taskReportExists(sddDir, taskN) {
2565
2735
  try {
2566
- const st = statSync4(join9(sddDir, `task-${taskN}-report.md`));
2736
+ const st = statSync4(join10(sddDir, `task-${taskN}-report.md`));
2567
2737
  return st.isFile() && st.size > 0;
2568
2738
  } catch {
2569
2739
  return false;
@@ -2572,7 +2742,7 @@ function taskReportExists(sddDir, taskN) {
2572
2742
  function readProgressLedger(sddDir) {
2573
2743
  let content;
2574
2744
  try {
2575
- content = readFileSync7(join9(sddDir, "progress.md"), "utf8");
2745
+ content = readFileSync7(join10(sddDir, "progress.md"), "utf8");
2576
2746
  } catch {
2577
2747
  return [];
2578
2748
  }
@@ -2605,8 +2775,8 @@ function implementerSessionStickyRules(input) {
2605
2775
  return { resume: true, reason: `sticky resume OK: host_agent_id ${session.host_agent_id}, next task ${nextTask}` };
2606
2776
  }
2607
2777
  // src/migrate.ts
2608
- import { copyFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
2609
- 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";
2610
2780
  var MIGRATE_STATUS_FILE = "status.json";
2611
2781
  var ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json";
2612
2782
  var NOTES_LEDGER_FILE = "notes.jsonl";
@@ -2646,18 +2816,18 @@ function todayString3() {
2646
2816
  return `${now.getFullYear()}-${month}-${day}`;
2647
2817
  }
2648
2818
  function scanCompasses(harnessDir) {
2649
- const iterationsDir = join10(harnessDir, "iterations");
2819
+ const iterationsDir = join11(harnessDir, "iterations");
2650
2820
  const out = [];
2651
2821
  let entries;
2652
2822
  try {
2653
- entries = readdirSync6(iterationsDir, { withFileTypes: true });
2823
+ entries = readdirSync7(iterationsDir, { withFileTypes: true });
2654
2824
  } catch {
2655
2825
  return out;
2656
2826
  }
2657
2827
  for (const entry of entries) {
2658
2828
  if (!entry.isDirectory())
2659
2829
  continue;
2660
- const compassPath = join10(iterationsDir, entry.name, "delivery-compass.md");
2830
+ const compassPath = join11(iterationsDir, entry.name, "delivery-compass.md");
2661
2831
  let content;
2662
2832
  try {
2663
2833
  content = readFileSync8(compassPath, "utf8");
@@ -2737,8 +2907,8 @@ function buildIterationSnapshot(compass, rowById, rootUpdatedAt) {
2737
2907
  id: compass.id,
2738
2908
  type: "iteration",
2739
2909
  status,
2740
- file: join10("workflows", compass.id, WORKFLOW_SNAPSHOT_FILE),
2741
- 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"),
2742
2912
  data: snapshot
2743
2913
  };
2744
2914
  }
@@ -2773,7 +2943,7 @@ function buildStandaloneSnapshot(row, rootUpdatedAt, migrationNotes) {
2773
2943
  id,
2774
2944
  type: "plan",
2775
2945
  status,
2776
- file: join10("workflows", id, WORKFLOW_SNAPSHOT_FILE),
2946
+ file: join11("workflows", id, WORKFLOW_SNAPSHOT_FILE),
2777
2947
  source: "status.json plans[] row",
2778
2948
  data: snapshot
2779
2949
  };
@@ -2881,7 +3051,7 @@ function buildRegister(residualFindings, byPlan, projectId, migratedAt) {
2881
3051
  return null;
2882
3052
  const doc = { entries };
2883
3053
  return {
2884
- file: join10("projects", projectId, PROJECT_REGISTER_FILE),
3054
+ file: join11("projects", projectId, PROJECT_REGISTER_FILE),
2885
3055
  source: "status.json residual_findings",
2886
3056
  data: doc
2887
3057
  };
@@ -2912,7 +3082,7 @@ function collectNotesFiles(snapshots) {
2912
3082
  if (lines.length === 0)
2913
3083
  continue;
2914
3084
  out.push({
2915
- file: join10(dirname7(snapshot.file), NOTES_LEDGER_FILE),
3085
+ file: join11(dirname7(snapshot.file), NOTES_LEDGER_FILE),
2916
3086
  source,
2917
3087
  lines
2918
3088
  });
@@ -2920,11 +3090,11 @@ function collectNotesFiles(snapshots) {
2920
3090
  return out;
2921
3091
  }
2922
3092
  function migrateHarnessTree(root, opts = {}) {
2923
- const harnessDir = resolve9(root);
3093
+ const harnessDir = resolve10(root);
2924
3094
  const workflowDir = resolveWorkflowDir(harnessDir, { harnessDir });
2925
3095
  const projectDir = resolveProjectDir(harnessDir, { harnessDir });
2926
3096
  const projectId = opts.projectId ?? _DEFAULT_PROJECT;
2927
- const statusPath = join10(harnessDir, MIGRATE_STATUS_FILE);
3097
+ const statusPath = join11(harnessDir, MIGRATE_STATUS_FILE);
2928
3098
  const legacy = readJson(statusPath);
2929
3099
  if (legacy.version === 2) {
2930
3100
  const updatedAt = typeof legacy.updated_at === "string" && legacy.updated_at !== "" ? legacy.updated_at : "1970-01-01";
@@ -3020,7 +3190,7 @@ function migrateHarnessTree(root, opts = {}) {
3020
3190
  migrationNotes.push(`roadmap title sanitized for frontmatter (line breaks replaced with spaces): ${JSON.stringify(rawTitle)}`);
3021
3191
  }
3022
3192
  roadmap = {
3023
- file: join10("projects", projectId, PROJECT_ROADMAP_FILE),
3193
+ file: join11("projects", projectId, PROJECT_ROADMAP_FILE),
3024
3194
  source: "status.json metadata.program_roadmap",
3025
3195
  content: buildRoadmap({ ...programRoadmap, title: sanitizedTitle }, projectId, migratedAt)
3026
3196
  };
@@ -3069,19 +3239,19 @@ async function applyMigratePlan(plan) {
3069
3239
  if (plan.dryRun) {
3070
3240
  return { applied: false, message: `dry-run: ${plan.steps.length} steps planned (source → destination), zero writes` };
3071
3241
  }
3072
- const statusPath = join10(plan.root, MIGRATE_STATUS_FILE);
3242
+ const statusPath = join11(plan.root, MIGRATE_STATUS_FILE);
3073
3243
  const current = readJson(statusPath);
3074
3244
  if (current.version === 2) {
3075
3245
  return { applied: false, message: "no-op: status.json already at schema version 2 (migrated) — nothing to do" };
3076
3246
  }
3077
- const harnessRoot = resolve9(plan.root);
3078
- const workflowRoot = resolve9(plan.workflowDir);
3079
- const projectRoot = resolve9(plan.projectDir);
3080
- 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)) {
3081
3251
  throw new Error(`refusing to apply migration: plan workflowDir/projectDir must be absolute (got ${JSON.stringify(plan.workflowDir)} / ${JSON.stringify(plan.projectDir)})`);
3082
3252
  }
3083
- const workflowTargetOf = (canonicalFile) => join10(workflowRoot, relative3("workflows", canonicalFile));
3084
- 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));
3085
3255
  const allDestinations = [
3086
3256
  plan.archive.file,
3087
3257
  ...plan.snapshots.map((snapshot) => snapshot.file),
@@ -3090,14 +3260,14 @@ async function applyMigratePlan(plan) {
3090
3260
  ...plan.roadmap !== null ? [plan.roadmap.file] : []
3091
3261
  ];
3092
3262
  for (const destination of allDestinations) {
3093
- const resolvedDest = resolve9(join10(plan.root, destination));
3263
+ const resolvedDest = resolve10(join11(plan.root, destination));
3094
3264
  const inside = (dir) => resolvedDest === dir || resolvedDest.startsWith(`${dir}${sep2}`);
3095
3265
  if (!inside(harnessRoot) && !inside(workflowRoot) && !inside(projectRoot)) {
3096
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)})`);
3097
3267
  }
3098
3268
  }
3099
- mkdirSync7(join10(plan.root, dirname7(plan.archive.file)), { recursive: true });
3100
- 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));
3101
3271
  for (const snapshot of plan.snapshots) {
3102
3272
  await writeWorkflowSnapshot(snapshot.data, dirname7(workflowTargetOf(snapshot.file)));
3103
3273
  }
@@ -3546,8 +3716,8 @@ function completenessLevel(frontmatterText, checklist) {
3546
3716
  }
3547
3717
  // src/audit.ts
3548
3718
  import { execFileSync as execFileSync4 } from "node:child_process";
3549
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
3550
- 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";
3551
3721
  function violation9(severity, code, message, fix) {
3552
3722
  return { ok: false, severity, code, message, fix };
3553
3723
  }
@@ -3848,7 +4018,7 @@ function scanSecrets(files) {
3848
4018
  unreadableFiles++;
3849
4019
  continue;
3850
4020
  }
3851
- const base = basename4(file);
4021
+ const base = basename5(file);
3852
4022
  for (const entry of NEVER_COMMIT_FILENAMES) {
3853
4023
  if (entry.re.test(base))
3854
4024
  findings.push({ file, line: 1, type: entry.type });
@@ -3900,12 +4070,12 @@ var LOCKFILE_NAMES = [
3900
4070
  function rootLockfiles(root) {
3901
4071
  let entries;
3902
4072
  try {
3903
- entries = readdirSync7(root, { withFileTypes: true });
4073
+ entries = readdirSync8(root, { withFileTypes: true });
3904
4074
  } catch {
3905
4075
  return [];
3906
4076
  }
3907
4077
  const names = new Set(LOCKFILE_NAMES);
3908
- 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));
3909
4079
  if (present.length === 0)
3910
4080
  return [];
3911
4081
  try {
@@ -3914,7 +4084,7 @@ function rootLockfiles(root) {
3914
4084
  encoding: "utf8",
3915
4085
  stdio: ["ignore", "pipe", "ignore"]
3916
4086
  }).split("\x00").filter((f) => f !== ""));
3917
- return present.filter((p) => tracked.has(basename4(p)));
4087
+ return present.filter((p) => tracked.has(basename5(p)));
3918
4088
  } catch {
3919
4089
  return present;
3920
4090
  }
@@ -3930,17 +4100,17 @@ function supplyChainChecks(repoRoot) {
3930
4100
  findings.push({ kind: "lockfile-duplicate", file: lockfiles.map((f) => f.replace(`${repoRoot}/`, "")).join(", ") });
3931
4101
  violations.push(violation9("medium", "audit.supply.lockfile-duplicate", `multiple lockfiles at ${repoRoot}: ${lockfiles.join(", ")}`, "keep exactly one lockfile per package manager"));
3932
4102
  }
3933
- const workflowsDir = join11(repoRoot, ".github", "workflows");
4103
+ const workflowsDir = join12(repoRoot, ".github", "workflows");
3934
4104
  let wfEntries = [];
3935
4105
  try {
3936
- wfEntries = readdirSync7(workflowsDir, { withFileTypes: true });
4106
+ wfEntries = readdirSync8(workflowsDir, { withFileTypes: true });
3937
4107
  } catch {
3938
4108
  wfEntries = [];
3939
4109
  }
3940
4110
  for (const entry of wfEntries) {
3941
4111
  if (!entry.isFile() || !/\.(?:ya?ml)$/.test(entry.name))
3942
4112
  continue;
3943
- const wfPath = join11(workflowsDir, entry.name);
4113
+ const wfPath = join12(workflowsDir, entry.name);
3944
4114
  const relPath = `.github/workflows/${entry.name}`;
3945
4115
  let text;
3946
4116
  try {
@@ -4097,9 +4267,9 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4097
4267
  const date = options.date ?? new Date().toISOString().slice(0, 10);
4098
4268
  const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
4099
4269
  mkdirSync8(outDir, { recursive: true });
4100
- const existingReadme = join11(outDir, "README.md");
4101
- const carried = existsSync7(existingReadme) ? extractSecurityDispositionSections(readFileSync9(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
4102
- 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));
4103
4273
  let next = existing.reduce((max, f) => Math.max(max, Number(f.slice(0, 3))), 0) + 1;
4104
4274
  const redactedFindings = findings.map(redactFinding);
4105
4275
  const written = [];
@@ -4115,13 +4285,13 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4115
4285
  }
4116
4286
  usedSlugs.add(slug);
4117
4287
  const file = `${num}-${slug}.md`;
4118
- writeFileSync6(join11(outDir, file), renderPlanFile(finding, plannedAt));
4288
+ writeFileSync6(join12(outDir, file), renderPlanFile(finding, plannedAt));
4119
4289
  written.push(file);
4120
4290
  next++;
4121
4291
  }
4122
4292
  const all = [...existing, ...written].sort();
4123
4293
  const rows = all.map((file) => {
4124
- const summary = readPlanFileSummary(join11(outDir, file));
4294
+ const summary = readPlanFileSummary(join12(outDir, file));
4125
4295
  const fields = summary.fields;
4126
4296
  return {
4127
4297
  num: file.slice(0, 3),
@@ -4155,7 +4325,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4155
4325
  });
4156
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;
4157
4327
  const hardeningCheckedLines = options.hardeningChecked !== undefined ? options.hardeningChecked.map((hc) => `- ${hc.kind}: ${escapeCell(redactText(hc.text))}`) : carried.hardeningChecked;
4158
- writeFileSync6(join11(outDir, "README.md"), renderIndex({
4328
+ writeFileSync6(join12(outDir, "README.md"), renderIndex({
4159
4329
  date,
4160
4330
  repoName: options.repoName ?? "repo",
4161
4331
  repoShortSha: options.repoShortSha ?? "unknown",
@@ -4164,7 +4334,7 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
4164
4334
  needsVerification: needsVerificationLines,
4165
4335
  hardeningChecked: hardeningCheckedLines
4166
4336
  }));
4167
- return { outDir: resolve10(outDir), date, files: written, nextNumber: next };
4337
+ return { outDir: resolve11(outDir), date, files: written, nextNumber: next };
4168
4338
  }
4169
4339
  async function promoteAuditPlans(outDir, selected, options) {
4170
4340
  if (selected.length === 0) {
@@ -4173,19 +4343,19 @@ async function promoteAuditPlans(outDir, selected, options) {
4173
4343
  if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
4174
4344
  throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
4175
4345
  }
4176
- const workflowId = options.workflowId ?? basename4(resolve10(outDir));
4346
+ const workflowId = options.workflowId ?? basename5(resolve11(outDir));
4177
4347
  assertSafePathComponent(workflowId, "workflow id");
4178
- const harnessDir = resolve10(options.harnessDir);
4179
- const statusPath = join11(harnessDir, "status.json");
4180
- const workflowDir = join11(harnessDir, "workflows", workflowId);
4181
- 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);
4182
4352
  const planFiles = resolveSelectedPlanFiles(outDir, selected);
4183
4353
  const indexRows = readExecutionOrderIndex(outDir);
4184
4354
  const plans = planFiles.map((planFile) => {
4185
4355
  const stem = planFile.replace(/\.md$/, "");
4186
4356
  const num = stem.slice(0, 3);
4187
4357
  const indexRow = indexRows.get(num);
4188
- const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
4358
+ const title = indexRow?.title ?? readPlanFileSummary(join12(outDir, planFile)).title;
4189
4359
  return {
4190
4360
  id: stem,
4191
4361
  title,
@@ -4213,18 +4383,18 @@ async function promoteAuditPlans(outDir, selected, options) {
4213
4383
  if (!entryGate.ok) {
4214
4384
  throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
4215
4385
  }
4216
- await withStatusWriteLock(statusPath, () => {
4217
- if (existsSync7(snapshotPath)) {
4386
+ await withStatusWriteLock(statusPath, async () => {
4387
+ if (existsSync8(snapshotPath)) {
4218
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`);
4219
4389
  }
4220
4390
  mkdirSync8(workflowDir, { recursive: true });
4221
4391
  try {
4222
4392
  writeJson(snapshotPath, snapshot);
4223
- registerWorkflowEntryLocked(statusPath, entry);
4393
+ await registerWorkflowEntryLocked(statusPath, entry);
4224
4394
  } catch (error) {
4225
4395
  rmSync(snapshotPath, { force: true });
4226
4396
  try {
4227
- if (readdirSync7(workflowDir).length === 0) {
4397
+ if (readdirSync8(workflowDir).length === 0) {
4228
4398
  rmdirSync2(workflowDir);
4229
4399
  }
4230
4400
  } catch {}
@@ -4235,7 +4405,7 @@ async function promoteAuditPlans(outDir, selected, options) {
4235
4405
  return { workflowId, snapshotPath };
4236
4406
  }
4237
4407
  function resolveSelectedPlanFiles(outDir, selected) {
4238
- 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();
4239
4409
  const byNum = new Map;
4240
4410
  const byStem = new Map;
4241
4411
  for (const file of files) {
@@ -4250,7 +4420,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
4250
4420
  for (const id of selected) {
4251
4421
  const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
4252
4422
  if (file === undefined) {
4253
- 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)}`);
4254
4424
  }
4255
4425
  if (!seen.has(file)) {
4256
4426
  seen.add(file);
@@ -4260,7 +4430,7 @@ function resolveSelectedPlanFiles(outDir, selected) {
4260
4430
  return resolved;
4261
4431
  }
4262
4432
  function readExecutionOrderIndex(outDir) {
4263
- const readmePath = join11(outDir, "README.md");
4433
+ const readmePath = join12(outDir, "README.md");
4264
4434
  let text;
4265
4435
  try {
4266
4436
  text = readFileSync9(readmePath, "utf8");
@@ -4289,7 +4459,7 @@ function readExecutionOrderIndex(outDir) {
4289
4459
  return rows;
4290
4460
  }
4291
4461
  function planFileRel(outDir, planFile) {
4292
- const resolved = resolve10(outDir);
4462
+ const resolved = resolve11(outDir);
4293
4463
  const parts = resolved.split(sep3);
4294
4464
  const plansIdx = parts.lastIndexOf("plans");
4295
4465
  if (plansIdx >= 0) {
@@ -4298,8 +4468,8 @@ function planFileRel(outDir, planFile) {
4298
4468
  return planFile;
4299
4469
  }
4300
4470
  // src/compound.ts
4301
- import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
4302
- 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";
4303
4473
  function violation10(severity, code, message, fix) {
4304
4474
  return { ok: false, severity, code, message, fix };
4305
4475
  }
@@ -4566,7 +4736,7 @@ function referenceExists(repoRoot, docText) {
4566
4736
  if (ref === "" || seen.has(ref))
4567
4737
  continue;
4568
4738
  seen.add(ref);
4569
- 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)) {
4570
4740
  continue;
4571
4741
  }
4572
4742
  if (ref.includes("/") || REF_EXT_RE.test(ref)) {
@@ -4585,7 +4755,7 @@ function referenceExists(repoRoot, docText) {
4585
4755
  const dir = stack.pop();
4586
4756
  let entries;
4587
4757
  try {
4588
- entries = readdirSync8(dir, { withFileTypes: true });
4758
+ entries = readdirSync9(dir, { withFileTypes: true });
4589
4759
  } catch {
4590
4760
  continue;
4591
4761
  }
@@ -4594,7 +4764,7 @@ function referenceExists(repoRoot, docText) {
4594
4764
  break;
4595
4765
  if (entry.isDirectory()) {
4596
4766
  if (!WALK_SKIP_DIRS.has(entry.name))
4597
- stack.push(join12(dir, entry.name));
4767
+ stack.push(join13(dir, entry.name));
4598
4768
  } else if (!entry.isSymbolicLink()) {
4599
4769
  const base = entry.name.replace(/\.(?:ts|tsx|js|jsx|mjs|cjs)$/, "");
4600
4770
  if (moduleNames.has(base))
@@ -4606,7 +4776,7 @@ function referenceExists(repoRoot, docText) {
4606
4776
  for (const { ref, isSymbol, module } of refs) {
4607
4777
  if (!isSymbol || module === undefined) {
4608
4778
  const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
4609
- if (existsSync8(resolve11(repoRoot, candidate))) {
4779
+ if (existsSync9(resolve12(repoRoot, candidate))) {
4610
4780
  checked++;
4611
4781
  } else {
4612
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"));
@@ -4626,14 +4796,14 @@ function collectKnowledgeDocs(dir) {
4626
4796
  const current = stack.pop();
4627
4797
  let entries;
4628
4798
  try {
4629
- entries = readdirSync8(current, { withFileTypes: true });
4799
+ entries = readdirSync9(current, { withFileTypes: true });
4630
4800
  } catch {
4631
4801
  continue;
4632
4802
  }
4633
4803
  for (const entry of entries) {
4634
4804
  if (entry.isSymbolicLink())
4635
4805
  continue;
4636
- const full = join12(current, entry.name);
4806
+ const full = join13(current, entry.name);
4637
4807
  if (entry.isDirectory()) {
4638
4808
  stack.push(full);
4639
4809
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
@@ -4653,8 +4823,8 @@ function normalizeIndexRef(cell) {
4653
4823
  }
4654
4824
  function assertIndexRows(knowledgeDir) {
4655
4825
  const violations = [];
4656
- const readmePath = join12(knowledgeDir, "README.md");
4657
- if (!existsSync8(readmePath)) {
4826
+ const readmePath = join13(knowledgeDir, "README.md");
4827
+ if (!existsSync9(readmePath)) {
4658
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"));
4659
4829
  return { ok: false, violations };
4660
4830
  }
@@ -4679,19 +4849,19 @@ function assertIndexRows(knowledgeDir) {
4679
4849
  }
4680
4850
  function compoundRefreshScope(harnessDir, projectRoot) {
4681
4851
  return [
4682
- join12(harnessDir, "knowledge"),
4683
- join12(harnessDir, "knowledge", "README.md"),
4684
- join12(projectRoot, "CONCEPTS.md"),
4685
- join12(harnessDir, "status.json")
4852
+ join13(harnessDir, "knowledge"),
4853
+ join13(harnessDir, "knowledge", "README.md"),
4854
+ join13(projectRoot, "CONCEPTS.md"),
4855
+ join13(harnessDir, "status.json")
4686
4856
  ];
4687
4857
  }
4688
4858
  function isFileLikeRoot(root) {
4689
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
4859
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename6(root));
4690
4860
  }
4691
4861
  function scopeGuard(path, allowedRoots) {
4692
- const resolved = resolve11(path);
4862
+ const resolved = resolve12(path);
4693
4863
  for (const root of allowedRoots) {
4694
- const r = resolve11(root);
4864
+ const r = resolve12(root);
4695
4865
  if (isFileLikeRoot(r)) {
4696
4866
  if (resolved === r)
4697
4867
  return { ok: true, violations: [] };
@@ -4939,8 +5109,8 @@ function lintStrategySections(docText) {
4939
5109
  return { ok: violations.length === 0, violations };
4940
5110
  }
4941
5111
  // src/roles.ts
4942
- import { existsSync as existsSync9 } from "node:fs";
4943
- import { join as join13 } from "node:path";
5112
+ import { existsSync as existsSync10 } from "node:fs";
5113
+ import { join as join14 } from "node:path";
4944
5114
  function violation12(severity, code, message, fix) {
4945
5115
  return { ok: false, severity, code, message, fix };
4946
5116
  }
@@ -4996,8 +5166,8 @@ function validateRoleMapping(rolesDir, options = {}) {
4996
5166
  const violations = [];
4997
5167
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
4998
5168
  for (const { agentId, reference } of mapping) {
4999
- if (!existsSync9(join13(rolesDir, reference))) {
5000
- 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`));
5001
5171
  }
5002
5172
  }
5003
5173
  for (const { family, memberIds } of families) {
@@ -5202,8 +5372,8 @@ function resolveAssetPath(skillName, relPath, host) {
5202
5372
  return `skill \`${skillName}\` → ${relPath} (${resolveSkillRoot(host, { skill: skillName, rel: relPath })})`;
5203
5373
  }
5204
5374
  // src/prreview.ts
5205
- import { readdirSync as readdirSync9 } from "node:fs";
5206
- 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";
5207
5377
  var MERGE_CLASSES = ["must-fix", "should-fix", "nit"];
5208
5378
  var PR_VERDICTS = ["ship it", "needs fixes", "blocked"];
5209
5379
  var REVIEW_EMOJI = {
@@ -5240,6 +5410,162 @@ function computePrTally(input) {
5240
5410
  ` + `must-fix=${mustFix} should-fix=${shouldFix} nit=${nit} unverified=${unverified}`;
5241
5411
  return { verdict, scorePct, tally: { mustFix, shouldFix, nit, unverified }, chatHeader };
5242
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
+ }
5243
5569
  var SHORT_SHA_WIDTH = 7;
5244
5570
  var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
5245
5571
  function todayString4() {
@@ -5287,7 +5613,7 @@ function prReviewReportPath(opts) {
5287
5613
  const sameStem = new RegExp(`^${escaped}(?:-r([0-9]+))?\\.md$`);
5288
5614
  let dirents;
5289
5615
  try {
5290
- dirents = readdirSync9(opts.reportsDir, { withFileTypes: true });
5616
+ dirents = readdirSync10(opts.reportsDir, { withFileTypes: true });
5291
5617
  } catch (error) {
5292
5618
  if (error.code === "ENOENT")
5293
5619
  dirents = [];
@@ -5303,7 +5629,7 @@ function prReviewReportPath(opts) {
5303
5629
  }
5304
5630
  const revision = maxRevision + 1;
5305
5631
  const name = revision === 1 ? `${finalStem}.md` : `${finalStem}-r${revision}.md`;
5306
- return join14(opts.reportsDir, name);
5632
+ return join15(opts.reportsDir, name);
5307
5633
  }
5308
5634
  var PR_TIERS = ["quick", "default", "deep"];
5309
5635
  function violation14(severity, code, message, fix) {
@@ -5573,10 +5899,10 @@ function prReviewSeatPrompt(opts) {
5573
5899
  }
5574
5900
  const skillRoot = opts.skillRoot.trim();
5575
5901
  const worktreePath = opts.worktreePath.trim();
5576
- if (!isAbsolute8(skillRoot)) {
5902
+ if (!isAbsolute9(skillRoot)) {
5577
5903
  throw new TypeError(`prReviewSeatPrompt: skillRoot must be an absolute path - got ${JSON.stringify(opts.skillRoot)}`);
5578
5904
  }
5579
- if (!isAbsolute8(worktreePath)) {
5905
+ if (!isAbsolute9(worktreePath)) {
5580
5906
  throw new TypeError(`prReviewSeatPrompt: worktreePath must be an absolute path - got ${JSON.stringify(opts.worktreePath)}`);
5581
5907
  }
5582
5908
  const slug = `${domain}-${seat}`;
@@ -5600,14 +5926,14 @@ function prReviewSeatPrompt(opts) {
5600
5926
  lines.push("");
5601
5927
  lines.push("## Read first");
5602
5928
  lines.push("");
5603
- const prReviewRef = join14(skillRoot, "references", "pr-review.md");
5929
+ const prReviewRef = join15(skillRoot, "references", "pr-review.md");
5604
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";
5605
5931
  lines.push(`1. \`${prReviewRef}\` — read at least these sections: ${sections}.`);
5606
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).`);
5607
5933
  if (opts.stage === 2) {
5608
- 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.`);
5609
5935
  if (opts.securitySeat === true) {
5610
- 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.`);
5611
5937
  }
5612
5938
  }
5613
5939
  lines.push("");
@@ -5791,6 +6117,7 @@ export {
5791
6117
  validateProjectRegister,
5792
6118
  validatePrReviewReport,
5793
6119
  validatePlanRow,
6120
+ validateMstarReviewV1,
5794
6121
  validateIntegrationMergeLease,
5795
6122
  validateGitignore,
5796
6123
  validateFindingDoc,
@@ -5803,9 +6130,11 @@ export {
5803
6130
  techDebtRollup,
5804
6131
  taskReportExists,
5805
6132
  taskBrief,
6133
+ synthesizeReview,
5806
6134
  supplyChainChecks,
5807
6135
  stripFrontmatter,
5808
6136
  singleReviewSnapshot,
6137
+ setArtifactStore,
5809
6138
  sddWorkspace,
5810
6139
  scopeGuard,
5811
6140
  scanSecrets,
@@ -5829,6 +6158,7 @@ export {
5829
6158
  resolveHarnessDir,
5830
6159
  resolveCompassEnforcement,
5831
6160
  resolveAssetPath,
6161
+ resolveArtifactPath,
5832
6162
  releaseLease,
5833
6163
  registerWorkflow,
5834
6164
  referenceExists,
@@ -5855,6 +6185,7 @@ export {
5855
6185
  parseAssignmentBranchForms,
5856
6186
  normalizeSeverity,
5857
6187
  migrateHarnessTree,
6188
+ loadStoreModule,
5858
6189
  listProjectReferenceFiles,
5859
6190
  lintStrategySections,
5860
6191
  lintSkillFrontmatter,
@@ -5865,6 +6196,7 @@ export {
5865
6196
  l1PreDispatchCheck,
5866
6197
  isReadOnlyAssignmentRole,
5867
6198
  implementerSessionStickyRules,
6199
+ getArtifactStore,
5868
6200
  findingsCleanupGate,
5869
6201
  findTemporaryMarkers,
5870
6202
  findSimplifyMarkers,
@@ -5875,6 +6207,7 @@ export {
5875
6207
  emitGitignoreSnippet,
5876
6208
  detectHost,
5877
6209
  detectHarnessKind,
6210
+ createFsStore,
5878
6211
  computePrTally,
5879
6212
  compoundRefreshScope,
5880
6213
  composeDispatchGate,
@@ -5890,6 +6223,7 @@ export {
5890
6223
  assertLightDarkParity,
5891
6224
  assertIndexRows,
5892
6225
  assertIndexRowObligations,
6226
+ assertFsStorePath,
5893
6227
  assertDefaultBranchProtected,
5894
6228
  assertControlVsFeaturePath,
5895
6229
  assertBranchAlignment,