@mstar-harness/dsh 3.8.3 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +1 -1
  3. package/README.zh.md +1 -1
  4. package/dist/client/panel/locale.d.ts +1 -1
  5. package/dist/client.js +5 -5
  6. package/dist/index.js +1293 -955
  7. package/dist/types.d.ts +2 -2
  8. package/harness-commands/iteration-drive.md +12 -6
  9. package/harness-commands/iteration-loop.md +10 -10
  10. package/harness-commands/iteration-start.md +10 -10
  11. package/harness-skills/mstar-artifacts/SKILL.md +3 -3
  12. package/harness-skills/mstar-artifacts/references/status-and-residuals.md +12 -8
  13. package/harness-skills/mstar-artifacts/templates/plan.main.md +2 -0
  14. package/harness-skills/mstar-branch-worktree/SKILL.md +69 -28
  15. package/harness-skills/mstar-branch-worktree/references/parallel-writable-pre-dispatch.md +9 -8
  16. package/harness-skills/mstar-conventions/SKILL.md +2 -2
  17. package/harness-skills/mstar-dispatch-gates/SKILL.md +1 -1
  18. package/harness-skills/mstar-engine-legacy/references/lease-protocol.md +12 -9
  19. package/harness-skills/mstar-engine-legacy/references/status-field-history.md +3 -3
  20. package/harness-skills/mstar-host/references/codex.md +6 -4
  21. package/harness-skills/mstar-host/references/dsh.md +1 -1
  22. package/harness-skills/mstar-iteration/SKILL.md +18 -12
  23. package/harness-skills/mstar-iteration/references/command-shared-invariants.md +2 -1
  24. package/harness-skills/mstar-iteration/references/phase-1-prepare.md +2 -0
  25. package/harness-skills/mstar-iteration/references/phase-2-worktree-lease.md +70 -46
  26. package/harness-skills/mstar-iteration/references/phase-3-iteration-close.md +4 -4
  27. package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +6 -5
  28. package/harness-skills/mstar-iteration/references/phase-6-post-merge-close.md +71 -0
  29. package/harness-skills/mstar-review-qc/SKILL.md +1 -0
  30. package/harness-skills/mstar-roles/references/project-manager/dispatch-and-assignment.md +10 -8
  31. package/harness-skills/mstar-roles/references/project-manager.md +3 -2
  32. package/harness-skills/mstar-roles/references/prompt-engineer.md +1 -1
  33. package/harness-skills/mstar-roles/references/qa-engineer/acceptance-gate.md +15 -0
  34. package/harness-skills/mstar-roles/references/qa-engineer.md +3 -1
  35. package/harness-skills/mstar-sdd/SKILL.md +3 -1
  36. package/harness-skills/mstar-sdd/references/file-handoffs.md +21 -3
  37. package/harness-skills/mstar-sdd/references/implementer-continuation-prompt.md +1 -1
  38. package/harness-skills/mstar-sdd/references/implementer-prompt.md +1 -1
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1122,8 +1122,8 @@ var require_lib2 = __commonJS(function(exports, module) {
1122
1122
  });
1123
1123
 
1124
1124
  // src/index.ts
1125
- import { existsSync as existsSync22, readFileSync as readFileSync18, readdirSync as readdirSync14 } from "node:fs";
1126
- import { join as join25 } from "node:path";
1125
+ import { existsSync as existsSync24, readFileSync as readFileSync19, readdirSync as readdirSync14 } from "node:fs";
1126
+ import { join as join28 } from "node:path";
1127
1127
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1128
1128
  import {
1129
1129
  apply as applySkillLocal,
@@ -1138,24 +1138,28 @@ import { basename, dirname, join, resolve } from "node:path";
1138
1138
  import { fileURLToPath } from "node:url";
1139
1139
  import { readFileSync as readFileSync2, statSync } from "node:fs";
1140
1140
  import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
1141
- 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";
1141
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
1142
1142
  import { execFileSync } from "node:child_process";
1143
- import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
1144
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
1145
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
1143
+ import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute5, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
1144
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
1145
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "node:fs";
1146
+ import { dirname as dirname4, join as join6, resolve as resolve5, sep } from "node:path";
1147
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "node:path";
1146
1148
  import { AsyncLocalStorage } from "node:async_hooks";
1147
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync } from "node:fs";
1148
- import { dirname as dirname4, join as join7, resolve as resolve5, sep } from "node:path";
1149
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "node:fs";
1150
+ import { isAbsolute as isAbsolute4, join as join5 } from "node:path";
1149
1151
  import { execFileSync as execFileSync2 } from "node:child_process";
1150
- import { existsSync as existsSync7, realpathSync as realpathSync3 } from "node:fs";
1151
- import { isAbsolute as isAbsolute5, join as join10, resolve as resolve8 } from "node:path";
1152
+ import { existsSync as existsSync8, realpathSync as realpathSync3 } from "node:fs";
1153
+ import { isAbsolute as isAbsolute6, join as join10, resolve as resolve8 } from "node:path";
1154
+ import { existsSync as existsSync9 } from "node:fs";
1155
+ import { join as join11, dirname as dirname7 } from "node:path";
1152
1156
  import { execFileSync as execFileSync3, spawn } from "node:child_process";
1153
- import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync7, realpathSync as realpathSync4, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
1154
- import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute6, join as join11, relative as relative3, resolve as resolve9 } from "node:path";
1155
- import { existsSync as existsSync10, readdirSync as readdirSync9, readFileSync as readFileSync10 } from "node:fs";
1156
- import { basename as basename7, isAbsolute as isAbsolute8, join as join15, relative as relative6, resolve as resolve13, sep as sep4 } from "node:path";
1157
- import { existsSync as existsSync11 } from "node:fs";
1158
- import { join as join16 } from "node:path";
1157
+ import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync4, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
1158
+ import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute7, join as join12, relative as relative3, resolve as resolve9 } from "node:path";
1159
+ import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync11 } from "node:fs";
1160
+ import { basename as basename8, isAbsolute as isAbsolute9, join as join16, relative as relative6, resolve as resolve13, sep as sep4 } from "node:path";
1161
+ import { existsSync as existsSync13 } from "node:fs";
1162
+ import { join as join17 } from "node:path";
1159
1163
  var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
1160
1164
  function applyEnforcement(gate, opts) {
1161
1165
  return { ...gate, hardBlocked: opts.hard && gate.violations.length > 0 };
@@ -1292,445 +1296,168 @@ function isAtOrBelow(dir, root) {
1292
1296
  const rel = relative(root, dir);
1293
1297
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
1294
1298
  }
1295
- var COMPASS_STATUSES = ["active", "locked", "completed"];
1296
- var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
1297
- var PLAN_STATUS_DONE = "Done";
1298
- function typeName(value) {
1299
- if (value === null)
1300
- return "null";
1301
- if (Array.isArray(value))
1302
- return "array";
1303
- return typeof value;
1304
- }
1305
- function validateCompassShape(doc) {
1306
- const issues = [];
1307
- const expectString = (key, opts = {}) => {
1308
- const value = doc[key];
1309
- if (typeof value !== "string") {
1310
- issues.push({ path: [key], message: `expected string, received ${typeName(value)}` });
1311
- return;
1312
- }
1313
- if (opts.min !== undefined && value.length < opts.min) {
1314
- issues.push({ path: [key], message: `string must contain at least ${opts.min} character(s)` });
1315
- return;
1316
- }
1317
- if (opts.regex !== undefined && !opts.regex.test(value)) {
1318
- issues.push({ path: [key], message: `string must match ${opts.regex}` });
1319
- }
1320
- };
1321
- expectString("iteration_id", { min: 1 });
1322
- expectString("start_date", { regex: DATE_RE });
1323
- const status = doc.status;
1324
- if (typeof status !== "string" || !COMPASS_STATUSES.includes(status)) {
1325
- issues.push({
1326
- path: ["status"],
1327
- message: `expected one of ${COMPASS_STATUSES.map((s) => `'${s}'`).join(" | ")}, received ${typeName(status)}`
1328
- });
1329
- }
1330
- expectString("iteration_base_branch", { min: 1 });
1331
- expectString("target_branch", { min: 1 });
1332
- const plans = doc.plans;
1333
- if (plans !== undefined) {
1334
- if (!Array.isArray(plans)) {
1335
- issues.push({ path: ["plans"], message: `expected array, received ${typeName(plans)}` });
1336
- } else {
1337
- plans.forEach((entry, index) => {
1338
- if (typeof entry !== "string") {
1339
- issues.push({ path: ["plans", index], message: `expected string, received ${typeName(entry)}` });
1340
- } else if (entry.length < 1) {
1341
- issues.push({ path: ["plans", index], message: "string must contain at least 1 character(s)" });
1342
- }
1343
- });
1344
- }
1345
- }
1346
- const end_date = doc.end_date;
1347
- if (end_date !== undefined) {
1348
- if (typeof end_date !== "string") {
1349
- issues.push({ path: ["end_date"], message: `expected string, received ${typeName(end_date)}` });
1350
- } else if (!DATE_RE.test(end_date)) {
1351
- issues.push({ path: ["end_date"], message: `string must match ${DATE_RE}` });
1352
- }
1353
- }
1354
- if (issues.length > 0)
1355
- return { ok: false, issues };
1356
- return {
1357
- ok: true,
1358
- data: {
1359
- iteration_id: doc.iteration_id,
1360
- start_date: doc.start_date,
1361
- status,
1362
- iteration_base_branch: doc.iteration_base_branch,
1363
- target_branch: doc.target_branch,
1364
- ...plans !== undefined ? { plans } : {},
1365
- ...end_date !== undefined ? { end_date } : {}
1366
- }
1367
- };
1299
+ function isPlainObject(value) {
1300
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1368
1301
  }
1369
1302
  function violation(severity, code, message, fix) {
1370
1303
  return { ok: false, severity, code, message, fix };
1371
1304
  }
1372
- function isPlainObject(value) {
1373
- return typeof value === "object" && value !== null && !Array.isArray(value);
1305
+ function validateNonEmptyString(violations, value, field, missingCode, invalidCode) {
1306
+ if (value === undefined) {
1307
+ violations.push(violation("high", missingCode, `missing required field: ${field}`));
1308
+ } else if (typeof value !== "string" || value.trim() === "") {
1309
+ violations.push(violation("medium", invalidCode, `${field} must be a non-empty string`));
1310
+ }
1374
1311
  }
1375
- function validateCompassFrontmatter(doc) {
1376
- if (!isPlainObject(doc)) {
1312
+ var DATE_PART = String.raw`\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])`;
1313
+ var RFC3339_Z_RE = new RegExp(String.raw`^${DATE_PART}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$`);
1314
+ var DATE_ONLY_RE = new RegExp(String.raw`^${DATE_PART}$`);
1315
+ function isValidClaimedAt(value) {
1316
+ return typeof value === "string" && (RFC3339_Z_RE.test(value) || DATE_ONLY_RE.test(value));
1317
+ }
1318
+ function validateExecutionLease(lease) {
1319
+ const violations = [];
1320
+ if (!isPlainObject(lease)) {
1377
1321
  return {
1378
1322
  ok: false,
1379
1323
  violations: [
1380
- violation("medium", "COMPASS_INVALID_FIELD", "Compass frontmatter must be a YAML object with iteration_id / start_date / status / iteration_base_branch / target_branch (template: mstar-iteration §1.3)", "Fix the frontmatter of {ITERATION_DIR}/<iteration-id>/delivery-compass.md")
1324
+ violation("high", "lease.execution-lease.invalid", "execution_lease must be an object — null and tombstone objects are invalid; writers delete the key on release")
1381
1325
  ]
1382
1326
  };
1383
1327
  }
1384
- const parsed = validateCompassShape(doc);
1385
- if (!parsed.ok) {
1328
+ validateNonEmptyString(violations, lease.holder, "holder", "lease.execution-lease.missing-holder", "lease.execution-lease.invalid-holder");
1329
+ if (lease.claimed_at === undefined) {
1330
+ violations.push(violation("high", "lease.execution-lease.missing-claimed-at", "missing required field: claimed_at"));
1331
+ } else if (!isValidClaimedAt(lease.claimed_at)) {
1332
+ violations.push(violation("medium", "lease.execution-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T02:30:00Z) or a YYYY-MM-DD date"));
1333
+ }
1334
+ if (lease.worktree_path === undefined) {
1335
+ violations.push(violation("high", "lease.execution-lease.missing-worktree-path", "missing required field: worktree_path"));
1336
+ } else if (typeof lease.worktree_path !== "string" || lease.worktree_path.trim() === "") {
1337
+ violations.push(violation("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be a non-empty string"));
1338
+ } else if (!isAbsolute2(lease.worktree_path)) {
1339
+ violations.push(violation("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be an absolute path — it identifies the dedicated feature-worktree root (a Git checkout distinct from the main worktree and the integration worktree)"));
1340
+ }
1341
+ validateNonEmptyString(violations, lease.working_branch, "working_branch", "lease.execution-lease.missing-working-branch", "lease.execution-lease.invalid-working-branch");
1342
+ if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
1343
+ violations.push(violation("medium", "lease.execution-lease.invalid-session-label", "session_label must be a string (display only — never used for ownership comparison)"));
1344
+ }
1345
+ return { ok: violations.length === 0, violations };
1346
+ }
1347
+ function validateIntegrationMergeLease(lease) {
1348
+ const violations = [];
1349
+ if (!isPlainObject(lease)) {
1386
1350
  return {
1387
1351
  ok: false,
1388
- violations: parsed.issues.map((issue) => {
1389
- const field = issue.path.join(".") || "(root)";
1390
- return violation("medium", "COMPASS_INVALID_FIELD", `Compass frontmatter field '${field}' is invalid: ${issue.message}`, `Fix '${field}' in {ITERATION_DIR}/<iteration-id>/delivery-compass.md frontmatter (template: mstar-iteration §1.3)`);
1391
- })
1352
+ violations: [
1353
+ violation("high", "lease.merge-lease.invalid", "integration_merge_lease must be an object — absent means unclaimed; null and tombstone objects are invalid; writers delete the key on release")
1354
+ ]
1392
1355
  };
1393
1356
  }
1394
- const violations = [];
1395
- const { status, end_date } = parsed.data;
1396
- if (status === "completed" && end_date === undefined) {
1397
- violations.push(violation("high", "COMPASS_END_DATE_REQUIRED", "Compass frontmatter status is 'completed' but end_date is missing — end_date is required at iteration-close (mstar-iteration §3.4, template Fields guide)", "Add `end_date: YYYY-MM-DD` to the frontmatter"));
1357
+ validateNonEmptyString(violations, lease.holder, "holder", "lease.merge-lease.missing-holder", "lease.merge-lease.invalid-holder");
1358
+ if (lease.claimed_at === undefined) {
1359
+ violations.push(violation("high", "lease.merge-lease.missing-claimed-at", "missing required field: claimed_at"));
1360
+ } else if (!isValidClaimedAt(lease.claimed_at)) {
1361
+ violations.push(violation("medium", "lease.merge-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T04:00:00Z) or a YYYY-MM-DD date"));
1398
1362
  }
1399
- if (status !== "completed" && end_date !== undefined) {
1400
- violations.push(violation("medium", "COMPASS_END_DATE_NOT_ALLOWED", `Compass frontmatter sets end_date while status is '${status}' — end_date is only written at iteration-close (mstar-iteration §3.4)`, "Remove end_date until iteration-close"));
1363
+ validateNonEmptyString(violations, lease.plan_id, "plan_id", "lease.merge-lease.missing-plan-id", "lease.merge-lease.invalid-plan-id");
1364
+ validateNonEmptyString(violations, lease.source_branch, "source_branch", "lease.merge-lease.missing-source-branch", "lease.merge-lease.invalid-source-branch");
1365
+ validateNonEmptyString(violations, lease.target_branch, "target_branch", "lease.merge-lease.missing-target-branch", "lease.merge-lease.invalid-target-branch");
1366
+ if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
1367
+ violations.push(violation("medium", "lease.merge-lease.invalid-session-label", "session_label must be a string (display only — never used for ownership comparison)"));
1401
1368
  }
1402
1369
  return { ok: violations.length === 0, violations };
1403
1370
  }
1404
- function registeredPlanIds(compassDoc) {
1405
- if (!Array.isArray(compassDoc.plans))
1406
- return [];
1407
- return compassDoc.plans.filter((plan) => typeof plan === "string" && plan.length > 0);
1371
+ function planExecutionLeaseLocations(row) {
1372
+ return { row: row.execution_lease };
1408
1373
  }
1409
- function findPlanRow(snapshotDoc, planId) {
1410
- if (!Array.isArray(snapshotDoc.plans))
1411
- return null;
1412
- for (const row of snapshotDoc.plans) {
1413
- if (!isPlainObject(row))
1414
- continue;
1415
- const rowId = typeof row.id === "string" ? row.id : typeof row.plan_id === "string" ? row.plan_id : null;
1416
- if (rowId === planId)
1417
- return row;
1374
+ function verifyPlanExecutionLease(row, planId) {
1375
+ const { row: rowLease } = planExecutionLeaseLocations(row);
1376
+ const lease = rowLease;
1377
+ if (lease === undefined) {
1378
+ if (row.status === "InProgress") {
1379
+ return {
1380
+ ok: false,
1381
+ violations: [
1382
+ violation("high", "lease.verify.orphan", "plan is InProgress without an execution_lease — orphan: STOP, no writable dispatch until recovery (status-and-residuals.md § Orphan recovery)")
1383
+ ]
1384
+ };
1385
+ }
1386
+ return {
1387
+ ok: false,
1388
+ violations: [
1389
+ violation("high", "lease.verify.missing", `plan ${planId} has no execution_lease at the SSOT location plans[].execution_lease`)
1390
+ ]
1391
+ };
1418
1392
  }
1419
- return null;
1420
- }
1421
- function entryPlansAllDone(snapshotDoc, registered) {
1422
1393
  const violations = [];
1423
- if (registered.length === 0) {
1424
- violations.push(violation("medium", "COMPASS_NO_PLANS", "Compass frontmatter registers no plans — the all-plans-Done transition cannot be verified (mstar-iteration §1.3 / Phase transition gates)", "List the iteration's plan ids in the compass frontmatter `plans`"));
1425
- return violations;
1426
- }
1427
- for (const planId of registered) {
1428
- const row = findPlanRow(snapshotDoc, planId);
1429
- if (row === null) {
1430
- violations.push(violation("high", "PLAN_NOT_IN_STATUS", `Plan '${planId}' is registered in the compass frontmatter but has no row in the workflow snapshot plans[] (mstar-iteration §3.1 entry item 1)`, "Add the plan row to {HARNESS_DIR}/workflows/<id>/snapshot.json"));
1394
+ violations.push(...validateExecutionLease(lease).violations);
1395
+ return { ok: violations.length === 0, violations, lease };
1396
+ }
1397
+ var heldLockDirs = new AsyncLocalStorage;
1398
+ var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"';
1399
+ var REQUIRED_FIELDS = [
1400
+ { key: "executeAs", label: "Execute as", code: "execute-as" },
1401
+ { key: "delegation", label: "Delegation", code: "delegation" },
1402
+ { key: "taskCategory", label: "Task category", code: "task-category" }
1403
+ ];
1404
+ function violation2(severity, code, message, fix) {
1405
+ return { ok: false, severity, code, message, fix };
1406
+ }
1407
+ function parseAssignmentFields(assignmentText) {
1408
+ const fields = {};
1409
+ for (const line of assignmentText.split(/\r?\n/)) {
1410
+ const match = line.match(/^[ \t]*(?:[-*][ \t]+)?\*\*\s*([^*:]+?)\s*\*\*\s*:\s*(.*)$/) ?? line.match(/^[ \t]*(?:[-*][ \t]+)?([A-Za-z][A-Za-z -]*?)\s*:\s*(.*)$/);
1411
+ if (!match)
1412
+ continue;
1413
+ const label = match[1].trim();
1414
+ const value = match[2].trim();
1415
+ const known = REQUIRED_FIELDS.find((f) => f.label === label);
1416
+ if (known) {
1417
+ fields[known.key] = value;
1431
1418
  continue;
1432
1419
  }
1433
- if (row.status !== PLAN_STATUS_DONE) {
1434
- violations.push(violation("high", "PLAN_NOT_DONE", `Plan '${planId}' status is ${JSON.stringify(row.status)} in the workflow snapshot — all compass-registered plans must be 'Done' before iteration-close (mstar-iteration §3.1 entry item 1)`));
1435
- }
1420
+ if (label === "Working branch")
1421
+ fields.workingBranch = value;
1422
+ else if (label === "Branch policy")
1423
+ fields.branchPolicy = value;
1436
1424
  }
1437
- return violations;
1425
+ return fields;
1438
1426
  }
1439
- function entryFrontmatterComplete(compassDoc) {
1440
- return validateCompassFrontmatter(compassDoc).violations;
1427
+ var ASSIGNMENT_ENFORCEMENT_BOLD_RE = /^[ \t]*(?:[-*][ \t]+)?\*\*\s*Enforcement\s*\*\*\s*:\s*(.*)$/m;
1428
+ var ASSIGNMENT_ENFORCEMENT_PLAIN_RE = /^[ \t]*(?:[-*][ \t]+)?Enforcement\s*:\s*(.*)$/m;
1429
+ var COMPASS_ENFORCEMENT_RE = /^enforcement\s*:\s*(.*)$/m;
1430
+ function enforcementValue(raw) {
1431
+ const value = raw.trim();
1432
+ const unquoted = value.replace(/^(['"])(.*)\1$/, "$2");
1433
+ return unquoted.trim().toLowerCase();
1441
1434
  }
1442
- function exitFrontmatterClosed(compassDoc) {
1443
- const violations = [];
1444
- if (compassDoc.status !== "completed") {
1445
- violations.push(violation("high", "EXIT_STATUS_NOT_COMPLETED", `Compass frontmatter status must be 'completed' at close exit — current: ${JSON.stringify(compassDoc.status)} (mstar-iteration §3.4 / §3.5 exit item 4)`));
1446
- }
1447
- const endDate = compassDoc.end_date;
1448
- if (typeof endDate !== "string" || !DATE_RE.test(endDate)) {
1449
- violations.push(violation("high", "EXIT_END_DATE_REQUIRED", "Compass frontmatter end_date (YYYY-MM-DD) is required when closing (mstar-iteration §3.4 / §3.5 exit item 4)"));
1450
- }
1451
- return violations;
1435
+ var ASSIGNMENT_BODY_START_RE = /^(?:#{1,6}[ \t]+Task\b|-{3,}[ \t]*$|#[ \t])/m;
1436
+ function assignmentHeaderRegion(assignmentText) {
1437
+ const marker = assignmentText.match(ASSIGNMENT_BODY_START_RE);
1438
+ return marker !== null ? assignmentText.slice(0, marker.index) : assignmentText;
1452
1439
  }
1453
- function exitBranchCheck(opts) {
1454
- const violations = [];
1455
- const { currentBranch, specIntegrationBranch } = opts;
1456
- if (currentBranch === undefined || specIntegrationBranch === undefined) {
1457
- violations.push(violation("medium", "EXIT_BRANCH_UNVERIFIABLE", "Cannot verify the current branch is spec_integration_branch — missing currentBranch / specIntegrationBranch probe inputs (mstar-iteration §3.5 exit item 5)"));
1458
- } else if (currentBranch !== specIntegrationBranch) {
1459
- violations.push(violation("high", "EXIT_BRANCH_MISMATCH", `Current branch '${currentBranch}' is not the spec_integration_branch '${specIntegrationBranch}' (mstar-iteration §3.5 exit item 5)`));
1460
- }
1461
- return violations;
1440
+ function parseEnforcementFlag(text) {
1441
+ const bold = text.match(ASSIGNMENT_ENFORCEMENT_BOLD_RE);
1442
+ if (bold !== null)
1443
+ return { hard: enforcementValue(bold[1]) === "hard", source: "assignment" };
1444
+ const plain = text.match(ASSIGNMENT_ENFORCEMENT_PLAIN_RE);
1445
+ if (plain !== null)
1446
+ return { hard: enforcementValue(plain[1]) === "hard", source: "assignment" };
1447
+ const compass = text.match(COMPASS_ENFORCEMENT_RE);
1448
+ if (compass !== null)
1449
+ return { hard: enforcementValue(compass[1]) === "hard", source: "compass" };
1450
+ return { hard: false, source: "none" };
1462
1451
  }
1463
- function exitPrBaseCheck(compassDoc, opts) {
1464
- const violations = [];
1465
- const target = compassDoc.target_branch;
1466
- const { prBaseBranch } = opts;
1467
- if (prBaseBranch === undefined) {
1468
- violations.push(violation("medium", "EXIT_PR_BASE_UNVERIFIABLE", "Cannot verify the PR base — missing prBaseBranch probe input (mstar-iteration §3.5 exit item 6)"));
1469
- } else if (typeof target !== "string" || prBaseBranch !== target) {
1470
- violations.push(violation("high", "EXIT_PR_BASE_MISMATCH", `PR base '${prBaseBranch}' must equal the compass target_branch '${String(target)}' — not an undocumented branch (mstar-iteration §3.5 exit item 6)`));
1471
- }
1472
- return violations;
1473
- }
1474
- function evaluatePhaseGate(snapshotDoc, compassDoc, opts = {}) {
1475
- const registered = registeredPlanIds(compassDoc);
1476
- const entryViolations = [
1477
- ...entryPlansAllDone(snapshotDoc, registered),
1478
- ...entryFrontmatterComplete(compassDoc)
1479
- ];
1480
- const exitViolations = [
1481
- ...exitFrontmatterClosed(compassDoc),
1482
- ...exitBranchCheck(opts),
1483
- ...exitPrBaseCheck(compassDoc, opts)
1484
- ];
1485
- const allPlansDone = registered.length > 0 && registered.every((planId) => {
1486
- const row = findPlanRow(snapshotDoc, planId);
1487
- return row !== null && row.status === PLAN_STATUS_DONE;
1488
- });
1489
- const entry = { ok: entryViolations.length === 0, violations: entryViolations };
1490
- const exit = { ok: exitViolations.length === 0, violations: exitViolations };
1491
- let transition;
1492
- if (!allPlansDone)
1493
- transition = "phase-2-execute";
1494
- else if (entry.ok && exit.ok)
1495
- transition = "phase-4-pr-delivery";
1496
- else
1497
- transition = "phase-3-close";
1498
- const gateBlocking = allPlansDone ? [...entryViolations, ...exitViolations] : [];
1499
- return {
1500
- transition,
1501
- allPlansDone,
1502
- entry,
1503
- exit,
1504
- ok: gateBlocking.length === 0,
1505
- violations: gateBlocking
1506
- };
1507
- }
1508
- function parseCompassFrontmatter(filePath) {
1509
- return parseCompassFrontmatterText(readFileSync3(filePath, "utf8"), filePath);
1510
- }
1511
- function parseCompassFrontmatterText(content, filePath) {
1512
- const lines = content.split(/\r?\n/);
1513
- if (lines[0]?.trim() !== "---") {
1514
- throw new Error(`no YAML frontmatter fence in ${filePath} (expected first line "---")`);
1515
- }
1516
- const end = lines.indexOf("---", 1);
1517
- if (end === -1) {
1518
- throw new Error(`unterminated YAML frontmatter in ${filePath} (no closing "---")`);
1519
- }
1520
- const doc = {};
1521
- let listKey = null;
1522
- for (let i = 1;i < end; i += 1) {
1523
- const line = lines[i] ?? "";
1524
- if (!line.trim() || line.trim().startsWith("#"))
1525
- continue;
1526
- if (listKey !== null && /^\s*-\s+/.test(line)) {
1527
- const item = line.replace(/^\s*-\s+/, "").trim().replace(/^["']|["']$/g, "");
1528
- if (!Array.isArray(doc[listKey]))
1529
- doc[listKey] = [];
1530
- doc[listKey].push(item);
1531
- continue;
1532
- }
1533
- listKey = null;
1534
- const kv = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
1535
- if (!kv) {
1536
- throw new Error(`unsupported frontmatter line in ${filePath}: ${JSON.stringify(line)}`);
1537
- }
1538
- const value = kv[2].trim();
1539
- doc[kv[1]] = value === "" ? null : /^\[.*\]$/.test(value) ? parseFlowArray(value, filePath) : value.replace(/^["']|["']$/g, "");
1540
- listKey = value === "" ? kv[1] : null;
1541
- }
1542
- return doc;
1543
- }
1544
- function parseFlowArray(raw, filePath) {
1545
- const inner = raw.slice(1, -1);
1546
- if (/[[\]]/.test(inner)) {
1547
- throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)} — only flat scalar items are supported (e.g. [a, b])`);
1548
- }
1549
- let quote = null;
1550
- for (const ch of inner) {
1551
- if (ch === '"' || ch === "'") {
1552
- if (quote === null)
1553
- quote = ch;
1554
- else if (quote === ch)
1555
- quote = null;
1556
- } else if (ch === "," && quote !== null) {
1557
- throw new Error(`ambiguous flow-style array in ${filePath}: ${JSON.stringify(raw)} — quoted item containing comma cannot be split unambiguously (flat scalar items only)`);
1558
- }
1559
- }
1560
- if (quote !== null) {
1561
- throw new Error(`unterminated ${quote} quote in flow-style array in ${filePath}: ${JSON.stringify(raw)}`);
1562
- }
1563
- const items = [];
1564
- for (const part of inner.split(",")) {
1565
- const item = part.trim().replace(/^["']|["']$/g, "");
1566
- if (item === "")
1567
- continue;
1568
- items.push(item);
1569
- }
1570
- return items;
1571
- }
1572
- function isPlainObject2(value) {
1573
- return typeof value === "object" && value !== null && !Array.isArray(value);
1574
- }
1575
- function violation2(severity, code, message, fix) {
1576
- return { ok: false, severity, code, message, fix };
1577
- }
1578
- function validateNonEmptyString(violations, value, field, missingCode, invalidCode) {
1579
- if (value === undefined) {
1580
- violations.push(violation2("high", missingCode, `missing required field: ${field}`));
1581
- } else if (typeof value !== "string" || value.trim() === "") {
1582
- violations.push(violation2("medium", invalidCode, `${field} must be a non-empty string`));
1583
- }
1584
- }
1585
- var DATE_PART = String.raw`\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])`;
1586
- var RFC3339_Z_RE = new RegExp(String.raw`^${DATE_PART}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$`);
1587
- var DATE_ONLY_RE = new RegExp(String.raw`^${DATE_PART}$`);
1588
- function isValidClaimedAt(value) {
1589
- return typeof value === "string" && (RFC3339_Z_RE.test(value) || DATE_ONLY_RE.test(value));
1590
- }
1591
- function validateExecutionLease(lease) {
1592
- const violations = [];
1593
- if (!isPlainObject2(lease)) {
1594
- return {
1595
- ok: false,
1596
- violations: [
1597
- violation2("high", "lease.execution-lease.invalid", "execution_lease must be an object — null and tombstone objects are invalid; writers delete the key on release")
1598
- ]
1599
- };
1600
- }
1601
- validateNonEmptyString(violations, lease.holder, "holder", "lease.execution-lease.missing-holder", "lease.execution-lease.invalid-holder");
1602
- if (lease.claimed_at === undefined) {
1603
- violations.push(violation2("high", "lease.execution-lease.missing-claimed-at", "missing required field: claimed_at"));
1604
- } else if (!isValidClaimedAt(lease.claimed_at)) {
1605
- violations.push(violation2("medium", "lease.execution-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T02:30:00Z) or a YYYY-MM-DD date"));
1606
- }
1607
- if (lease.worktree_path === undefined) {
1608
- violations.push(violation2("high", "lease.execution-lease.missing-worktree-path", "missing required field: worktree_path"));
1609
- } else if (typeof lease.worktree_path !== "string" || lease.worktree_path.trim() === "") {
1610
- violations.push(violation2("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be a non-empty string"));
1611
- } else if (!isAbsolute2(lease.worktree_path)) {
1612
- violations.push(violation2("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be an absolute path — it identifies the dedicated feature-worktree root (and MUST differ from metadata.control_worktree_path)"));
1613
- }
1614
- validateNonEmptyString(violations, lease.working_branch, "working_branch", "lease.execution-lease.missing-working-branch", "lease.execution-lease.invalid-working-branch");
1615
- if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
1616
- violations.push(violation2("medium", "lease.execution-lease.invalid-session-label", "session_label must be a string (display only — never used for ownership comparison)"));
1617
- }
1618
- return { ok: violations.length === 0, violations };
1619
- }
1620
- function validateIntegrationMergeLease(lease) {
1621
- const violations = [];
1622
- if (!isPlainObject2(lease)) {
1623
- return {
1624
- ok: false,
1625
- violations: [
1626
- violation2("high", "lease.merge-lease.invalid", "integration_merge_lease must be an object — absent means unclaimed; null and tombstone objects are invalid; writers delete the key on release")
1627
- ]
1628
- };
1629
- }
1630
- validateNonEmptyString(violations, lease.holder, "holder", "lease.merge-lease.missing-holder", "lease.merge-lease.invalid-holder");
1631
- if (lease.claimed_at === undefined) {
1632
- violations.push(violation2("high", "lease.merge-lease.missing-claimed-at", "missing required field: claimed_at"));
1633
- } else if (!isValidClaimedAt(lease.claimed_at)) {
1634
- violations.push(violation2("medium", "lease.merge-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T04:00:00Z) or a YYYY-MM-DD date"));
1635
- }
1636
- validateNonEmptyString(violations, lease.plan_id, "plan_id", "lease.merge-lease.missing-plan-id", "lease.merge-lease.invalid-plan-id");
1637
- validateNonEmptyString(violations, lease.source_branch, "source_branch", "lease.merge-lease.missing-source-branch", "lease.merge-lease.invalid-source-branch");
1638
- validateNonEmptyString(violations, lease.target_branch, "target_branch", "lease.merge-lease.missing-target-branch", "lease.merge-lease.invalid-target-branch");
1639
- if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
1640
- violations.push(violation2("medium", "lease.merge-lease.invalid-session-label", "session_label must be a string (display only — never used for ownership comparison)"));
1641
- }
1642
- return { ok: violations.length === 0, violations };
1643
- }
1644
- function planExecutionLeaseLocations(row) {
1645
- return { row: row.execution_lease };
1646
- }
1647
- function verifyPlanExecutionLease(row, planId) {
1648
- const { row: rowLease } = planExecutionLeaseLocations(row);
1649
- const lease = rowLease;
1650
- if (lease === undefined) {
1651
- if (row.status === "InProgress") {
1652
- return {
1653
- ok: false,
1654
- violations: [
1655
- violation2("high", "lease.verify.orphan", "plan is InProgress without an execution_lease — orphan: STOP, no writable dispatch until recovery (status-and-residuals.md § Orphan recovery)")
1656
- ]
1657
- };
1658
- }
1659
- return {
1660
- ok: false,
1661
- violations: [
1662
- violation2("high", "lease.verify.missing", `plan ${planId} has no execution_lease at the SSOT location plans[].execution_lease`)
1663
- ]
1664
- };
1665
- }
1666
- const violations = [];
1667
- violations.push(...validateExecutionLease(lease).violations);
1668
- return { ok: violations.length === 0, violations, lease };
1669
- }
1670
- var heldLockDirs = new AsyncLocalStorage;
1671
- var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"';
1672
- var REQUIRED_FIELDS = [
1673
- { key: "executeAs", label: "Execute as", code: "execute-as" },
1674
- { key: "delegation", label: "Delegation", code: "delegation" },
1675
- { key: "taskCategory", label: "Task category", code: "task-category" }
1676
- ];
1677
- function violation3(severity, code, message, fix) {
1678
- return { ok: false, severity, code, message, fix };
1679
- }
1680
- function parseAssignmentFields(assignmentText) {
1681
- const fields = {};
1682
- for (const line of assignmentText.split(/\r?\n/)) {
1683
- const match = line.match(/^[ \t]*(?:[-*][ \t]+)?\*\*\s*([^*:]+?)\s*\*\*\s*:\s*(.*)$/) ?? line.match(/^[ \t]*(?:[-*][ \t]+)?([A-Za-z][A-Za-z -]*?)\s*:\s*(.*)$/);
1684
- if (!match)
1685
- continue;
1686
- const label = match[1].trim();
1687
- const value = match[2].trim();
1688
- const known = REQUIRED_FIELDS.find((f) => f.label === label);
1689
- if (known) {
1690
- fields[known.key] = value;
1691
- continue;
1692
- }
1693
- if (label === "Working branch")
1694
- fields.workingBranch = value;
1695
- else if (label === "Branch policy")
1696
- fields.branchPolicy = value;
1697
- }
1698
- return fields;
1699
- }
1700
- var ASSIGNMENT_ENFORCEMENT_BOLD_RE = /^[ \t]*(?:[-*][ \t]+)?\*\*\s*Enforcement\s*\*\*\s*:\s*(.*)$/m;
1701
- var ASSIGNMENT_ENFORCEMENT_PLAIN_RE = /^[ \t]*(?:[-*][ \t]+)?Enforcement\s*:\s*(.*)$/m;
1702
- var COMPASS_ENFORCEMENT_RE = /^enforcement\s*:\s*(.*)$/m;
1703
- function enforcementValue(raw) {
1704
- const value = raw.trim();
1705
- const unquoted = value.replace(/^(['"])(.*)\1$/, "$2");
1706
- return unquoted.trim().toLowerCase();
1707
- }
1708
- var ASSIGNMENT_BODY_START_RE = /^(?:#{1,6}[ \t]+Task\b|-{3,}[ \t]*$|#[ \t])/m;
1709
- function assignmentHeaderRegion(assignmentText) {
1710
- const marker = assignmentText.match(ASSIGNMENT_BODY_START_RE);
1711
- return marker !== null ? assignmentText.slice(0, marker.index) : assignmentText;
1712
- }
1713
- function parseEnforcementFlag(text) {
1714
- const bold = text.match(ASSIGNMENT_ENFORCEMENT_BOLD_RE);
1715
- if (bold !== null)
1716
- return { hard: enforcementValue(bold[1]) === "hard", source: "assignment" };
1717
- const plain = text.match(ASSIGNMENT_ENFORCEMENT_PLAIN_RE);
1718
- if (plain !== null)
1719
- return { hard: enforcementValue(plain[1]) === "hard", source: "assignment" };
1720
- const compass = text.match(COMPASS_ENFORCEMENT_RE);
1721
- if (compass !== null)
1722
- return { hard: enforcementValue(compass[1]) === "hard", source: "compass" };
1723
- return { hard: false, source: "none" };
1724
- }
1725
- function requireField(violations, value, label, code) {
1726
- if (value === undefined) {
1727
- const v = violation3("high", `assignment.field.missing-${code}`, `missing required Assignment field: ${label}`, `add "**${label}**: <value>" to the Assignment`);
1728
- v.aliases = [`assignment.presence.missing-${code}`];
1729
- violations.push(v);
1730
- } else if (value === "") {
1731
- const v = violation3("high", `assignment.field.invalid-${code}`, `${label} must be non-empty`, `fill in "**${label}**: <value>"`);
1732
- v.aliases = [`assignment.presence.missing-${code}`];
1733
- violations.push(v);
1452
+ function requireField(violations, value, label, code) {
1453
+ if (value === undefined) {
1454
+ const v = violation2("high", `assignment.field.missing-${code}`, `missing required Assignment field: ${label}`, `add "**${label}**: <value>" to the Assignment`);
1455
+ v.aliases = [`assignment.presence.missing-${code}`];
1456
+ violations.push(v);
1457
+ } else if (value === "") {
1458
+ const v = violation2("high", `assignment.field.invalid-${code}`, `${label} must be non-empty`, `fill in "**${label}**: <value>"`);
1459
+ v.aliases = [`assignment.presence.missing-${code}`];
1460
+ violations.push(v);
1734
1461
  }
1735
1462
  }
1736
1463
  function parseWorkingBranchValue(value) {
@@ -1787,20 +1514,20 @@ function validateAssignmentFields(assignmentText, opts = {}) {
1787
1514
  const formCount = Number(workingPresent) + Number(policyPresent);
1788
1515
  const forms = parseAssignmentBranchForms(assignmentText);
1789
1516
  if (formCount === 0) {
1790
- violations.push(violation3("high", "assignment.field.branch-missing", "writable assignment must contain exactly one branch form", `add exactly one of: ${BRANCH_FORMS_HINT}`));
1517
+ violations.push(violation2("high", "assignment.field.branch-missing", "writable assignment must contain exactly one branch form", `add exactly one of: ${BRANCH_FORMS_HINT}`));
1791
1518
  } else if (formCount > 1) {
1792
- violations.push(violation3("high", "assignment.field.branch-multiple", `writable assignment contains ${formCount} branch forms (Working branch + Branch policy) — exactly one required`, `keep exactly one of: ${BRANCH_FORMS_HINT}`));
1519
+ violations.push(violation2("high", "assignment.field.branch-multiple", `writable assignment contains ${formCount} branch forms (Working branch + Branch policy) — exactly one required`, `keep exactly one of: ${BRANCH_FORMS_HINT}`));
1793
1520
  } else if (workingPresent) {
1794
1521
  const create = forms.createForm;
1795
1522
  if (create !== undefined && (create.base === undefined || create.base.trim() === "" || create.name.trim() === "")) {
1796
- violations.push(violation3("high", "assignment.field.branch-missing-base", `create-form Working branch is incomplete: "${fields.workingBranch}" (expected "create <new-branch> from <base>")`, "write both the new branch name and the ancestor branch after `from` (main / existing feature branch / remote-tracking branch / `current`)"));
1523
+ violations.push(violation2("high", "assignment.field.branch-missing-base", `create-form Working branch is incomplete: "${fields.workingBranch}" (expected "create <new-branch> from <base>")`, "write both the new branch name and the ancestor branch after `from` (main / existing feature branch / remote-tracking branch / `current`)"));
1797
1524
  }
1798
1525
  } else if (policyPresent) {
1799
1526
  const direct = forms.directOn;
1800
1527
  if (direct === undefined) {
1801
- violations.push(violation3("high", "assignment.field.branch-policy-missing-branch", `unparseable Branch policy: "${fields.branchPolicy}" (expected "direct on <branch> — <reason>")`, "start the field with `direct on <branch>`"));
1528
+ violations.push(violation2("high", "assignment.field.branch-policy-missing-branch", `unparseable Branch policy: "${fields.branchPolicy}" (expected "direct on <branch> — <reason>")`, "start the field with `direct on <branch>`"));
1802
1529
  } else if (direct.reason === "") {
1803
- violations.push(violation3("high", "assignment.field.branch-policy-missing-reason", `Branch policy "direct on ${direct.branch}" is missing the reason`, 'append "— <reason>" after the branch name'));
1530
+ violations.push(violation2("high", "assignment.field.branch-policy-missing-reason", `Branch policy "direct on ${direct.branch}" is missing the reason`, 'append "— <reason>" after the branch name'));
1804
1531
  }
1805
1532
  }
1806
1533
  }
@@ -1811,7 +1538,7 @@ function assertDefaultBranchProtected(branch, opts = {}) {
1811
1538
  const violations = [];
1812
1539
  const normalized = branch.trim();
1813
1540
  if (normalized !== "" && defaultBranches.includes(normalized) && opts.directOnException !== true) {
1814
- violations.push(violation3("high", "dispatch.default-branch.protected", `writable work on default protected branch "${normalized}" requires an explicit direct-on exception`, `add "Branch policy: direct on ${normalized} — <reason>" to the Assignment, or use a feature branch`));
1541
+ violations.push(violation2("high", "dispatch.default-branch.protected", `writable work on default protected branch "${normalized}" requires an explicit direct-on exception`, `add "Branch policy: direct on ${normalized} — <reason>" to the Assignment, or use a feature branch`));
1815
1542
  }
1816
1543
  return { ok: violations.length === 0, violations };
1817
1544
  }
@@ -1820,7 +1547,7 @@ function executionModeToN(executionMode, opts = {}) {
1820
1547
  const mode = executionMode.trim().toLowerCase().split(/\s+/)[0] ?? "";
1821
1548
  let n;
1822
1549
  if (mode === "") {
1823
- violations.push(violation3("high", "dispatch.execution-mode.missing", "missing required Assignment field: Execution mode", 'add "**Execution mode**: sdd | inline | targeted"'));
1550
+ violations.push(violation2("high", "dispatch.execution-mode.missing", "missing required Assignment field: Execution mode", 'add "**Execution mode**: sdd | inline | targeted"'));
1824
1551
  } else if (mode === "sdd") {
1825
1552
  n = 3;
1826
1553
  } else if (mode === "inline") {
@@ -1828,14 +1555,14 @@ function executionModeToN(executionMode, opts = {}) {
1828
1555
  } else if (mode === "targeted") {
1829
1556
  const seats = [...new Set((opts.seats ?? []).map((s) => s.trim()).filter((s) => s !== ""))];
1830
1557
  if (seats.length === 0) {
1831
- violations.push(violation3("high", "dispatch.execution-mode.missing-seats", 'execution mode "targeted" requires listed reviewer seats', 'add "QC re-review: targeted — reviewers: <role-id>, …" to the Assignment and pass the seats'));
1558
+ violations.push(violation2("high", "dispatch.execution-mode.missing-seats", 'execution mode "targeted" requires listed reviewer seats', 'add "QC re-review: targeted — reviewers: <role-id>, …" to the Assignment and pass the seats'));
1832
1559
  } else if (seats.length > 3) {
1833
- violations.push(violation3("high", "dispatch.execution-mode.too-many-seats", `execution mode "targeted" lists ${seats.length} reviewer seats — at most 3 (targeted re-review seats are the tri seats, N = 1–3)`, "list at most three reviewer seats for the targeted re-review"));
1560
+ violations.push(violation2("high", "dispatch.execution-mode.too-many-seats", `execution mode "targeted" lists ${seats.length} reviewer seats — at most 3 (targeted re-review seats are the tri seats, N = 1–3)`, "list at most three reviewer seats for the targeted re-review"));
1834
1561
  } else {
1835
1562
  n = seats.length;
1836
1563
  }
1837
1564
  } else {
1838
- violations.push(violation3("high", "dispatch.execution-mode.unknown", `unknown execution mode "${executionMode.trim()}" (expected sdd | inline | targeted)`, "fix the Execution mode field"));
1565
+ violations.push(violation2("high", "dispatch.execution-mode.unknown", `unknown execution mode "${executionMode.trim()}" (expected sdd | inline | targeted)`, "fix the Execution mode field"));
1839
1566
  }
1840
1567
  return n === undefined ? { ok: false, violations } : { ok: true, violations, n };
1841
1568
  }
@@ -1883,7 +1610,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
1883
1610
  return {
1884
1611
  ok: false,
1885
1612
  violations: [
1886
- 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")
1613
+ violation2("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")
1887
1614
  ]
1888
1615
  };
1889
1616
  }
@@ -1891,7 +1618,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
1891
1618
  return {
1892
1619
  ok: false,
1893
1620
  violations: [
1894
- violation3("critical", "dispatch.anti-recursion.self-type", `recursive dispatch refused: role binding "${subagentType}" equals Execute as "${executeAs}" (leaf executors must not re-invoke their own role)`, "complete the work in this session, or return Blocked to project-manager")
1621
+ violation2("critical", "dispatch.anti-recursion.self-type", `recursive dispatch refused: role binding "${subagentType}" equals Execute as "${executeAs}" (leaf executors must not re-invoke their own role)`, "complete the work in this session, or return Blocked to project-manager")
1895
1622
  ]
1896
1623
  };
1897
1624
  }
@@ -1901,45 +1628,50 @@ var WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
1901
1628
  var WORKFLOW_LIFECYCLE_STATUSES = ["running", "paused", "completed", "failed", "stopped"];
1902
1629
  var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
1903
1630
  var WORKFLOW_LIFECYCLE_TYPES = ["plan", "iteration"];
1904
- function isPlainObject3(value) {
1631
+ function isPlainObject2(value) {
1905
1632
  return typeof value === "object" && value !== null && !Array.isArray(value);
1906
1633
  }
1907
- function violation4(severity, code, message, fix) {
1634
+ function violation3(severity, code, message, fix) {
1908
1635
  return { ok: false, severity, code, message, fix };
1909
1636
  }
1910
1637
  function validateNonEmptyString2(violations, value, field, missingCode, invalidCode) {
1911
1638
  if (value === undefined) {
1912
- violations.push(violation4("high", missingCode, `missing required field: ${field}`));
1639
+ violations.push(violation3("high", missingCode, `missing required field: ${field}`));
1913
1640
  } else if (typeof value !== "string" || value.trim() === "") {
1914
- violations.push(violation4("medium", invalidCode, `${field} must be a non-empty string`));
1641
+ violations.push(violation3("medium", invalidCode, `${field} must be a non-empty string`));
1642
+ }
1643
+ }
1644
+ function validateWorktreePathValue(violations, value, field) {
1645
+ if (typeof value !== "string" || value.trim() === "" || !isAbsolute4(value)) {
1646
+ violations.push(violation3("high", "workflow.snapshot.invalid-integration-worktree-path", `${field} must be a non-empty absolute path — got ${JSON.stringify(value)}`, "record the absolute integration checkout path (integration_worktree_path)"));
1915
1647
  }
1916
1648
  }
1917
1649
  function validateWorkflowSnapshot(doc) {
1918
1650
  const violations = [];
1919
- if (!isPlainObject3(doc)) {
1651
+ if (!isPlainObject2(doc)) {
1920
1652
  return {
1921
1653
  ok: false,
1922
- violations: [violation4("high", "workflow.snapshot.invalid", "workflow snapshot must be an object")]
1654
+ violations: [violation3("high", "workflow.snapshot.invalid", "workflow snapshot must be an object")]
1923
1655
  };
1924
1656
  }
1925
1657
  if (doc.schema_version === undefined) {
1926
- violations.push(violation4("high", "workflow.snapshot.missing-schema-version", "missing required field: schema_version"));
1658
+ violations.push(violation3("high", "workflow.snapshot.missing-schema-version", "missing required field: schema_version"));
1927
1659
  } else if (doc.schema_version !== 1) {
1928
- violations.push(violation4("high", "workflow.snapshot.invalid-schema-version", `schema_version must be 1 — got ${JSON.stringify(doc.schema_version)} (version is reserved for the root file discriminator)`));
1660
+ violations.push(violation3("high", "workflow.snapshot.invalid-schema-version", `schema_version must be 1 — got ${JSON.stringify(doc.schema_version)} (version is reserved for the root file discriminator)`));
1929
1661
  }
1930
1662
  if (doc.version !== undefined) {
1931
- violations.push(violation4("medium", "workflow.snapshot.reserved-version", `top-level version is reserved for the root status.json discriminator — snapshots use schema_version; remove the version key (got ${JSON.stringify(doc.version)})`, "remove the version key from the snapshot"));
1663
+ violations.push(violation3("medium", "workflow.snapshot.reserved-version", `top-level version is reserved for the root status.json discriminator — snapshots use schema_version; remove the version key (got ${JSON.stringify(doc.version)})`, "remove the version key from the snapshot"));
1932
1664
  }
1933
1665
  validateNonEmptyString2(violations, doc.id, "id", "workflow.snapshot.missing-id", "workflow.snapshot.invalid-id");
1934
1666
  if (doc.type === undefined) {
1935
- violations.push(violation4("high", "workflow.snapshot.missing-type", "missing required field: type"));
1667
+ violations.push(violation3("high", "workflow.snapshot.missing-type", "missing required field: type"));
1936
1668
  } else if (typeof doc.type !== "string" || !WORKFLOW_LIFECYCLE_TYPES.includes(doc.type)) {
1937
- violations.push(violation4("medium", "workflow.snapshot.invalid-type", `type must be one of ${WORKFLOW_LIFECYCLE_TYPES.join(" | ")} — got ${JSON.stringify(doc.type)}`));
1669
+ violations.push(violation3("medium", "workflow.snapshot.invalid-type", `type must be one of ${WORKFLOW_LIFECYCLE_TYPES.join(" | ")} — got ${JSON.stringify(doc.type)}`));
1938
1670
  }
1939
1671
  if (doc.status === undefined) {
1940
- violations.push(violation4("high", "workflow.snapshot.missing-status", "missing required field: status"));
1672
+ violations.push(violation3("high", "workflow.snapshot.missing-status", "missing required field: status"));
1941
1673
  } else if (typeof doc.status !== "string" || !WORKFLOW_LIFECYCLE_STATUSES.includes(doc.status)) {
1942
- violations.push(violation4("medium", "workflow.snapshot.invalid-status", `status must be one of ${WORKFLOW_LIFECYCLE_STATUSES.join(" | ")} — got ${JSON.stringify(doc.status)}`));
1674
+ violations.push(violation3("medium", "workflow.snapshot.invalid-status", `status must be one of ${WORKFLOW_LIFECYCLE_STATUSES.join(" | ")} — got ${JSON.stringify(doc.status)}`));
1943
1675
  }
1944
1676
  validateNonEmptyString2(violations, doc.started_at, "started_at", "workflow.snapshot.missing-started-at", "workflow.snapshot.invalid-started-at");
1945
1677
  validateNonEmptyString2(violations, doc.updated_at, "updated_at", "workflow.snapshot.missing-updated-at", "workflow.snapshot.invalid-updated-at");
@@ -1947,44 +1679,54 @@ function validateWorkflowSnapshot(doc) {
1947
1679
  validateNonEmptyString2(violations, doc.ended_at, "ended_at", "workflow.snapshot.missing-ended-at", "workflow.snapshot.invalid-ended-at");
1948
1680
  }
1949
1681
  if (doc.phase !== undefined && typeof doc.phase !== "string") {
1950
- violations.push(violation4("medium", "workflow.snapshot.invalid-phase", "phase must be a string (free-form phase machine label)"));
1682
+ violations.push(violation3("medium", "workflow.snapshot.invalid-phase", "phase must be a string (free-form phase machine label)"));
1951
1683
  }
1952
1684
  if (doc.plans === undefined) {
1953
- violations.push(violation4("high", "workflow.snapshot.missing-plans", "missing required field: plans"));
1685
+ violations.push(violation3("high", "workflow.snapshot.missing-plans", "missing required field: plans"));
1954
1686
  } else if (!Array.isArray(doc.plans)) {
1955
- violations.push(violation4("high", "workflow.snapshot.invalid-plans", "plans must be an array of legacy plan rows"));
1687
+ violations.push(violation3("high", "workflow.snapshot.invalid-plans", "plans must be an array of legacy plan rows"));
1956
1688
  } else {
1957
1689
  for (const row of doc.plans) {
1958
1690
  violations.push(...validatePlanRow(row).violations);
1959
- if (isPlainObject3(row) && row.execution_lease !== undefined) {
1691
+ if (isPlainObject2(row) && row.execution_lease !== undefined) {
1960
1692
  violations.push(...validateExecutionLease(row.execution_lease).violations);
1961
1693
  }
1962
1694
  }
1963
1695
  }
1964
1696
  if (doc.execution_policy !== undefined) {
1965
- if (!isPlainObject3(doc.execution_policy)) {
1966
- violations.push(violation4("medium", "workflow.snapshot.invalid-execution-policy", "execution_policy must be an object"));
1697
+ if (!isPlainObject2(doc.execution_policy)) {
1698
+ violations.push(violation3("medium", "workflow.snapshot.invalid-execution-policy", "execution_policy must be an object"));
1967
1699
  }
1968
1700
  }
1969
1701
  if (doc.integration_merge_lease !== undefined) {
1970
1702
  violations.push(...validateIntegrationMergeLease(doc.integration_merge_lease).violations);
1971
1703
  }
1972
1704
  if (doc.branch !== undefined) {
1973
- if (!isPlainObject3(doc.branch)) {
1974
- violations.push(violation4("medium", "workflow.snapshot.invalid-branch", "branch must be an object"));
1705
+ if (!isPlainObject2(doc.branch)) {
1706
+ violations.push(violation3("medium", "workflow.snapshot.invalid-branch", "branch must be an object"));
1975
1707
  } else {
1976
1708
  for (const key of ["base", "integration", "target"]) {
1977
1709
  if (doc.branch[key] !== undefined && (typeof doc.branch[key] !== "string" || doc.branch[key].trim() === "")) {
1978
- violations.push(violation4("medium", "workflow.snapshot.invalid-branch", `branch.${key} must be a non-empty string`));
1710
+ violations.push(violation3("medium", "workflow.snapshot.invalid-branch", `branch.${key} must be a non-empty string`));
1979
1711
  }
1980
1712
  }
1981
1713
  }
1982
1714
  }
1983
- if (doc.control_worktree_path !== undefined) {
1984
- validateNonEmptyString2(violations, doc.control_worktree_path, "control_worktree_path", "workflow.snapshot.missing-control-worktree-path", "workflow.snapshot.invalid-control-worktree-path");
1715
+ const legacyWorktreePath = doc.control_worktree_path;
1716
+ const canonicalWorktreePath = doc.integration_worktree_path;
1717
+ if (legacyWorktreePath !== undefined && canonicalWorktreePath !== undefined) {
1718
+ violations.push(violation3("high", "workflow.snapshot.conflicting-worktree-paths", "both integration_worktree_path and the legacy control_worktree_path key are present — the canonical snapshot carries only integration_worktree_path (refused even when the values are equal)", "remove the legacy control_worktree_path key"));
1719
+ } else {
1720
+ if (canonicalWorktreePath !== undefined) {
1721
+ validateWorktreePathValue(violations, canonicalWorktreePath, "integration_worktree_path");
1722
+ }
1723
+ if (legacyWorktreePath !== undefined) {
1724
+ violations.push(violation3("medium", "workflow.snapshot.legacy-control-worktree-path", "legacy control_worktree_path is present — the canonical reader normalizes it to integration_worktree_path in memory; migrate on the next authorized write (writers emit only the canonical key)", "rename control_worktree_path to integration_worktree_path on the next authorized write"));
1725
+ validateWorktreePathValue(violations, legacyWorktreePath, "control_worktree_path (legacy alias)");
1726
+ }
1985
1727
  }
1986
- if (doc.legacy_metadata !== undefined && !isPlainObject3(doc.legacy_metadata)) {
1987
- violations.push(violation4("medium", "workflow.snapshot.invalid-legacy-metadata", "legacy_metadata must be an object"));
1728
+ if (doc.legacy_metadata !== undefined && !isPlainObject2(doc.legacy_metadata)) {
1729
+ violations.push(violation3("medium", "workflow.snapshot.invalid-legacy-metadata", "legacy_metadata must be an object"));
1988
1730
  }
1989
1731
  if (doc.compass_ref !== undefined) {
1990
1732
  validateNonEmptyString2(violations, doc.compass_ref, "compass_ref", "workflow.snapshot.missing-compass-ref", "workflow.snapshot.invalid-compass-ref");
@@ -1992,30 +1734,73 @@ function validateWorkflowSnapshot(doc) {
1992
1734
  const terminal = typeof doc.status === "string" && WORKFLOW_TERMINAL_STATUSES.includes(doc.status);
1993
1735
  if (terminal) {
1994
1736
  if (doc.ended_at === undefined) {
1995
- violations.push(violation4("high", "workflow.snapshot.missing-ended-at", `terminal status ${JSON.stringify(doc.status)} requires ended_at — a terminal snapshot must record when the lifecycle ended`));
1737
+ violations.push(violation3("high", "workflow.snapshot.missing-ended-at", `terminal status ${JSON.stringify(doc.status)} requires ended_at — a terminal snapshot must record when the lifecycle ended`));
1996
1738
  }
1997
1739
  if (Array.isArray(doc.plans)) {
1998
1740
  for (const row of doc.plans) {
1999
- if (isPlainObject3(row) && row.execution_lease !== undefined) {
2000
- violations.push(violation4("high", "workflow.snapshot.terminal-dangling-execution-lease", `terminal snapshot must not carry a row execution_lease (dangling lease) — release every lease before the lifecycle ends`));
1741
+ if (isPlainObject2(row) && row.execution_lease !== undefined) {
1742
+ violations.push(violation3("high", "workflow.snapshot.terminal-dangling-execution-lease", `terminal snapshot must not carry a row execution_lease (dangling lease) — release every lease before the lifecycle ends`));
2001
1743
  }
2002
1744
  }
2003
1745
  }
2004
1746
  if (doc.integration_merge_lease !== undefined) {
2005
- violations.push(violation4("high", "workflow.snapshot.terminal-dangling-merge-lease", "terminal snapshot must not carry integration_merge_lease (dangling lease) — release the merge lease before the lifecycle ends"));
1747
+ violations.push(violation3("high", "workflow.snapshot.terminal-dangling-merge-lease", "terminal snapshot must not carry integration_merge_lease (dangling lease) — release the merge lease before the lifecycle ends"));
2006
1748
  }
2007
1749
  }
2008
1750
  return { ok: violations.length === 0, violations };
2009
1751
  }
2010
- var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
2011
- var PLAN_STATUSES = ["Todo", "InProgress", "InReview", "Blocked", "Done"];
2012
- var RESIDUAL_DECISIONS = ["defer", "accept", "risk-accepted"];
2013
- var RESIDUAL_LIFECYCLES = ["open", "resolved", "waived", "superseded", "duplicate"];
2014
- function isPlainObject4(value) {
2015
- return typeof value === "object" && value !== null && !Array.isArray(value);
2016
- }
2017
- function violation5(severity, code, message, fix) {
2018
- return { ok: false, severity, code, message, fix };
1752
+ var LEGACY_WORKTREE_PATH_CODE = "workflow.snapshot.legacy-control-worktree-path";
1753
+
1754
+ class WorkflowSnapshotValidationError extends Error {
1755
+ violations;
1756
+ constructor(message, violations) {
1757
+ super(message);
1758
+ this.violations = violations;
1759
+ }
1760
+ }
1761
+ function readWorkflowSnapshot(dir) {
1762
+ const snapshotPath = join5(dir, WORKFLOW_SNAPSHOT_FILE);
1763
+ if (!existsSync3(snapshotPath)) {
1764
+ throw new Error(`workflow snapshot not found: ${snapshotPath}`);
1765
+ }
1766
+ let doc;
1767
+ try {
1768
+ doc = JSON.parse(readFileSync3(snapshotPath, "utf8"));
1769
+ } catch (error) {
1770
+ throw new Error(`Invalid JSON in ${snapshotPath}: ${error.message}`);
1771
+ }
1772
+ return normalizeWorkflowSnapshot(doc, snapshotPath);
1773
+ }
1774
+ function normalizeWorkflowSnapshot(doc, snapshotPath) {
1775
+ const gate = validateWorkflowSnapshot(doc);
1776
+ const migration = gate.violations.filter((v) => v.code === LEGACY_WORKTREE_PATH_CODE);
1777
+ const blocking = gate.violations.filter((v) => v.code !== LEGACY_WORKTREE_PATH_CODE);
1778
+ if (blocking.length > 0) {
1779
+ const detail = blocking.map((v) => `${v.code}: ${v.message}`).join("; ");
1780
+ throw new WorkflowSnapshotValidationError(`refusing to read invalid workflow snapshot ${snapshotPath}: ${detail}`, blocking);
1781
+ }
1782
+ if (migration.length === 0) {
1783
+ return { snapshot: doc, diagnostics: [] };
1784
+ }
1785
+ const raw = doc;
1786
+ const { control_worktree_path: _legacy, ...rest } = raw;
1787
+ const normalized = { ...rest, integration_worktree_path: raw.control_worktree_path };
1788
+ const revalidated = validateWorkflowSnapshot(normalized);
1789
+ if (!revalidated.ok) {
1790
+ const detail = revalidated.violations.map((v) => `${v.code}: ${v.message}`).join("; ");
1791
+ throw new Error(`refusing to read workflow snapshot ${snapshotPath}: legacy normalization produced an invalid document: ${detail}`);
1792
+ }
1793
+ return { snapshot: normalized, diagnostics: migration };
1794
+ }
1795
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
1796
+ var PLAN_STATUSES = ["Todo", "InProgress", "InReview", "Blocked", "Done"];
1797
+ var RESIDUAL_DECISIONS = ["defer", "accept", "risk-accepted"];
1798
+ var RESIDUAL_LIFECYCLES = ["open", "resolved", "waived", "superseded", "duplicate"];
1799
+ function isPlainObject3(value) {
1800
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1801
+ }
1802
+ function violation4(severity, code, message, fix) {
1803
+ return { ok: false, severity, code, message, fix };
2019
1804
  }
2020
1805
  function normalizeSeverity(value) {
2021
1806
  if (value === "warning")
@@ -2031,19 +1816,19 @@ function isOpenResidual(entry) {
2031
1816
  }
2032
1817
  function validateNonEmptyString3(violations, value, field, missingCode, invalidCode) {
2033
1818
  if (value === undefined) {
2034
- violations.push(violation5("high", missingCode, `missing required field: ${field}`));
1819
+ violations.push(violation4("high", missingCode, `missing required field: ${field}`));
2035
1820
  } else if (typeof value !== "string" || value.trim() === "") {
2036
- violations.push(violation5("medium", invalidCode, `${field} must be a non-empty string`));
1821
+ violations.push(violation4("medium", invalidCode, `${field} must be a non-empty string`));
2037
1822
  }
2038
1823
  }
2039
1824
  function validatePlanRow(row) {
2040
1825
  const violations = [];
2041
- if (!isPlainObject4(row)) {
2042
- return { ok: false, violations: [violation5("high", "status.plan-row.invalid", "plan row must be an object")] };
1826
+ if (!isPlainObject3(row)) {
1827
+ return { ok: false, violations: [violation4("high", "status.plan-row.invalid", "plan row must be an object")] };
2043
1828
  }
2044
1829
  const { id, plan_id: planId, title, file, status, metadata, execution_lease } = row;
2045
1830
  if (id === undefined && planId === undefined) {
2046
- violations.push(violation5("high", "status.plan-row.missing-id", "missing required field: id (or legacy plan_id)"));
1831
+ violations.push(violation4("high", "status.plan-row.missing-id", "missing required field: id (or legacy plan_id)"));
2047
1832
  } else {
2048
1833
  if (id !== undefined) {
2049
1834
  validateNonEmptyString3(violations, id, "id", "status.plan-row.missing-id", "status.plan-row.invalid-id");
@@ -2052,31 +1837,31 @@ function validatePlanRow(row) {
2052
1837
  validateNonEmptyString3(violations, planId, "plan_id", "status.plan-row.missing-plan-id", "status.plan-row.invalid-plan-id");
2053
1838
  }
2054
1839
  if (id !== undefined && planId !== undefined && id !== planId) {
2055
- violations.push(violation5("medium", "status.plan-row.dual-id", "row has both id and plan_id with different values — write one canonical key (prefer id)"));
1840
+ violations.push(violation4("medium", "status.plan-row.dual-id", "row has both id and plan_id with different values — write one canonical key (prefer id)"));
2056
1841
  }
2057
1842
  }
2058
1843
  validateNonEmptyString3(violations, title, "title", "status.plan-row.missing-title", "status.plan-row.invalid-title");
2059
1844
  validateNonEmptyString3(violations, file, "file", "status.plan-row.missing-file", "status.plan-row.invalid-file");
2060
1845
  if (status === undefined) {
2061
- violations.push(violation5("high", "status.plan-row.missing-status", "missing required field: status"));
1846
+ violations.push(violation4("high", "status.plan-row.missing-status", "missing required field: status"));
2062
1847
  } else if (typeof status !== "string" || !PLAN_STATUSES.includes(status)) {
2063
- violations.push(violation5("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")} — got ${JSON.stringify(status)}`));
1848
+ violations.push(violation4("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")} — got ${JSON.stringify(status)}`));
2064
1849
  }
2065
- if (metadata !== undefined && !isPlainObject4(metadata)) {
2066
- violations.push(violation5("medium", "status.plan-row.invalid-metadata", "metadata must be an object"));
1850
+ if (metadata !== undefined && !isPlainObject3(metadata)) {
1851
+ violations.push(violation4("medium", "status.plan-row.invalid-metadata", "metadata must be an object"));
2067
1852
  }
2068
- if (execution_lease !== undefined && !isPlainObject4(execution_lease)) {
2069
- violations.push(violation5("medium", "status.plan-row.invalid-execution-lease", "execution_lease must be an object"));
1853
+ if (execution_lease !== undefined && !isPlainObject3(execution_lease)) {
1854
+ violations.push(violation4("medium", "status.plan-row.invalid-execution-lease", "execution_lease must be an object"));
2070
1855
  }
2071
1856
  if (status === "Done" && execution_lease !== undefined) {
2072
- violations.push(violation5("medium", "status.plan-row.done-with-lease", 'plan status Done must not carry an execution_lease — the Done authority deletes the lease in the same complete-file update as status: "Done" (status-and-residuals.md § Hold, release, and override)', 'delete plans[].execution_lease in the same update that sets status: "Done"'));
1857
+ violations.push(violation4("medium", "status.plan-row.done-with-lease", 'plan status Done must not carry an execution_lease — the Done authority deletes the lease in the same complete-file update as status: "Done" (status-and-residuals.md § Hold, release, and override)', 'delete plans[].execution_lease in the same update that sets status: "Done"'));
2073
1858
  }
2074
1859
  return { ok: violations.length === 0, violations };
2075
1860
  }
2076
1861
  function validateResidual(entry) {
2077
1862
  const violations = [];
2078
- if (!isPlainObject4(entry)) {
2079
- return { ok: false, violations: [violation5("high", "status.residual.invalid", "residual entry must be an object")] };
1863
+ if (!isPlainObject3(entry)) {
1864
+ return { ok: false, violations: [violation4("high", "status.residual.invalid", "residual entry must be an object")] };
2080
1865
  }
2081
1866
  const { id, title, severity, source, scope, decision, owner, target, tracking, detail_doc, lifecycle, closed_at } = entry;
2082
1867
  validateNonEmptyString3(violations, id, "id", "status.residual.missing-id", "status.residual.invalid-id");
@@ -2085,42 +1870,42 @@ function validateResidual(entry) {
2085
1870
  validateNonEmptyString3(violations, scope, "scope", "status.residual.missing-scope", "status.residual.invalid-scope");
2086
1871
  validateNonEmptyString3(violations, owner, "owner", "status.residual.missing-owner", "status.residual.invalid-owner");
2087
1872
  if (severity === undefined) {
2088
- violations.push(violation5("high", "status.residual.missing-severity", "missing required field: severity"));
1873
+ violations.push(violation4("high", "status.residual.missing-severity", "missing required field: severity"));
2089
1874
  } else if (typeof severity !== "string" || !SEVERITY_ORDER.includes(severity) && severity !== "warning") {
2090
- violations.push(violation5("medium", "status.residual.invalid-severity", `severity must be one of ${SEVERITY_ORDER.join(" | ")} — got ${JSON.stringify(severity)}`));
1875
+ violations.push(violation4("medium", "status.residual.invalid-severity", `severity must be one of ${SEVERITY_ORDER.join(" | ")} — got ${JSON.stringify(severity)}`));
2091
1876
  } else if (severity === "warning") {
2092
- violations.push(violation5("low", "status.residual.legacy-warning", `severity "warning" is legacy — forbidden on new entries; read paths normalize it to "low"`, `use "low" (normalizeSeverity maps 'warning' → 'low')`));
1877
+ violations.push(violation4("low", "status.residual.legacy-warning", `severity "warning" is legacy — forbidden on new entries; read paths normalize it to "low"`, `use "low" (normalizeSeverity maps 'warning' → 'low')`));
2093
1878
  }
2094
1879
  if (decision === undefined) {
2095
- violations.push(violation5("high", "status.residual.missing-decision", "missing required field: decision"));
1880
+ violations.push(violation4("high", "status.residual.missing-decision", "missing required field: decision"));
2096
1881
  } else if (typeof decision !== "string" || !RESIDUAL_DECISIONS.includes(decision)) {
2097
- violations.push(violation5("medium", "status.residual.invalid-decision", `decision must be one of ${RESIDUAL_DECISIONS.join(" | ")} — got ${JSON.stringify(decision)}`));
1882
+ violations.push(violation4("medium", "status.residual.invalid-decision", `decision must be one of ${RESIDUAL_DECISIONS.join(" | ")} — got ${JSON.stringify(decision)}`));
2098
1883
  }
2099
1884
  if (target === undefined) {
2100
- violations.push(violation5("high", "status.residual.missing-target", "missing required field: target"));
1885
+ violations.push(violation4("high", "status.residual.missing-target", "missing required field: target"));
2101
1886
  } else if (typeof target !== "string" && target !== null) {
2102
- violations.push(violation5("medium", "status.residual.invalid-target", "target must be a string or null"));
1887
+ violations.push(violation4("medium", "status.residual.invalid-target", "target must be a string or null"));
2103
1888
  }
2104
1889
  if (tracking === undefined) {
2105
- violations.push(violation5("high", "status.residual.missing-tracking", "missing required field: tracking"));
1890
+ violations.push(violation4("high", "status.residual.missing-tracking", "missing required field: tracking"));
2106
1891
  } else if (typeof tracking !== "string" && tracking !== null) {
2107
- violations.push(violation5("medium", "status.residual.invalid-tracking", "tracking must be a string or null"));
1892
+ violations.push(violation4("medium", "status.residual.invalid-tracking", "tracking must be a string or null"));
2108
1893
  }
2109
1894
  if (detail_doc !== undefined && typeof detail_doc !== "string" && detail_doc !== null) {
2110
- violations.push(violation5("medium", "status.residual.invalid-detail-doc", "detail_doc must be a string or null"));
1895
+ violations.push(violation4("medium", "status.residual.invalid-detail-doc", "detail_doc must be a string or null"));
2111
1896
  }
2112
- if (closed_at !== undefined && (typeof closed_at !== "string" || !DATE_RE2.test(closed_at))) {
2113
- violations.push(violation5("medium", "status.residual.invalid-closed-at", "closed_at must be YYYY-MM-DD"));
1897
+ if (closed_at !== undefined && (typeof closed_at !== "string" || !DATE_RE.test(closed_at))) {
1898
+ violations.push(violation4("medium", "status.residual.invalid-closed-at", "closed_at must be YYYY-MM-DD"));
2114
1899
  }
2115
1900
  if (lifecycle !== undefined) {
2116
1901
  if (typeof lifecycle !== "string" || !RESIDUAL_LIFECYCLES.includes(lifecycle)) {
2117
- violations.push(violation5("medium", "status.residual.invalid-lifecycle", `lifecycle must be one of ${RESIDUAL_LIFECYCLES.join(" | ")} — got ${JSON.stringify(lifecycle)}`));
1902
+ violations.push(violation4("medium", "status.residual.invalid-lifecycle", `lifecycle must be one of ${RESIDUAL_LIFECYCLES.join(" | ")} — got ${JSON.stringify(lifecycle)}`));
2118
1903
  } else if (lifecycle !== "open") {
2119
1904
  if (closed_at === undefined) {
2120
- violations.push(violation5("high", "status.residual.closed-missing-closed-at", `lifecycle "${lifecycle}" requires closed_at (YYYY-MM-DD)`, 'set closed_at (e.g. "2026-08-08")'));
1905
+ violations.push(violation4("high", "status.residual.closed-missing-closed-at", `lifecycle "${lifecycle}" requires closed_at (YYYY-MM-DD)`, 'set closed_at (e.g. "2026-08-08")'));
2121
1906
  }
2122
1907
  if (entry.closure_note === undefined) {
2123
- violations.push(violation5("medium", "status.residual.closed-missing-closure-note", `lifecycle "${lifecycle}" requires closure_note (what changed; how verified)`, "add closure_note explaining the close"));
1908
+ violations.push(violation4("medium", "status.residual.closed-missing-closure-note", `lifecycle "${lifecycle}" requires closure_note (what changed; how verified)`, "add closure_note explaining the close"));
2124
1909
  }
2125
1910
  }
2126
1911
  }
@@ -2135,25 +1920,25 @@ function isHarnessRelativePath(dir) {
2135
1920
  }
2136
1921
  function validateWorkflowEntry(entry) {
2137
1922
  const violations = [];
2138
- if (!isPlainObject4(entry)) {
1923
+ if (!isPlainObject3(entry)) {
2139
1924
  return {
2140
1925
  ok: false,
2141
- violations: [violation5("high", "status.workflow.invalid", "workflow entry must be an object")]
1926
+ violations: [violation4("high", "status.workflow.invalid", "workflow entry must be an object")]
2142
1927
  };
2143
1928
  }
2144
1929
  validateNonEmptyString3(violations, entry.id, "id", "status.workflow.missing-id", "status.workflow.invalid-id");
2145
1930
  if (entry.type === undefined) {
2146
- violations.push(violation5("high", "status.workflow.missing-type", "missing required field: type"));
1931
+ violations.push(violation4("high", "status.workflow.missing-type", "missing required field: type"));
2147
1932
  } else if (typeof entry.type !== "string" || !WORKFLOW_LIFECYCLE_TYPES.includes(entry.type)) {
2148
- violations.push(violation5("medium", "status.workflow.invalid-type", `type must be one of ${WORKFLOW_LIFECYCLE_TYPES.join(" | ")} — got ${JSON.stringify(entry.type)}`));
1933
+ violations.push(violation4("medium", "status.workflow.invalid-type", `type must be one of ${WORKFLOW_LIFECYCLE_TYPES.join(" | ")} — got ${JSON.stringify(entry.type)}`));
2149
1934
  }
2150
1935
  validateNonEmptyString3(violations, entry.started_at, "started_at", "status.workflow.missing-started-at", "status.workflow.invalid-started-at");
2151
1936
  if (entry.dir === undefined) {
2152
- violations.push(violation5("high", "status.workflow.missing-dir", "missing required field: dir"));
1937
+ violations.push(violation4("high", "status.workflow.missing-dir", "missing required field: dir"));
2153
1938
  } else if (typeof entry.dir !== "string" || entry.dir.trim() === "") {
2154
- violations.push(violation5("medium", "status.workflow.invalid-dir", "dir must be a non-empty string"));
1939
+ violations.push(violation4("medium", "status.workflow.invalid-dir", "dir must be a non-empty string"));
2155
1940
  } else if (!isHarnessRelativePath(entry.dir)) {
2156
- violations.push(violation5("medium", "status.workflow.invalid-dir", `dir must be a harness-relative path (no absolute paths, no ".." segments) — got ${JSON.stringify(entry.dir)}`));
1941
+ violations.push(violation4("medium", "status.workflow.invalid-dir", `dir must be a harness-relative path (no absolute paths, no ".." segments) — got ${JSON.stringify(entry.dir)}`));
2157
1942
  }
2158
1943
  return { ok: violations.length === 0, violations };
2159
1944
  }
@@ -2167,20 +1952,20 @@ function validateStatusV2(docOrPath, opts = {}) {
2167
1952
  } catch (error) {
2168
1953
  return {
2169
1954
  ok: false,
2170
- violations: [violation5("high", "status.invalid-json", error.message)]
1955
+ violations: [violation4("high", "status.invalid-json", error.message)]
2171
1956
  };
2172
1957
  }
2173
1958
  } else {
2174
1959
  doc = docOrPath;
2175
1960
  }
2176
- if (!isPlainObject4(doc)) {
2177
- return { ok: false, violations: [violation5("high", "status.invalid-doc", "status document must be an object")] };
1961
+ if (!isPlainObject3(doc)) {
1962
+ return { ok: false, violations: [violation4("high", "status.invalid-doc", "status document must be an object")] };
2178
1963
  }
2179
1964
  if (doc.version !== 2) {
2180
1965
  return {
2181
1966
  ok: false,
2182
1967
  violations: [
2183
- violation5("high", "status.migration-required", `status.json schema version 2 required — got ${JSON.stringify(doc.version)} (v1 or unknown version); run \`mstar migrate\` to convert the tree`, "run `mstar migrate`")
1968
+ violation4("high", "status.migration-required", `status.json schema version 2 required — got ${JSON.stringify(doc.version)} (v1 or unknown version); run \`mstar migrate\` to convert the tree`, "run `mstar migrate`")
2184
1969
  ]
2185
1970
  };
2186
1971
  }
@@ -2188,7 +1973,7 @@ function validateStatusV2(docOrPath, opts = {}) {
2188
1973
  return {
2189
1974
  ok: false,
2190
1975
  violations: [
2191
- violation5("high", "status.migration-required", "v1-shaped status.json (root plans[]) is not a v2 document — run `mstar migrate` to convert the tree", "run `mstar migrate`")
1976
+ violation4("high", "status.migration-required", "v1-shaped status.json (root plans[]) is not a v2 document — run `mstar migrate` to convert the tree", "run `mstar migrate`")
2192
1977
  ]
2193
1978
  };
2194
1979
  }
@@ -2196,122 +1981,399 @@ function validateStatusV2(docOrPath, opts = {}) {
2196
1981
  return {
2197
1982
  ok: false,
2198
1983
  violations: [
2199
- violation5("high", "status.migration-required", "v1-shaped status.json (root residual_findings) is not a v2 document — run `mstar migrate` to convert the tree", "run `mstar migrate`")
1984
+ violation4("high", "status.migration-required", "v1-shaped status.json (root residual_findings) is not a v2 document — run `mstar migrate` to convert the tree", "run `mstar migrate`")
2200
1985
  ]
2201
1986
  };
2202
1987
  }
2203
1988
  const violations = [];
2204
1989
  if (doc.updated_at === undefined) {
2205
- violations.push(violation5("high", "status.missing-updated-at", "missing required field: updated_at"));
2206
- } else if (typeof doc.updated_at !== "string" || !DATE_RE2.test(doc.updated_at)) {
2207
- violations.push(violation5("medium", "status.invalid-updated-at", "updated_at must be YYYY-MM-DD"));
1990
+ violations.push(violation4("high", "status.missing-updated-at", "missing required field: updated_at"));
1991
+ } else if (typeof doc.updated_at !== "string" || !DATE_RE.test(doc.updated_at)) {
1992
+ violations.push(violation4("medium", "status.invalid-updated-at", "updated_at must be YYYY-MM-DD"));
2208
1993
  }
2209
1994
  if (doc.workflows === undefined) {
2210
- violations.push(violation5("high", "status.missing-workflows", "missing required field: workflows"));
1995
+ violations.push(violation4("high", "status.missing-workflows", "missing required field: workflows"));
2211
1996
  } else if (!Array.isArray(doc.workflows)) {
2212
- violations.push(violation5("high", "status.invalid-workflows", "workflows must be an array"));
1997
+ violations.push(violation4("high", "status.invalid-workflows", "workflows must be an array"));
2213
1998
  } else {
2214
1999
  const seen = new Set;
2215
2000
  for (const entry of doc.workflows) {
2216
2001
  violations.push(...validateWorkflowEntry(entry).violations);
2217
- if (isPlainObject4(entry) && typeof entry.id === "string") {
2002
+ if (isPlainObject3(entry) && typeof entry.id === "string") {
2218
2003
  if (seen.has(entry.id)) {
2219
- violations.push(violation5("medium", "status.workflow.duplicate-id", `duplicate workflow id in workflows[]: ${JSON.stringify(entry.id)}`));
2004
+ violations.push(violation4("medium", "status.workflow.duplicate-id", `duplicate workflow id in workflows[]: ${JSON.stringify(entry.id)}`));
2220
2005
  }
2221
2006
  seen.add(entry.id);
2222
2007
  }
2223
2008
  }
2224
2009
  }
2225
- if (harnessDir !== undefined && Array.isArray(doc.workflows)) {
2226
- let realHarnessDir = null;
2227
- try {
2228
- realHarnessDir = realpathSync(harnessDir);
2229
- } catch {}
2230
- for (const entry of doc.workflows) {
2231
- if (!isPlainObject4(entry) || typeof entry.dir !== "string")
2232
- continue;
2233
- const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
2234
- const snapshotPath = join7(harnessDir, relSnapshot);
2235
- const label = typeof entry.id === "string" ? entry.id : relSnapshot;
2236
- let physical;
2237
- try {
2238
- physical = realpathSync(snapshotPath);
2239
- } catch {
2240
- violations.push(violation5("high", "status.workflow.snapshot-missing", `workflows[] lists ${JSON.stringify(label)} but its snapshot does not exist at ${JSON.stringify(relSnapshot)} — the root holds active lifecycles only; unregister the id when its snapshot is removed`));
2241
- continue;
2242
- }
2243
- if (realHarnessDir !== null && physical !== realHarnessDir && !physical.startsWith(`${realHarnessDir}${sep}`)) {
2244
- violations.push(violation5("high", "status.workflow.snapshot-outside-harness", `workflows[] lists ${JSON.stringify(label)} but its snapshot resolves outside the harness dir (${JSON.stringify(physical)}) — symlinked snapshot paths are rejected; the snapshot must physically live under ${JSON.stringify(harnessDir)}`));
2245
- continue;
2246
- }
2247
- let snapshot;
2248
- try {
2249
- snapshot = readJson(snapshotPath);
2250
- } catch (error) {
2251
- violations.push(violation5("high", "status.workflow.snapshot-invalid", `snapshot at ${JSON.stringify(relSnapshot)} is not valid JSON: ${error.message}`));
2252
- continue;
2253
- }
2254
- if (typeof snapshot.status === "string" && WORKFLOW_TERMINAL_STATUSES.includes(snapshot.status)) {
2255
- violations.push(violation5("high", "status.workflow.terminal-listed", `workflows[] lists ${JSON.stringify(label)} whose snapshot status is terminal (${snapshot.status}) — removal-at-terminal: terminal writers unregister AFTER the snapshot write`));
2256
- }
2257
- if (typeof entry.type === "string" && typeof snapshot.type === "string" && entry.type !== snapshot.type) {
2258
- violations.push(violation5("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} — the root entry mirrors the snapshot; align them`));
2259
- }
2260
- if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
2261
- violations.push(violation5("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot — align them or remove the colliding workflow`));
2262
- }
2010
+ if (harnessDir !== undefined && Array.isArray(doc.workflows)) {
2011
+ let realHarnessDir = null;
2012
+ try {
2013
+ realHarnessDir = realpathSync(harnessDir);
2014
+ } catch {}
2015
+ for (const entry of doc.workflows) {
2016
+ if (!isPlainObject3(entry) || typeof entry.dir !== "string")
2017
+ continue;
2018
+ const relSnapshot = join6(entry.dir, WORKFLOW_SNAPSHOT_FILE);
2019
+ const snapshotPath = join6(harnessDir, relSnapshot);
2020
+ const label = typeof entry.id === "string" ? entry.id : relSnapshot;
2021
+ let physical;
2022
+ try {
2023
+ physical = realpathSync(snapshotPath);
2024
+ } catch {
2025
+ violations.push(violation4("high", "status.workflow.snapshot-missing", `workflows[] lists ${JSON.stringify(label)} but its snapshot does not exist at ${JSON.stringify(relSnapshot)} — the root holds active lifecycles only; unregister the id when its snapshot is removed`));
2026
+ continue;
2027
+ }
2028
+ if (realHarnessDir !== null && physical !== realHarnessDir && !physical.startsWith(`${realHarnessDir}${sep}`)) {
2029
+ violations.push(violation4("high", "status.workflow.snapshot-outside-harness", `workflows[] lists ${JSON.stringify(label)} but its snapshot resolves outside the harness dir (${JSON.stringify(physical)}) — symlinked snapshot paths are rejected; the snapshot must physically live under ${JSON.stringify(harnessDir)}`));
2030
+ continue;
2031
+ }
2032
+ let snapshot;
2033
+ try {
2034
+ snapshot = readJson(snapshotPath);
2035
+ } catch (error) {
2036
+ violations.push(violation4("high", "status.workflow.snapshot-invalid", `snapshot at ${JSON.stringify(relSnapshot)} is not valid JSON: ${error.message}`));
2037
+ continue;
2038
+ }
2039
+ if (typeof snapshot.status === "string" && WORKFLOW_TERMINAL_STATUSES.includes(snapshot.status)) {
2040
+ violations.push(violation4("high", "status.workflow.terminal-listed", `workflows[] lists ${JSON.stringify(label)} whose snapshot status is terminal (${snapshot.status}) — removal-at-terminal: terminal writers unregister AFTER the snapshot write`));
2041
+ }
2042
+ if (typeof entry.type === "string" && typeof snapshot.type === "string" && entry.type !== snapshot.type) {
2043
+ violations.push(violation4("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} — the root entry mirrors the snapshot; align them`));
2044
+ }
2045
+ if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
2046
+ violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot — align them or remove the colliding workflow`));
2047
+ }
2048
+ }
2049
+ }
2050
+ return { ok: violations.length === 0, violations };
2051
+ }
2052
+ var validateStatus = validateStatusV2;
2053
+ function resolveCompassEnforcement(harnessDir) {
2054
+ const iterationsDir = resolveIterationDir(harnessDir);
2055
+ if (!existsSync4(iterationsDir))
2056
+ return { hard: false, source: "none" };
2057
+ let entries;
2058
+ try {
2059
+ entries = readdirSync2(iterationsDir, { withFileTypes: true });
2060
+ } catch {
2061
+ return { hard: false, source: "none" };
2062
+ }
2063
+ for (const entry of entries) {
2064
+ if (!entry.isDirectory())
2065
+ continue;
2066
+ const compassPath = join6(iterationsDir, entry.name, "delivery-compass.md");
2067
+ if (!existsSync4(compassPath))
2068
+ continue;
2069
+ let content;
2070
+ try {
2071
+ content = readFileSync4(compassPath, "utf8");
2072
+ } catch {
2073
+ continue;
2074
+ }
2075
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2076
+ const fm = frontmatter !== null ? frontmatter[1] : "";
2077
+ if (!/^status[ \t]*:[ \t]*(?:active|locked)[ \t]*$/m.test(fm))
2078
+ continue;
2079
+ const flag = parseEnforcementFlag(fm);
2080
+ if (flag.hard)
2081
+ return flag;
2082
+ }
2083
+ return { hard: false, source: "none" };
2084
+ }
2085
+ function resolveMstarcEnforcement(harnessDir) {
2086
+ const dir = resolve5(harnessDir);
2087
+ const rc = loadMstarc(dir, dirname4(dir));
2088
+ const value = rc?.config.enforcement;
2089
+ if (value === "hard")
2090
+ return { hard: true, source: "mstarc" };
2091
+ if (value === "soft")
2092
+ return { hard: false, source: "mstarc" };
2093
+ return { hard: false, source: "none" };
2094
+ }
2095
+ function resolveRepoEnforcement(harnessDir) {
2096
+ const rc = resolveMstarcEnforcement(harnessDir);
2097
+ if (rc.source !== "none")
2098
+ return rc;
2099
+ return resolveCompassEnforcement(harnessDir);
2100
+ }
2101
+ var COMPASS_STATUSES = ["active", "locked", "completed"];
2102
+ var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
2103
+ var PLAN_STATUS_DONE = "Done";
2104
+ function typeName(value) {
2105
+ if (value === null)
2106
+ return "null";
2107
+ if (Array.isArray(value))
2108
+ return "array";
2109
+ return typeof value;
2110
+ }
2111
+ function validateCompassShape(doc) {
2112
+ const issues = [];
2113
+ const expectString = (key, opts = {}) => {
2114
+ const value = doc[key];
2115
+ if (typeof value !== "string") {
2116
+ issues.push({ path: [key], message: `expected string, received ${typeName(value)}` });
2117
+ return;
2118
+ }
2119
+ if (opts.min !== undefined && value.length < opts.min) {
2120
+ issues.push({ path: [key], message: `string must contain at least ${opts.min} character(s)` });
2121
+ return;
2122
+ }
2123
+ if (opts.regex !== undefined && !opts.regex.test(value)) {
2124
+ issues.push({ path: [key], message: `string must match ${opts.regex}` });
2125
+ }
2126
+ };
2127
+ expectString("iteration_id", { min: 1 });
2128
+ expectString("start_date", { regex: DATE_RE2 });
2129
+ const status = doc.status;
2130
+ if (typeof status !== "string" || !COMPASS_STATUSES.includes(status)) {
2131
+ issues.push({
2132
+ path: ["status"],
2133
+ message: `expected one of ${COMPASS_STATUSES.map((s) => `'${s}'`).join(" | ")}, received ${typeName(status)}`
2134
+ });
2135
+ }
2136
+ expectString("iteration_base_branch", { min: 1 });
2137
+ expectString("target_branch", { min: 1 });
2138
+ const plans = doc.plans;
2139
+ if (plans !== undefined) {
2140
+ if (!Array.isArray(plans)) {
2141
+ issues.push({ path: ["plans"], message: `expected array, received ${typeName(plans)}` });
2142
+ } else {
2143
+ plans.forEach((entry, index) => {
2144
+ if (typeof entry !== "string") {
2145
+ issues.push({ path: ["plans", index], message: `expected string, received ${typeName(entry)}` });
2146
+ } else if (entry.length < 1) {
2147
+ issues.push({ path: ["plans", index], message: "string must contain at least 1 character(s)" });
2148
+ }
2149
+ });
2150
+ }
2151
+ }
2152
+ const end_date = doc.end_date;
2153
+ if (end_date !== undefined) {
2154
+ if (typeof end_date !== "string") {
2155
+ issues.push({ path: ["end_date"], message: `expected string, received ${typeName(end_date)}` });
2156
+ } else if (!DATE_RE2.test(end_date)) {
2157
+ issues.push({ path: ["end_date"], message: `string must match ${DATE_RE2}` });
2158
+ }
2159
+ }
2160
+ if (issues.length > 0)
2161
+ return { ok: false, issues };
2162
+ return {
2163
+ ok: true,
2164
+ data: {
2165
+ iteration_id: doc.iteration_id,
2166
+ start_date: doc.start_date,
2167
+ status,
2168
+ iteration_base_branch: doc.iteration_base_branch,
2169
+ target_branch: doc.target_branch,
2170
+ ...plans !== undefined ? { plans } : {},
2171
+ ...end_date !== undefined ? { end_date } : {}
2172
+ }
2173
+ };
2174
+ }
2175
+ function violation5(severity, code, message, fix) {
2176
+ return { ok: false, severity, code, message, fix };
2177
+ }
2178
+ function isPlainObject4(value) {
2179
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2180
+ }
2181
+ function validateCompassFrontmatter(doc) {
2182
+ if (!isPlainObject4(doc)) {
2183
+ return {
2184
+ ok: false,
2185
+ violations: [
2186
+ violation5("medium", "COMPASS_INVALID_FIELD", "Compass frontmatter must be a YAML object with iteration_id / start_date / status / iteration_base_branch / target_branch (template: mstar-iteration §1.3)", "Fix the frontmatter of {ITERATION_DIR}/<iteration-id>/delivery-compass.md")
2187
+ ]
2188
+ };
2189
+ }
2190
+ const parsed = validateCompassShape(doc);
2191
+ if (!parsed.ok) {
2192
+ return {
2193
+ ok: false,
2194
+ violations: parsed.issues.map((issue) => {
2195
+ const field = issue.path.join(".") || "(root)";
2196
+ return violation5("medium", "COMPASS_INVALID_FIELD", `Compass frontmatter field '${field}' is invalid: ${issue.message}`, `Fix '${field}' in {ITERATION_DIR}/<iteration-id>/delivery-compass.md frontmatter (template: mstar-iteration §1.3)`);
2197
+ })
2198
+ };
2199
+ }
2200
+ const violations = [];
2201
+ const { status, end_date } = parsed.data;
2202
+ if (status === "completed" && end_date === undefined) {
2203
+ violations.push(violation5("high", "COMPASS_END_DATE_REQUIRED", "Compass frontmatter status is 'completed' but end_date is missing — end_date is required at iteration-close (mstar-iteration §3.4, template Fields guide)", "Add `end_date: YYYY-MM-DD` to the frontmatter"));
2204
+ }
2205
+ if (status !== "completed" && end_date !== undefined) {
2206
+ violations.push(violation5("medium", "COMPASS_END_DATE_NOT_ALLOWED", `Compass frontmatter sets end_date while status is '${status}' — end_date is only written at iteration-close (mstar-iteration §3.4)`, "Remove end_date until iteration-close"));
2207
+ }
2208
+ return { ok: violations.length === 0, violations };
2209
+ }
2210
+ function registeredPlanIds(compassDoc) {
2211
+ if (!Array.isArray(compassDoc.plans))
2212
+ return [];
2213
+ return compassDoc.plans.filter((plan) => typeof plan === "string" && plan.length > 0);
2214
+ }
2215
+ function findPlanRow(snapshotDoc, planId) {
2216
+ if (!Array.isArray(snapshotDoc.plans))
2217
+ return null;
2218
+ for (const row of snapshotDoc.plans) {
2219
+ if (!isPlainObject4(row))
2220
+ continue;
2221
+ const rowId = typeof row.id === "string" ? row.id : typeof row.plan_id === "string" ? row.plan_id : null;
2222
+ if (rowId === planId)
2223
+ return row;
2224
+ }
2225
+ return null;
2226
+ }
2227
+ function entryPlansAllDone(snapshotDoc, registered) {
2228
+ const violations = [];
2229
+ if (registered.length === 0) {
2230
+ violations.push(violation5("medium", "COMPASS_NO_PLANS", "Compass frontmatter registers no plans — the all-plans-Done transition cannot be verified (mstar-iteration §1.3 / Phase transition gates)", "List the iteration's plan ids in the compass frontmatter `plans`"));
2231
+ return violations;
2232
+ }
2233
+ for (const planId of registered) {
2234
+ const row = findPlanRow(snapshotDoc, planId);
2235
+ if (row === null) {
2236
+ violations.push(violation5("high", "PLAN_NOT_IN_STATUS", `Plan '${planId}' is registered in the compass frontmatter but has no row in the workflow snapshot plans[] (mstar-iteration §3.1 entry item 1)`, "Add the plan row to {HARNESS_DIR}/workflows/<id>/snapshot.json"));
2237
+ continue;
2238
+ }
2239
+ if (row.status !== PLAN_STATUS_DONE) {
2240
+ violations.push(violation5("high", "PLAN_NOT_DONE", `Plan '${planId}' status is ${JSON.stringify(row.status)} in the workflow snapshot — all compass-registered plans must be 'Done' before iteration-close (mstar-iteration §3.1 entry item 1)`));
2241
+ }
2242
+ }
2243
+ return violations;
2244
+ }
2245
+ function entryFrontmatterComplete(compassDoc) {
2246
+ return validateCompassFrontmatter(compassDoc).violations;
2247
+ }
2248
+ function exitFrontmatterClosed(compassDoc) {
2249
+ const violations = [];
2250
+ if (compassDoc.status !== "completed") {
2251
+ violations.push(violation5("high", "EXIT_STATUS_NOT_COMPLETED", `Compass frontmatter status must be 'completed' at close exit — current: ${JSON.stringify(compassDoc.status)} (mstar-iteration §3.4 / §3.5 exit item 4)`));
2252
+ }
2253
+ const endDate = compassDoc.end_date;
2254
+ if (typeof endDate !== "string" || !DATE_RE2.test(endDate)) {
2255
+ violations.push(violation5("high", "EXIT_END_DATE_REQUIRED", "Compass frontmatter end_date (YYYY-MM-DD) is required when closing (mstar-iteration §3.4 / §3.5 exit item 4)"));
2256
+ }
2257
+ return violations;
2258
+ }
2259
+ function exitBranchCheck(opts) {
2260
+ const violations = [];
2261
+ const { currentBranch, specIntegrationBranch } = opts;
2262
+ if (currentBranch === undefined || specIntegrationBranch === undefined) {
2263
+ violations.push(violation5("medium", "EXIT_BRANCH_UNVERIFIABLE", "Cannot verify the current branch is spec_integration_branch — missing currentBranch / specIntegrationBranch probe inputs (mstar-iteration §3.5 exit item 5)"));
2264
+ } else if (currentBranch !== specIntegrationBranch) {
2265
+ violations.push(violation5("high", "EXIT_BRANCH_MISMATCH", `Current branch '${currentBranch}' is not the spec_integration_branch '${specIntegrationBranch}' (mstar-iteration §3.5 exit item 5)`));
2266
+ }
2267
+ return violations;
2268
+ }
2269
+ function exitPrBaseCheck(compassDoc, opts) {
2270
+ const violations = [];
2271
+ const target = compassDoc.target_branch;
2272
+ const { prBaseBranch } = opts;
2273
+ if (prBaseBranch === undefined) {
2274
+ violations.push(violation5("medium", "EXIT_PR_BASE_UNVERIFIABLE", "Cannot verify the PR base — missing prBaseBranch probe input (mstar-iteration §3.5 exit item 6)"));
2275
+ } else if (typeof target !== "string" || prBaseBranch !== target) {
2276
+ violations.push(violation5("high", "EXIT_PR_BASE_MISMATCH", `PR base '${prBaseBranch}' must equal the compass target_branch '${String(target)}' — not an undocumented branch (mstar-iteration §3.5 exit item 6)`));
2277
+ }
2278
+ return violations;
2279
+ }
2280
+ function evaluatePhaseGate(snapshotDoc, compassDoc, opts = {}) {
2281
+ const registered = registeredPlanIds(compassDoc);
2282
+ const entryViolations = [
2283
+ ...entryPlansAllDone(snapshotDoc, registered),
2284
+ ...entryFrontmatterComplete(compassDoc)
2285
+ ];
2286
+ const exitViolations = [
2287
+ ...exitFrontmatterClosed(compassDoc),
2288
+ ...exitBranchCheck(opts),
2289
+ ...exitPrBaseCheck(compassDoc, opts)
2290
+ ];
2291
+ const allPlansDone = registered.length > 0 && registered.every((planId) => {
2292
+ const row = findPlanRow(snapshotDoc, planId);
2293
+ return row !== null && row.status === PLAN_STATUS_DONE;
2294
+ });
2295
+ const entry = { ok: entryViolations.length === 0, violations: entryViolations };
2296
+ const exit = { ok: exitViolations.length === 0, violations: exitViolations };
2297
+ let transition;
2298
+ if (!allPlansDone)
2299
+ transition = "phase-2-execute";
2300
+ else if (entry.ok && exit.ok)
2301
+ transition = "phase-4-pr-delivery";
2302
+ else
2303
+ transition = "phase-3-close";
2304
+ const gateBlocking = allPlansDone ? [...entryViolations, ...exitViolations] : [];
2305
+ return {
2306
+ transition,
2307
+ allPlansDone,
2308
+ entry,
2309
+ exit,
2310
+ ok: gateBlocking.length === 0,
2311
+ violations: gateBlocking
2312
+ };
2313
+ }
2314
+ function parseCompassFrontmatter(filePath) {
2315
+ return parseCompassFrontmatterText(readFileSync5(filePath, "utf8"), filePath);
2316
+ }
2317
+ function parseCompassFrontmatterText(content, filePath) {
2318
+ const lines = content.split(/\r?\n/);
2319
+ if (lines[0]?.trim() !== "---") {
2320
+ throw new Error(`no YAML frontmatter fence in ${filePath} (expected first line "---")`);
2321
+ }
2322
+ const end = lines.indexOf("---", 1);
2323
+ if (end === -1) {
2324
+ throw new Error(`unterminated YAML frontmatter in ${filePath} (no closing "---")`);
2325
+ }
2326
+ const doc = {};
2327
+ let listKey = null;
2328
+ for (let i = 1;i < end; i += 1) {
2329
+ const line = lines[i] ?? "";
2330
+ if (!line.trim() || line.trim().startsWith("#"))
2331
+ continue;
2332
+ if (listKey !== null && /^\s*-\s+/.test(line)) {
2333
+ const item = line.replace(/^\s*-\s+/, "").trim().replace(/^["']|["']$/g, "");
2334
+ if (!Array.isArray(doc[listKey]))
2335
+ doc[listKey] = [];
2336
+ doc[listKey].push(item);
2337
+ continue;
2338
+ }
2339
+ listKey = null;
2340
+ const kv = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
2341
+ if (!kv) {
2342
+ throw new Error(`unsupported frontmatter line in ${filePath}: ${JSON.stringify(line)}`);
2263
2343
  }
2344
+ const value = kv[2].trim();
2345
+ doc[kv[1]] = value === "" ? null : /^\[.*\]$/.test(value) ? parseFlowArray(value, filePath) : value.replace(/^["']|["']$/g, "");
2346
+ listKey = value === "" ? kv[1] : null;
2264
2347
  }
2265
- return { ok: violations.length === 0, violations };
2348
+ return doc;
2266
2349
  }
2267
- var validateStatus = validateStatusV2;
2268
- function resolveCompassEnforcement(harnessDir) {
2269
- const iterationsDir = resolveIterationDir(harnessDir);
2270
- if (!existsSync4(iterationsDir))
2271
- return { hard: false, source: "none" };
2272
- let entries;
2273
- try {
2274
- entries = readdirSync3(iterationsDir, { withFileTypes: true });
2275
- } catch {
2276
- return { hard: false, source: "none" };
2350
+ function parseFlowArray(raw, filePath) {
2351
+ const inner = raw.slice(1, -1);
2352
+ if (/[[\]]/.test(inner)) {
2353
+ throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)} — only flat scalar items are supported (e.g. [a, b])`);
2277
2354
  }
2278
- for (const entry of entries) {
2279
- if (!entry.isDirectory())
2280
- continue;
2281
- const compassPath = join7(iterationsDir, entry.name, "delivery-compass.md");
2282
- if (!existsSync4(compassPath))
2283
- continue;
2284
- let content;
2285
- try {
2286
- content = readFileSync4(compassPath, "utf8");
2287
- } catch {
2288
- continue;
2355
+ let quote = null;
2356
+ for (const ch of inner) {
2357
+ if (ch === '"' || ch === "'") {
2358
+ if (quote === null)
2359
+ quote = ch;
2360
+ else if (quote === ch)
2361
+ quote = null;
2362
+ } else if (ch === "," && quote !== null) {
2363
+ throw new Error(`ambiguous flow-style array in ${filePath}: ${JSON.stringify(raw)} — quoted item containing comma cannot be split unambiguously (flat scalar items only)`);
2289
2364
  }
2290
- const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2291
- const fm = frontmatter !== null ? frontmatter[1] : "";
2292
- if (!/^status[ \t]*:[ \t]*(?:active|locked)[ \t]*$/m.test(fm))
2365
+ }
2366
+ if (quote !== null) {
2367
+ throw new Error(`unterminated ${quote} quote in flow-style array in ${filePath}: ${JSON.stringify(raw)}`);
2368
+ }
2369
+ const items = [];
2370
+ for (const part of inner.split(",")) {
2371
+ const item = part.trim().replace(/^["']|["']$/g, "");
2372
+ if (item === "")
2293
2373
  continue;
2294
- const flag = parseEnforcementFlag(fm);
2295
- if (flag.hard)
2296
- return flag;
2374
+ items.push(item);
2297
2375
  }
2298
- return { hard: false, source: "none" };
2299
- }
2300
- function resolveMstarcEnforcement(harnessDir) {
2301
- const dir = resolve5(harnessDir);
2302
- const rc = loadMstarc(dir, dirname4(dir));
2303
- const value = rc?.config.enforcement;
2304
- if (value === "hard")
2305
- return { hard: true, source: "mstarc" };
2306
- if (value === "soft")
2307
- return { hard: false, source: "mstarc" };
2308
- return { hard: false, source: "none" };
2309
- }
2310
- function resolveRepoEnforcement(harnessDir) {
2311
- const rc = resolveMstarcEnforcement(harnessDir);
2312
- if (rc.source !== "none")
2313
- return rc;
2314
- return resolveCompassEnforcement(harnessDir);
2376
+ return items;
2315
2377
  }
2316
2378
  var PROJECT_ROADMAP_FILE = "roadmap.md";
2317
2379
  var PROJECT_REGISTER_FILE = "residuals.json";
@@ -2454,7 +2516,7 @@ function defaultWorkspaceRoot(startDir) {
2454
2516
  }
2455
2517
  function isAtOrBelow2(dir, root) {
2456
2518
  const rel = relative2(root, dir);
2457
- return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
2519
+ return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
2458
2520
  }
2459
2521
  function mstarcDirOverride(harnessDir, key) {
2460
2522
  const dir = resolve7(harnessDir);
@@ -2472,7 +2534,7 @@ function canonicalizeNearestExisting(path) {
2472
2534
  let dir = abs;
2473
2535
  const tail = [];
2474
2536
  for (;; ) {
2475
- if (existsSync6(dir)) {
2537
+ if (existsSync7(dir)) {
2476
2538
  try {
2477
2539
  return join9(realpathSync2(dir), ...tail);
2478
2540
  } catch {
@@ -2545,15 +2607,74 @@ function isDirectory(dir) {
2545
2607
  return false;
2546
2608
  }
2547
2609
  }
2610
+ var INPUT_ENTRIES_CAP = 1e4;
2611
+ var MAX_RETAINED_ENTRIES = INPUT_ENTRIES_CAP / 2;
2548
2612
  var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
2549
- function probeTimeoutMs() {
2613
+ function gitProbeTimeoutMs() {
2550
2614
  const raw = process.env.MSTAR_GIT_PROBE_TIMEOUT_MS;
2551
2615
  if (raw === undefined || raw.trim() === "")
2552
2616
  return DEFAULT_PROBE_TIMEOUT_MS;
2553
2617
  const parsed = Number(raw);
2554
2618
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PROBE_TIMEOUT_MS;
2555
2619
  }
2556
- function violation7(severity, code, message, fix) {
2620
+ function parseMainWorktree(out) {
2621
+ const tokens = out.split("\x00");
2622
+ const first = tokens.findIndex((t) => t.startsWith("worktree "));
2623
+ if (first === -1)
2624
+ return null;
2625
+ const rawPath = tokens[first].slice("worktree ".length);
2626
+ if (rawPath.trim() === "")
2627
+ return null;
2628
+ let branch = null;
2629
+ let detached = false;
2630
+ for (let i = first + 1;i < tokens.length; i++) {
2631
+ const token = tokens[i];
2632
+ if (token.startsWith("worktree "))
2633
+ break;
2634
+ if (token === "bare")
2635
+ return null;
2636
+ if (token === "detached")
2637
+ detached = true;
2638
+ else if (token.startsWith("branch ")) {
2639
+ const ref = token.slice("branch ".length).trim();
2640
+ if (ref === "")
2641
+ return null;
2642
+ branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
2643
+ }
2644
+ }
2645
+ if (detached)
2646
+ branch = "";
2647
+ if (branch === null)
2648
+ return null;
2649
+ try {
2650
+ return { root: realpathSync3(rawPath), branch };
2651
+ } catch {
2652
+ return null;
2653
+ }
2654
+ }
2655
+ function readMainWorktree(cwd) {
2656
+ const start = cwd ?? process.cwd();
2657
+ try {
2658
+ const stdout = execFileSync2("git", ["-C", start, "worktree", "list", "--porcelain", "-z"], {
2659
+ encoding: "utf8",
2660
+ stdio: ["ignore", "pipe", "pipe"],
2661
+ timeout: gitProbeTimeoutMs()
2662
+ });
2663
+ return parseMainWorktree(stdout);
2664
+ } catch {
2665
+ return null;
2666
+ }
2667
+ }
2668
+ function assertMainWorktreeResidency(main, expectedBranch) {
2669
+ const violations = [];
2670
+ if (main.branch === "") {
2671
+ violations.push(violation8("high", "worktree.main.residency-switched", `main worktree "${main.root}" is detached (no branch checked out) — residency cannot match the recorded expectation ${JSON.stringify(expectedBranch)}`, "check out the recorded main-worktree branch in the main worktree"));
2672
+ } else if (main.branch !== expectedBranch) {
2673
+ violations.push(violation8("high", "worktree.main.residency-switched", `main worktree "${main.root}" is on branch "${main.branch}", expected the recorded branch "${expectedBranch}" — residency is checked against the value recorded at lifecycle start, never re-pointed at the observed branch`, "restore the recorded branch in the main worktree; re-record at lifecycle start only through the owning workflow"));
2674
+ }
2675
+ return gate(violations);
2676
+ }
2677
+ function violation8(severity, code, message, fix) {
2557
2678
  return { ok: false, severity, code, message, fix };
2558
2679
  }
2559
2680
  function gate(violations) {
@@ -2563,7 +2684,7 @@ function probeBranch(worktreePath, opts) {
2563
2684
  const precomputed = opts.branchOf?.(worktreePath);
2564
2685
  if (precomputed !== undefined)
2565
2686
  return { branch: precomputed };
2566
- const timeout = opts.timeoutMs ?? probeTimeoutMs();
2687
+ const timeout = opts.timeoutMs ?? gitProbeTimeoutMs();
2567
2688
  try {
2568
2689
  const stdout = execFileSync2(opts.gitPath ?? "git", ["-C", worktreePath, "branch", "--show-current"], {
2569
2690
  encoding: "utf8",
@@ -2584,7 +2705,7 @@ function probeBranch(worktreePath, opts) {
2584
2705
  }
2585
2706
  }
2586
2707
  function probeCheckout(worktreePath, opts) {
2587
- const timeout = opts.timeoutMs ?? probeTimeoutMs();
2708
+ const timeout = opts.timeoutMs ?? gitProbeTimeoutMs();
2588
2709
  try {
2589
2710
  const stdout = execFileSync2(opts.gitPath ?? "git", ["-C", worktreePath, "rev-parse", "--git-dir"], {
2590
2711
  encoding: "utf8",
@@ -2594,7 +2715,7 @@ function probeCheckout(worktreePath, opts) {
2594
2715
  const raw = stdout.trim();
2595
2716
  if (raw === "")
2596
2717
  return { error: `no git dir reported at "${worktreePath}"` };
2597
- const abs = isAbsolute5(raw) ? raw : join10(worktreePath, raw);
2718
+ const abs = isAbsolute6(raw) ? raw : join10(worktreePath, raw);
2598
2719
  return { gitDir: realpathSync3(abs) };
2599
2720
  } catch (err) {
2600
2721
  const e = err;
@@ -2605,41 +2726,118 @@ function probeCheckout(worktreePath, opts) {
2605
2726
  return { error: detail };
2606
2727
  }
2607
2728
  }
2729
+ function probeCheckoutRoot(path, opts = {}) {
2730
+ const timeout = opts.timeoutMs ?? gitProbeTimeoutMs();
2731
+ try {
2732
+ const stdout = execFileSync2(opts.gitPath ?? "git", ["-C", path, "rev-parse", "--show-toplevel"], {
2733
+ encoding: "utf8",
2734
+ stdio: ["ignore", "pipe", "pipe"],
2735
+ timeout
2736
+ });
2737
+ const raw = stdout.trim();
2738
+ if (raw === "")
2739
+ return null;
2740
+ return realpathSync3(raw);
2741
+ } catch {
2742
+ return null;
2743
+ }
2744
+ }
2608
2745
  function l1PreDispatchCheck(input, opts = {}) {
2609
2746
  const violations = [];
2610
- const { controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId } = input;
2611
- if (controlWorktreePath.trim() === "") {
2612
- violations.push(violation7("high", "worktree.l1.control-missing", "metadata.control_worktree_path is not recorded — the L1 control worktree (integration-branch checkout) must be recorded in status.json before writable dispatch", "record the control worktree path in status.json metadata.control_worktree_path"));
2747
+ const checkoutCache = new Map;
2748
+ const checkout = (path) => {
2749
+ const key = resolve8(path);
2750
+ if (!checkoutCache.has(key))
2751
+ checkoutCache.set(key, probeCheckout(path, opts));
2752
+ return checkoutCache.get(key);
2753
+ };
2754
+ const {
2755
+ workflowType,
2756
+ integrationWorktreePath,
2757
+ integrationBranch,
2758
+ mainWorktree,
2759
+ expectedMainBranch,
2760
+ lifecycleBranches,
2761
+ leaseWorktreePath,
2762
+ leaseWorkingBranch,
2763
+ planId
2764
+ } = input;
2765
+ if (mainWorktree === null) {
2766
+ violations.push(violation8("high", "worktree.main.unresolved", `main worktree identity cannot be proved for plan "${planId}" (git worktree discovery failed or unavailable) — L1 refuses instead of skipping the residency row`, "run from inside the repository (or pass a resolvable cwd) so the main worktree can be discovered"));
2767
+ } else {
2768
+ if (expectedMainBranch.trim() === "") {
2769
+ violations.push(violation8("high", "worktree.main.expected-branch-missing", `neither a recorded main-worktree branch nor an explicit branch.base is available for plan "${planId}" — the expectation is never the branch observed at check time`, "record the main worktree branch at lifecycle start (plan header / --main-branch) or carry the explicit branch.base"));
2770
+ } else {
2771
+ violations.push(...assertMainWorktreeResidency(mainWorktree, expectedMainBranch).violations);
2772
+ }
2773
+ if (mainWorktree.branch !== "" && lifecycleBranches.includes(mainWorktree.branch)) {
2774
+ violations.push(violation8("high", "worktree.main.residency-switched", `main worktree "${mainWorktree.root}" is on branch "${mainWorktree.branch}", which is owned by an active lifecycle — main must not carry any lifecycle-owned branch, even when the recorded expectation matches it`, "restore the recorded main-worktree branch; never switch main to satisfy a check"));
2775
+ }
2776
+ }
2777
+ const integrationRequired = workflowType === "iteration" || integrationWorktreePath.trim() !== "" || integrationBranch.trim() !== "";
2778
+ if (integrationRequired && (integrationWorktreePath.trim() === "" || integrationBranch.trim() === "")) {
2779
+ violations.push(violation8("high", "worktree.l1.integration-missing", `integration worktree path and branch are both required (workflow type "${workflowType}") for plan "${planId}" — got path ${JSON.stringify(integrationWorktreePath)}, branch ${JSON.stringify(integrationBranch)}`, "record snapshot integration_worktree_path + branch.integration (the dedicated integration checkout), or pass both empty for a standalone plan"));
2613
2780
  }
2614
2781
  if (leaseWorktreePath.trim() === "") {
2615
- violations.push(violation7("high", "worktree.l1.lease-missing", `execution_lease.worktree_path is empty for plan "${planId}" — no verified execution_lease to dispatch against`, "claim the execution_lease with an absolute feature worktree path before dispatch"));
2782
+ violations.push(violation8("high", "worktree.l1.lease-missing", `execution_lease.worktree_path is empty for plan "${planId}" — no verified execution_lease to dispatch against`, "claim the execution_lease with an absolute feature worktree path before dispatch"));
2616
2783
  }
2617
2784
  if (leaseWorkingBranch.trim() === "") {
2618
- 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"));
2619
- }
2620
- if (controlWorktreePath !== "" && leaseWorktreePath !== "") {
2621
- if (resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
2622
- 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"));
2623
- } else if (existsSync7(leaseWorktreePath)) {
2624
- const controlProbe = probeCheckout(controlWorktreePath, opts);
2625
- const leaseProbe = probeCheckout(leaseWorktreePath, opts);
2626
- if ("error" in controlProbe) {
2627
- violations.push(violation7("high", "worktree.l1.checkout-probe-failed", `cannot establish the lease worktree "${leaseWorktreePath}" is a distinct Git checkout from the control worktree "${controlWorktreePath}" for plan "${planId}": ${controlProbe.error}`, "verify the control worktree path is a git checkout (integration-branch checkout)"));
2628
- } else if ("error" in leaseProbe) {
2629
- violations.push(violation7("high", "worktree.l1.checkout-probe-failed", `cannot establish the lease worktree "${leaseWorktreePath}" is a distinct Git checkout from the control worktree "${controlWorktreePath}" for plan "${planId}": ${leaseProbe.error}`, "verify the lease worktree path is a git checkout (git worktree add <path> <branch>)"));
2630
- } else if (controlProbe.gitDir === leaseProbe.gitDir) {
2631
- violations.push(violation7("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" is the same Git checkout as metadata.control_worktree_path "${controlWorktreePath}" — a plain subdirectory or symlink alias of the control checkout is not isolation; the feature worktree MUST be a distinct checkout`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
2785
+ violations.push(violation8("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
2786
+ }
2787
+ if (integrationRequired && integrationWorktreePath.trim() !== "" && integrationBranch.trim() !== "") {
2788
+ if (!existsSync8(integrationWorktreePath)) {
2789
+ violations.push(violation8("high", "worktree.l1.integration-missing", `integration worktree "${integrationWorktreePath}" does not exist for plan "${planId}" — the integration checkout must exist before dispatch`, `create it before dispatch: git worktree add ${shellQuote(integrationWorktreePath)} ${shellQuote(integrationBranch)}`));
2790
+ } else {
2791
+ const probe = probeBranch(integrationWorktreePath, opts);
2792
+ if ("error" in probe) {
2793
+ violations.push(violation8("high", "worktree.branch-probe-failed", `cannot probe branch at the integration worktree "${integrationWorktreePath}" for plan "${planId}": ${probe.error}`, "verify the path is a git worktree checkout on the integration branch (not detached)"));
2794
+ } else if (probe.branch !== integrationBranch) {
2795
+ violations.push(violation8("high", "worktree.branch-mismatch", `integration worktree "${integrationWorktreePath}" is on branch "${probe.branch}", expected branch.integration "${integrationBranch}" (plan "${planId}")`, `checkout ${shellQuote(integrationBranch)} in the integration worktree`));
2632
2796
  }
2633
2797
  }
2634
2798
  }
2635
- if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
2636
- 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>`));
2637
- } else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
2799
+ const identityViolation = (aPath, bPath, code, describe, fix) => {
2800
+ if (resolve8(aPath) === resolve8(bPath)) {
2801
+ return violation8("critical", code, `${describe} — the same checkout (normalized path equality) is not isolation`, fix);
2802
+ }
2803
+ if (!existsSync8(aPath) || !existsSync8(bPath))
2804
+ return null;
2805
+ const a = checkout(aPath);
2806
+ const b = checkout(bPath);
2807
+ if ("error" in a) {
2808
+ return violation8("high", "worktree.l1.checkout-probe-failed", `cannot establish the Git-checkout identity of "${aPath}" for plan "${planId}": ${a.error}`, "verify the path is a git checkout");
2809
+ }
2810
+ if ("error" in b) {
2811
+ return violation8("high", "worktree.l1.checkout-probe-failed", `cannot establish the Git-checkout identity of "${bPath}" for plan "${planId}": ${b.error}`, "verify the path is a git checkout");
2812
+ }
2813
+ if (a.gitDir === b.gitDir) {
2814
+ return violation8("critical", code, `${describe} — a plain subdirectory or symlink alias of the other checkout is not isolation (canonical per-worktree git dirs match)`, fix);
2815
+ }
2816
+ return null;
2817
+ };
2818
+ if (mainWorktree !== null && leaseWorktreePath.trim() !== "") {
2819
+ const v = identityViolation(mainWorktree.root, leaseWorktreePath, "worktree.l1.lease-equals-main", `execution_lease.worktree_path "${leaseWorktreePath}" is the same Git checkout as the main worktree "${mainWorktree.root}" (plan "${planId}")`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease");
2820
+ if (v !== null)
2821
+ violations.push(v);
2822
+ }
2823
+ if (mainWorktree !== null && integrationRequired && integrationWorktreePath.trim() !== "") {
2824
+ const v = identityViolation(mainWorktree.root, integrationWorktreePath, "worktree.l1.integration-equals-main", `integration worktree "${integrationWorktreePath}" is the same Git checkout as the main worktree "${mainWorktree.root}" (plan "${planId}") — the integration checkout is the sole merge cwd and must be dedicated`, "use a dedicated integration checkout on branch.integration (git worktree add <path> <integration-branch>) and record it as integration_worktree_path");
2825
+ if (v !== null)
2826
+ violations.push(v);
2827
+ }
2828
+ if (integrationRequired && integrationWorktreePath.trim() !== "" && leaseWorktreePath.trim() !== "") {
2829
+ const v = identityViolation(integrationWorktreePath, leaseWorktreePath, "worktree.l1.lease-equals-integration", `execution_lease.worktree_path "${leaseWorktreePath}" is the same Git checkout as the integration worktree "${integrationWorktreePath}" (plan "${planId}") — the feature worktree must be distinct from both main and integration`, "use a distinct feature worktree for the plan (git worktree add <path> <working-branch>) and update the lease");
2830
+ if (v !== null)
2831
+ violations.push(v);
2832
+ }
2833
+ if (leaseWorktreePath.trim() !== "" && !existsSync8(leaseWorktreePath)) {
2834
+ violations.push(violation8("high", "worktree.l1.feature-missing", `feature worktree directory "${leaseWorktreePath}" does not exist for plan "${planId}"`, `create it before dispatch: git worktree add ${shellQuote(leaseWorktreePath)} <working-branch>`));
2835
+ } else if (leaseWorktreePath.trim() !== "" && leaseWorkingBranch.trim() !== "") {
2638
2836
  const probe = probeBranch(leaseWorktreePath, opts);
2639
2837
  if ("error" in probe) {
2640
- violations.push(violation7("high", "worktree.l1.branch-probe-failed", `cannot probe branch at "${leaseWorktreePath}" for plan "${planId}": ${probe.error}`, "verify the path is a git worktree checkout on the lease working branch (not detached)"));
2838
+ violations.push(violation8("high", "worktree.l1.branch-probe-failed", `cannot probe branch at "${leaseWorktreePath}" for plan "${planId}": ${probe.error}`, "verify the path is a git worktree checkout on the lease working branch (not detached)"));
2641
2839
  } else if (probe.branch !== leaseWorkingBranch) {
2642
- violations.push(violation7("high", "worktree.l1.branch-mismatch", `feature worktree "${leaseWorktreePath}" is on branch "${probe.branch}", expected execution_lease.working_branch "${leaseWorkingBranch}" (plan "${planId}")`, `checkout ${leaseWorkingBranch} in the feature worktree`));
2840
+ violations.push(violation8("high", "worktree.l1.branch-mismatch", `feature worktree "${leaseWorktreePath}" is on branch "${probe.branch}", expected execution_lease.working_branch "${leaseWorkingBranch}" (plan "${planId}")`, `checkout ${shellQuote(leaseWorkingBranch)} in the feature worktree`));
2643
2841
  }
2644
2842
  }
2645
2843
  return gate(violations);
@@ -2649,32 +2847,32 @@ function l2PreDispatchCheck(input, opts = {}) {
2649
2847
  const tracks = input.tracks ?? [];
2650
2848
  const seenPaths = new Set;
2651
2849
  if (tracks.length < 1) {
2652
- violations.push(violation7("high", "worktree.l2.no-tracks", "no parallel writable tracks — the L2 pre-dispatch checklist requires at least one track with an absolute worktreePath and Working branch", "pass each track's absolute Worktree path and PM-approved Working branch"));
2850
+ violations.push(violation8("high", "worktree.l2.no-tracks", "no parallel writable tracks — the L2 pre-dispatch checklist requires at least one track with an absolute worktreePath and Working branch", "pass each track's absolute Worktree path and PM-approved Working branch"));
2653
2851
  }
2654
2852
  tracks.forEach((track, index) => {
2655
2853
  if (track.worktreePath.trim() === "" || track.workingBranch.trim() === "") {
2656
- violations.push(violation7("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
2854
+ violations.push(violation8("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
2657
2855
  return;
2658
2856
  }
2659
- if (!isAbsolute5(track.worktreePath)) {
2660
- 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>)`));
2857
+ if (!isAbsolute6(track.worktreePath)) {
2858
+ violations.push(violation8("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>)`));
2661
2859
  return;
2662
2860
  }
2663
2861
  const normalized = resolve8(track.worktreePath);
2664
2862
  if (seenPaths.has(normalized)) {
2665
- 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"));
2863
+ violations.push(violation8("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"));
2666
2864
  return;
2667
2865
  }
2668
2866
  seenPaths.add(normalized);
2669
- if (!existsSync7(track.worktreePath)) {
2670
- 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}`));
2867
+ if (!existsSync8(track.worktreePath)) {
2868
+ violations.push(violation8("high", "worktree.l2.track-missing", `track worktree directory "${track.worktreePath}" does not exist`, `create it before dispatch: git worktree add ${track.worktreePath} ${track.workingBranch}`));
2671
2869
  return;
2672
2870
  }
2673
2871
  const probe = probeBranch(track.worktreePath, opts);
2674
2872
  if ("error" in probe) {
2675
- violations.push(violation7("high", "worktree.l2.branch-probe-failed", `cannot probe branch at "${track.worktreePath}": ${probe.error}`, "verify the path is a git worktree checkout on its Working branch (not detached)"));
2873
+ violations.push(violation8("high", "worktree.l2.branch-probe-failed", `cannot probe branch at "${track.worktreePath}": ${probe.error}`, "verify the path is a git worktree checkout on its Working branch (not detached)"));
2676
2874
  } else if (probe.branch !== track.workingBranch) {
2677
- violations.push(violation7("high", "worktree.l2.branch-mismatch", `track worktree "${track.worktreePath}" is on branch "${probe.branch}", expected Working branch "${track.workingBranch}"`, `checkout ${track.workingBranch} in that worktree`));
2875
+ violations.push(violation8("high", "worktree.l2.branch-mismatch", `track worktree "${track.worktreePath}" is on branch "${probe.branch}", expected Working branch "${track.workingBranch}"`, `checkout ${track.workingBranch} in that worktree`));
2678
2876
  }
2679
2877
  });
2680
2878
  return gate(violations);
@@ -2683,12 +2881,108 @@ function assertBranchAlignment(worktreePath, expectedBranch, opts = {}) {
2683
2881
  const violations = [];
2684
2882
  const probe = probeBranch(worktreePath, opts);
2685
2883
  if ("error" in probe) {
2686
- violations.push(violation7("high", "worktree.branch-probe-failed", `cannot probe branch at "${worktreePath}": ${probe.error}`, "verify the path is a git worktree checkout on the expected branch (not detached)"));
2884
+ violations.push(violation8("high", "worktree.branch-probe-failed", `cannot probe branch at "${worktreePath}": ${probe.error}`, "verify the path is a git worktree checkout on the expected branch (not detached)"));
2687
2885
  } else if (probe.branch !== expectedBranch) {
2688
- violations.push(violation7("high", "worktree.branch-mismatch", `worktree "${worktreePath}" is on branch "${probe.branch}", expected "${expectedBranch}" (Assignment Working branch)`, `checkout ${expectedBranch} in that worktree`));
2886
+ violations.push(violation8("high", "worktree.branch-mismatch", `worktree "${worktreePath}" is on branch "${probe.branch}", expected "${expectedBranch}" (Assignment Working branch)`, `checkout ${expectedBranch} in that worktree`));
2689
2887
  }
2690
2888
  return gate(violations);
2691
2889
  }
2890
+ function shellQuote(value) {
2891
+ return "'" + value.replaceAll("'", `'"'"'`) + "'";
2892
+ }
2893
+ function isPlainObject8(value) {
2894
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2895
+ }
2896
+ function collectActiveLifecycleBranches(snapshots) {
2897
+ const owned = new Set;
2898
+ for (const doc of snapshots) {
2899
+ const branch = doc.branch;
2900
+ if (isPlainObject8(branch) && typeof branch.integration === "string" && branch.integration.trim() !== "") {
2901
+ owned.add(branch.integration);
2902
+ }
2903
+ if (!Array.isArray(doc.plans))
2904
+ continue;
2905
+ for (const row of doc.plans) {
2906
+ if (!isPlainObject8(row))
2907
+ continue;
2908
+ const lease = row.execution_lease;
2909
+ if (isPlainObject8(lease) && typeof lease.working_branch === "string" && lease.working_branch.trim() !== "") {
2910
+ owned.add(lease.working_branch);
2911
+ }
2912
+ const meta = row.metadata;
2913
+ if (isPlainObject8(meta) && Array.isArray(meta.track_branches)) {
2914
+ for (const branch2 of meta.track_branches) {
2915
+ if (typeof branch2 === "string" && branch2.trim() !== "")
2916
+ owned.add(branch2);
2917
+ }
2918
+ }
2919
+ if (isPlainObject8(meta) && typeof meta.working_branch === "string" && meta.working_branch.trim() !== "") {
2920
+ owned.add(meta.working_branch);
2921
+ }
2922
+ }
2923
+ }
2924
+ return [...owned];
2925
+ }
2926
+ function scanActiveLifecycleBranches(harnessDir, governingWorkflowId) {
2927
+ const registerPath = join11(harnessDir, "status.json");
2928
+ if (!existsSync9(registerPath))
2929
+ return { kind: "ok", branches: [], notes: [] };
2930
+ let register;
2931
+ try {
2932
+ register = readJson(registerPath);
2933
+ } catch (error) {
2934
+ return { kind: "refusal", code: "worktree.l1.lifecycle-register-unreadable", detail: `${registerPath}: ${error.message}` };
2935
+ }
2936
+ if (!isPlainObject8(register) || register.version !== 2 || !Array.isArray(register.workflows)) {
2937
+ return {
2938
+ kind: "refusal",
2939
+ code: "worktree.l1.lifecycle-register-unreadable",
2940
+ detail: `${registerPath}: not a readable v2 root register (version 2 + workflows[]) — the active lifecycle set cannot be enumerated`
2941
+ };
2942
+ }
2943
+ let workflowsDir;
2944
+ try {
2945
+ workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
2946
+ } catch (error) {
2947
+ return { kind: "refusal", code: "worktree.l1.lifecycle-register-unreadable", detail: `${registerPath}: ${error.message}` };
2948
+ }
2949
+ const owned = new Set;
2950
+ const notes = [];
2951
+ for (const entry of register.workflows) {
2952
+ if (typeof entry !== "object" || entry === null || typeof entry.id !== "string") {
2953
+ return {
2954
+ kind: "refusal",
2955
+ code: "worktree.l1.lifecycle-register-unreadable",
2956
+ detail: `${registerPath}: malformed workflows[] entry — a registered active lifecycle cannot be identified`
2957
+ };
2958
+ }
2959
+ const id = entry.id;
2960
+ try {
2961
+ assertSafePathComponent(id, "workflow id");
2962
+ } catch (error) {
2963
+ return { kind: "refusal", code: "worktree.l1.lifecycle-register-unreadable", detail: `${registerPath}: ${error.message}` };
2964
+ }
2965
+ if (id === governingWorkflowId)
2966
+ continue;
2967
+ const snapshotPath = join11(workflowsDir, id, WORKFLOW_SNAPSHOT_FILE);
2968
+ let snapshot;
2969
+ try {
2970
+ const read = readWorkflowSnapshot(dirname7(snapshotPath));
2971
+ snapshot = read.snapshot;
2972
+ notes.push(...read.diagnostics);
2973
+ } catch (error) {
2974
+ return {
2975
+ kind: "refusal",
2976
+ code: "worktree.l1.lifecycle-snapshot-unreadable",
2977
+ detail: `${snapshotPath}: ${error.message}`
2978
+ };
2979
+ }
2980
+ for (const branch of collectActiveLifecycleBranches([snapshot]))
2981
+ owned.add(branch);
2982
+ }
2983
+ return { kind: "ok", branches: [...owned], notes };
2984
+ }
2985
+
2692
2986
  class SddScriptError extends Error {
2693
2987
  exitCode;
2694
2988
  constructor(message, exitCode) {
@@ -2718,21 +3012,22 @@ function gitOut(cwd, args) {
2718
3012
  cwd,
2719
3013
  encoding: "utf8",
2720
3014
  stdio: ["ignore", "pipe", "pipe"],
2721
- maxBuffer: GIT_CAPTURE_MAX_BYTES
3015
+ maxBuffer: GIT_CAPTURE_MAX_BYTES,
3016
+ timeout: gitProbeTimeoutMs()
2722
3017
  }).trim();
2723
3018
  } catch {
2724
3019
  return null;
2725
3020
  }
2726
3021
  }
2727
3022
  function probeHarnessWithStatus(root) {
2728
- if (isFile2(join11(root, ".mstar", "status.json")))
2729
- return join11(root, ".mstar");
2730
- if (isFile2(join11(root, ".agents", "status.json")))
2731
- return join11(root, ".agents");
2732
- if (hasWorkflowSnapshot(join11(root, ".mstar")))
2733
- return join11(root, ".mstar");
2734
- if (hasWorkflowSnapshot(join11(root, ".agents")))
2735
- return join11(root, ".agents");
3023
+ if (isFile2(join12(root, ".mstar", "status.json")))
3024
+ return join12(root, ".mstar");
3025
+ if (isFile2(join12(root, ".agents", "status.json")))
3026
+ return join12(root, ".agents");
3027
+ if (hasWorkflowSnapshot(join12(root, ".mstar")))
3028
+ return join12(root, ".mstar");
3029
+ if (hasWorkflowSnapshot(join12(root, ".agents")))
3030
+ return join12(root, ".agents");
2736
3031
  return null;
2737
3032
  }
2738
3033
  function hasWorkflowSnapshot(harnessDir) {
@@ -2740,13 +3035,13 @@ function hasWorkflowSnapshot(harnessDir) {
2740
3035
  try {
2741
3036
  workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
2742
3037
  } catch {
2743
- workflowsDir = join11(harnessDir, "workflows");
3038
+ workflowsDir = join12(harnessDir, "workflows");
2744
3039
  }
2745
3040
  if (!isDirectory2(workflowsDir))
2746
3041
  return false;
2747
3042
  try {
2748
3043
  for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
2749
- if (entry.isDirectory() && isFile2(join11(workflowsDir, entry.name, "snapshot.json")))
3044
+ if (entry.isDirectory() && isFile2(join12(workflowsDir, entry.name, "snapshot.json")))
2750
3045
  return true;
2751
3046
  }
2752
3047
  } catch {
@@ -2754,27 +3049,28 @@ function hasWorkflowSnapshot(harnessDir) {
2754
3049
  }
2755
3050
  return false;
2756
3051
  }
2757
- function isLinkedWorktree(root) {
2758
- const gitDirRaw = gitOut(root, ["rev-parse", "--git-dir"]);
2759
- const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
2760
- if (gitDirRaw === null || commonRaw === null)
2761
- return false;
2762
- const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join11(root, gitDirRaw);
2763
- const common = isAbsolute6(commonRaw) ? commonRaw : join11(root, commonRaw);
2764
- if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
2765
- return true;
2766
- try {
2767
- const gdParent = realpathSync4(dirname6(gitDir));
2768
- const cmAbs = realpathSync4(common);
2769
- return join11(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
2770
- } catch {
2771
- return false;
3052
+ function checkoutRootNearestExisting(dir, boundary) {
3053
+ const stop = resolve9(boundary);
3054
+ const startInside = isInside(resolve9(dir), stop);
3055
+ let current = resolve9(dir);
3056
+ for (;; ) {
3057
+ const probed = probeCheckoutRoot(current);
3058
+ if (probed !== null)
3059
+ return probed;
3060
+ if (startInside && current === stop)
3061
+ return null;
3062
+ const parent = dirname8(current);
3063
+ if (parent === current)
3064
+ return null;
3065
+ if (startInside && !isInside(parent, stop))
3066
+ return null;
3067
+ current = parent;
2772
3068
  }
2773
3069
  }
2774
3070
  function sddWorkspace(planId, opts = {}) {
2775
3071
  if (!planId) {
2776
3072
  throw new SddScriptError(`usage: mstar sdd workspace PLAN_ID [CONTROL_ROOT]
2777
- Set MSTAR_CONTROL_ROOT=<control_worktree_path> when running from a feature worktree.`, 2);
3073
+ Set MSTAR_CONTROL_ROOT=<main-repo-root> when running from a feature worktree.`, 2);
2778
3074
  }
2779
3075
  const cwd = opts.cwd ?? process.cwd();
2780
3076
  const controlRoot = opts.controlRoot ?? (process.env.MSTAR_CONTROL_ROOT || undefined);
@@ -2783,16 +3079,29 @@ function sddWorkspace(planId, opts = {}) {
2783
3079
  if (!isDirectory2(controlRoot)) {
2784
3080
  throw new SddScriptError(`mstar sdd workspace: CONTROL_ROOT / MSTAR_CONTROL_ROOT is not a directory: ${controlRoot}`, 1);
2785
3081
  }
2786
- root = realpathSync4(controlRoot);
3082
+ const supplied = realpathSync4(controlRoot);
3083
+ const main = readMainWorktree(supplied);
3084
+ if (main !== null) {
3085
+ if (main.root !== supplied) {
3086
+ throw new SddScriptError(`mstar sdd workspace: CONTROL_ROOT / MSTAR_CONTROL_ROOT "${supplied}" is a linked (integration/foreign) checkout of the main worktree "${main.root}".
3087
+ ` + ` The process control root is the main worktree itself — refusing to redirect the process SSOT.
3088
+ ` + ` Re-run with MSTAR_CONTROL_ROOT=${main.root}`, 1);
3089
+ }
3090
+ root = main.root;
3091
+ } else if (isFile2(join12(supplied, ".git")) || gitOut(supplied, ["rev-parse", "--is-inside-work-tree"]) === "true") {
3092
+ throw new SddScriptError(`mstar sdd workspace: cannot verify the main worktree for CONTROL_ROOT "${supplied}" (git worktree discovery failed).
3093
+ Refusing to resolve or create any SDD tree without a verified main worktree.`, 1);
3094
+ } else {
3095
+ root = supplied;
3096
+ }
2787
3097
  } else {
2788
- const topLevel = gitOut(cwd, ["rev-parse", "--show-toplevel"]);
2789
- root = realpathSync4(topLevel ?? cwd);
2790
- }
2791
- if (!controlRoot && isLinkedWorktree(root)) {
2792
- throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
2793
- Refusing to create a second SDD tree under the feature checkout.
2794
- Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
2795
- ` + ` See mstar-branch-worktree «Harness path SSOT under default gitignore».`, 1);
3098
+ const main = readMainWorktree(cwd);
3099
+ if (main === null) {
3100
+ throw new SddScriptError(`mstar sdd workspace: cannot verify the main worktree from cwd ${cwd} (git worktree discovery failed, git is unavailable, or the directory is not a Git worktree).
3101
+ ` + ` Refusing to resolve or create any SDD tree without a verified main worktree — no second process-SSOT tree is ever created under a linked checkout.
3102
+ ` + ` Re-run with MSTAR_CONTROL_ROOT=<main-repo-root> or: mstar sdd workspace ${planId} <main-repo-root>`, 1);
3103
+ }
3104
+ root = main.root;
2796
3105
  }
2797
3106
  const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
2798
3107
  let harnessDir;
@@ -2800,25 +3109,30 @@ function sddWorkspace(planId, opts = {}) {
2800
3109
  harnessDir = resolve9(root, harnessOverride);
2801
3110
  } else {
2802
3111
  const rc = findMstarc(root, root);
2803
- const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
3112
+ const rcHarnessDir = rc !== null ? parseMstarc(readFileSync8(rc, "utf8")).harnessDir : undefined;
2804
3113
  if (rcHarnessDir) {
2805
- harnessDir = resolve9(rc !== null ? dirname6(rc) : root, rcHarnessDir);
3114
+ harnessDir = resolve9(rc !== null ? dirname8(rc) : root, rcHarnessDir);
2806
3115
  } else {
2807
3116
  const probed = probeHarnessWithStatus(root);
2808
3117
  if (probed) {
2809
3118
  harnessDir = probed;
2810
- } else if (isDirectory2(join11(root, ".mstar"))) {
2811
- harnessDir = join11(root, ".mstar");
2812
- } else if (isDirectory2(join11(root, ".agents"))) {
2813
- harnessDir = join11(root, ".agents");
3119
+ } else if (isDirectory2(join12(root, ".mstar"))) {
3120
+ harnessDir = join12(root, ".mstar");
3121
+ } else if (isDirectory2(join12(root, ".agents"))) {
3122
+ harnessDir = join12(root, ".agents");
2814
3123
  } else {
2815
- harnessDir = join11(root, ".mstar");
3124
+ harnessDir = join12(root, ".mstar");
2816
3125
  }
2817
3126
  }
2818
3127
  }
3128
+ const harnessCheckout = checkoutRootNearestExisting(harnessDir, root);
3129
+ if (harnessCheckout !== null && harnessCheckout !== root) {
3130
+ throw new SddScriptError(`mstar sdd workspace: harness root "${harnessDir}" resolves inside Git checkout "${harnessCheckout}", not the verified main worktree "${root}".
3131
+ ` + ` Refusing to redirect the process SSOT into a linked checkout — resolve MSTAR_HARNESS_DIR / .mstarc harness_dir relative to the main worktree.`, 1);
3132
+ }
2819
3133
  const sddDir = resolveSddDir(harnessDir, planId);
2820
3134
  mkdirSync6(sddDir, { recursive: true });
2821
- writeFileSync4(join11(sddDir, ".gitignore"), `*
3135
+ writeFileSync4(join12(sddDir, ".gitignore"), `*
2822
3136
  `);
2823
3137
  return realpathSync4(sddDir);
2824
3138
  }
@@ -2838,7 +3152,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2838
3152
  }
2839
3153
  let content;
2840
3154
  try {
2841
- content = readFileSync7(planFile, "utf8");
3155
+ content = readFileSync8(planFile, "utf8");
2842
3156
  } catch {
2843
3157
  throw new SddScriptError(`no such plan file: ${planFile}`, 2);
2844
3158
  }
@@ -2847,7 +3161,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2847
3161
  if (outFile) {
2848
3162
  out = bound ? resolve9(observedCwd, outFile) : outFile;
2849
3163
  } else if (bound) {
2850
- out = join11(bound.sddDir, `task-${taskN}-brief.md`);
3164
+ out = join12(bound.sddDir, `task-${taskN}-brief.md`);
2851
3165
  mkdirAfterGate = bound.sddDir;
2852
3166
  } else {
2853
3167
  const sddDir = opts.sddDir ?? process.env.SDD_DIR;
@@ -2855,7 +3169,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2855
3169
  throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
2856
3170
  }
2857
3171
  mkdirSync6(sddDir, { recursive: true });
2858
- out = join11(sddDir, `task-${taskN}-brief.md`);
3172
+ out = join12(sddDir, `task-${taskN}-brief.md`);
2859
3173
  }
2860
3174
  if (bound) {
2861
3175
  const gate2 = checkSddAction(bound, { kind: "artifact", cwd: observedCwd, target: out });
@@ -2892,7 +3206,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2892
3206
  }
2893
3207
  function isInside(child, ancestor) {
2894
3208
  const rel = relative3(ancestor, child);
2895
- return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
3209
+ return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
2896
3210
  }
2897
3211
  function canonicalDir(path) {
2898
3212
  try {
@@ -2981,7 +3295,7 @@ function checkSddAction(context, action) {
2981
3295
  }
2982
3296
  return { ok: violations.length === 0, violations };
2983
3297
  }
2984
- function violation8(severity, code, message, fix) {
3298
+ function violation9(severity, code, message, fix) {
2985
3299
  return { ok: false, severity, code, message, fix };
2986
3300
  }
2987
3301
  var RAW_GROUP = "__raw";
@@ -3094,7 +3408,7 @@ function validateDesignTokenFrontmatter(frontmatterText) {
3094
3408
  const violations = [];
3095
3409
  const fm = parseDesignFrontmatter(frontmatterText);
3096
3410
  if (fm === null) {
3097
- violations.push(violation8("medium", "design-md.tokens.missing-frontmatter", "no `---` YAML frontmatter block found — DESIGN.md must open with a fenced frontmatter holding the token SSOT (design-md-spec §1.5)", "add a `---` fenced frontmatter with version, name, description, and the colors/typography/spacing/rounded groups"));
3411
+ violations.push(violation9("medium", "design-md.tokens.missing-frontmatter", "no `---` YAML frontmatter block found — DESIGN.md must open with a fenced frontmatter holding the token SSOT (design-md-spec §1.5)", "add a `---` fenced frontmatter with version, name, description, and the colors/typography/spacing/rounded groups"));
3098
3412
  return { ok: false, violations };
3099
3413
  }
3100
3414
  const groupEntries = (group) => {
@@ -3109,61 +3423,61 @@ function validateDesignTokenFrontmatter(frontmatterText) {
3109
3423
  };
3110
3424
  for (const group of ["colors", "typography", "spacing", "rounded"]) {
3111
3425
  if (!isMap(fm[group])) {
3112
- violations.push(violation8("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`${group}\` as a nested map`));
3426
+ violations.push(violation9("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`${group}\` as a nested map`));
3113
3427
  } else if (!groupIsMap(group)) {
3114
- violations.push(violation8("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`${group}\` as a nested map`));
3428
+ violations.push(violation9("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`${group}\` as a nested map`));
3115
3429
  } else if (groupEntries(group).length === 0) {
3116
- violations.push(violation8("medium", "design-md.tokens.missing-group", `missing required token group "${group}" — colors/typography/spacing/rounded are required by the frontmatter SSOT (design-md-spec §1.5)`, `add an active \`${group}:\` block with concrete token values`));
3430
+ violations.push(violation9("medium", "design-md.tokens.missing-group", `missing required token group "${group}" — colors/typography/spacing/rounded are required by the frontmatter SSOT (design-md-spec §1.5)`, `add an active \`${group}:\` block with concrete token values`));
3117
3431
  }
3118
3432
  }
3119
3433
  if (!isMap(fm.components) || !groupIsMap("components")) {
3120
- violations.push(violation8("medium", "design-md.tokens.group-not-map", `token group "components" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`components\` as a nested map`));
3434
+ violations.push(violation9("medium", "design-md.tokens.group-not-map", `token group "components" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`components\` as a nested map`));
3121
3435
  }
3122
- const placeholder = (group, name, value) => violations.push(violation8("low", "design-md.tokens.placeholder", `token "${group}.${name}" uses a "[...]" template value — placeholders never count as concrete tokens (completeness-checklist § How to use item 5)`, `replace \`${value}\` with a concrete value`));
3436
+ const placeholder = (group, name, value) => violations.push(violation9("low", "design-md.tokens.placeholder", `token "${group}.${name}" uses a "[...]" template value — placeholders never count as concrete tokens (completeness-checklist § How to use item 5)`, `replace \`${value}\` with a concrete value`));
3123
3437
  for (const [name, value] of groupEntries("colors")) {
3124
3438
  if (typeof value !== "string") {
3125
- violations.push(violation8("medium", "design-md.tokens.color-format", `color "${name}" must be a string value (design-md-spec §2.2)`, "quote the color value"));
3439
+ violations.push(violation9("medium", "design-md.tokens.color-format", `color "${name}" must be a string value (design-md-spec §2.2)`, "quote the color value"));
3126
3440
  continue;
3127
3441
  }
3128
3442
  if (isPlaceholder(value)) {
3129
3443
  placeholder("colors", name, value);
3130
3444
  } else if (!HEX_RE.test(value) && !OKLCH_RE.test(value)) {
3131
- violations.push(violation8("medium", "design-md.tokens.color-format", `color "${name}" = "${value}" is not a hex (\`#rrggbb\`/\`#rrggbbaa\`) or oklch() value (design-md-spec §2.2)`, "use an sRGB hex value, optionally with a `-p3` oklch() twin"));
3445
+ violations.push(violation9("medium", "design-md.tokens.color-format", `color "${name}" = "${value}" is not a hex (\`#rrggbb\`/\`#rrggbbaa\`) or oklch() value (design-md-spec §2.2)`, "use an sRGB hex value, optionally with a `-p3` oklch() twin"));
3132
3446
  }
3133
3447
  }
3134
3448
  for (const [name, value] of groupEntries("typography")) {
3135
3449
  if (!isMap(value)) {
3136
- violations.push(violation8("medium", "design-md.tokens.typography-shape", `typography token "${name}" must be a map of the five properties (design-md-spec §1.5)`, "give it fontFamily/fontSize/fontWeight/lineHeight/letterSpacing"));
3450
+ violations.push(violation9("medium", "design-md.tokens.typography-shape", `typography token "${name}" must be a map of the five properties (design-md-spec §1.5)`, "give it fontFamily/fontSize/fontWeight/lineHeight/letterSpacing"));
3137
3451
  continue;
3138
3452
  }
3139
3453
  const keys = Object.keys(value);
3140
3454
  const missing = TYPOGRAPHY_PROPS.filter((p) => !keys.includes(p));
3141
3455
  const extra = keys.filter((k) => !TYPOGRAPHY_PROPS.includes(k));
3142
3456
  if (missing.length > 0 || extra.length > 0) {
3143
- violations.push(violation8("medium", "design-md.tokens.typography-shape", `typography token "${name}" must have exactly the five properties fontFamily/fontSize/fontWeight/lineHeight/letterSpacing (design-md-spec §1.5)${missing.length > 0 ? ` — missing: ${missing.join(", ")}` : ""}${extra.length > 0 ? ` — extra: ${extra.join(", ")}` : ""}`, "align the token with the five-property shape"));
3457
+ violations.push(violation9("medium", "design-md.tokens.typography-shape", `typography token "${name}" must have exactly the five properties fontFamily/fontSize/fontWeight/lineHeight/letterSpacing (design-md-spec §1.5)${missing.length > 0 ? ` — missing: ${missing.join(", ")}` : ""}${extra.length > 0 ? ` — extra: ${extra.join(", ")}` : ""}`, "align the token with the five-property shape"));
3144
3458
  }
3145
3459
  for (const prop of ["fontFamily", "fontSize"]) {
3146
3460
  const v = value[prop];
3147
3461
  if (typeof v === "string" && isPlaceholder(v))
3148
3462
  placeholder("typography", name, v);
3149
3463
  else if (typeof v !== "string" || v.trim() === "") {
3150
- violations.push(violation8("medium", "design-md.tokens.typography-shape", `typography token "${name}" has an empty \`${prop}\` (design-md-spec §1.5)`, `fill \`${prop}\` with a concrete value`));
3464
+ violations.push(violation9("medium", "design-md.tokens.typography-shape", `typography token "${name}" has an empty \`${prop}\` (design-md-spec §1.5)`, `fill \`${prop}\` with a concrete value`));
3151
3465
  }
3152
3466
  }
3153
3467
  }
3154
3468
  if (groupIsMap("spacing")) {
3155
3469
  const spacing = fm.spacing;
3156
3470
  if (!Object.prototype.hasOwnProperty.call(spacing, "base")) {
3157
- violations.push(violation8("medium", "design-md.tokens.spacing-base", "spacing must declare the base unit as `base` (design-md-spec §2.4)", "add `base: 4px` (or 8px) to the spacing group"));
3471
+ violations.push(violation9("medium", "design-md.tokens.spacing-base", "spacing must declare the base unit as `base` (design-md-spec §2.4)", "add `base: 4px` (or 8px) to the spacing group"));
3158
3472
  }
3159
3473
  for (const [name, value] of Object.entries(spacing)) {
3160
3474
  if (name !== "base" && name !== RAW_GROUP && !/^\d+$/.test(name)) {
3161
- violations.push(violation8("medium", "design-md.tokens.spacing-key", `spacing key "${name}" must be \`base\` or a numeric multiplier (design-md-spec §1.5)`, "use numeric scale-step keys or `base`"));
3475
+ violations.push(violation9("medium", "design-md.tokens.spacing-key", `spacing key "${name}" must be \`base\` or a numeric multiplier (design-md-spec §1.5)`, "use numeric scale-step keys or `base`"));
3162
3476
  }
3163
3477
  if (typeof value === "string" && isPlaceholder(value)) {
3164
3478
  placeholder("spacing", name, value);
3165
3479
  } else if (typeof value !== "string" || !PX_RE.test(value)) {
3166
- violations.push(violation8("medium", "design-md.tokens.spacing-format", `spacing value "${name}" = "${String(value)}" is not a px length (design-md-spec §1.5)`, "use a pixel value like `4px`"));
3480
+ violations.push(violation9("medium", "design-md.tokens.spacing-format", `spacing value "${name}" = "${String(value)}" is not a px length (design-md-spec §1.5)`, "use a pixel value like `4px`"));
3167
3481
  }
3168
3482
  }
3169
3483
  }
@@ -3171,12 +3485,12 @@ function validateDesignTokenFrontmatter(frontmatterText) {
3171
3485
  if (typeof value === "string" && isPlaceholder(value)) {
3172
3486
  placeholder("rounded", name, value);
3173
3487
  } else if (typeof value !== "string" || !PX_RE.test(value)) {
3174
- violations.push(violation8("medium", "design-md.tokens.rounded-format", `rounded value "${name}" = "${String(value)}" is not a px length (design-md-spec §1.5)`, "use a pixel value like `6px`"));
3488
+ violations.push(violation9("medium", "design-md.tokens.rounded-format", `rounded value "${name}" = "${String(value)}" is not a px length (design-md-spec §1.5)`, "use a pixel value like `6px`"));
3175
3489
  }
3176
3490
  }
3177
3491
  for (const [name, value] of groupEntries("components")) {
3178
3492
  if (!isMap(value)) {
3179
- violations.push(violation8("medium", "design-md.tokens.components-shape", `component token "${name}" must be a map of properties (design-md-spec §2.8)`, "give it backgroundColor/textColor/typography/rounded/padding/height"));
3493
+ violations.push(violation9("medium", "design-md.tokens.components-shape", `component token "${name}" must be a map of properties (design-md-spec §2.8)`, "give it backgroundColor/textColor/typography/rounded/padding/height"));
3180
3494
  continue;
3181
3495
  }
3182
3496
  for (const [prop, v] of Object.entries(value)) {
@@ -3192,7 +3506,7 @@ function validateDesignTokenFrontmatter(frontmatterText) {
3192
3506
  const [, refGroup, refKey] = ref;
3193
3507
  const resolves = REF_GROUPS.includes(refGroup) && groupIsMap(refGroup) && Object.prototype.hasOwnProperty.call(fm[refGroup], refKey);
3194
3508
  if (!resolves) {
3195
- violations.push(violation8("medium", "design-md.tokens.ref-unresolved", `component "${name}" references "${v}" which does not resolve to an active token in this frontmatter (design-md-spec §6 — {path} refs MUST trace back to a key)`, `add the referenced token or use a literal value`));
3509
+ violations.push(violation9("medium", "design-md.tokens.ref-unresolved", `component "${name}" references "${v}" which does not resolve to an active token in this frontmatter (design-md-spec §6 — {path} refs MUST trace back to a key)`, `add the referenced token or use a literal value`));
3196
3510
  }
3197
3511
  }
3198
3512
  }
@@ -3204,7 +3518,7 @@ function assertLightDarkParity(lightFm, darkFm) {
3204
3518
  const light = parseDesignFrontmatter(lightFm);
3205
3519
  const dark = parseDesignFrontmatter(darkFm);
3206
3520
  if (light === null || dark === null) {
3207
- violations.push(violation8("medium", "design-md.parity.missing-frontmatter", `light/dark parity needs a YAML frontmatter in both files — ${light === null ? "DESIGN.md" : "DESIGN.dark.md"} has none (design-md-spec §4 rules 1–2)`, "add the fenced frontmatter to both theme files"));
3521
+ violations.push(violation9("medium", "design-md.parity.missing-frontmatter", `light/dark parity needs a YAML frontmatter in both files — ${light === null ? "DESIGN.md" : "DESIGN.dark.md"} has none (design-md-spec §4 rules 1–2)`, "add the fenced frontmatter to both theme files"));
3208
3522
  return { ok: false, violations };
3209
3523
  }
3210
3524
  const activeKeys = (fm) => {
@@ -3222,12 +3536,12 @@ function assertLightDarkParity(lightFm, darkFm) {
3222
3536
  const darkKeys = activeKeys(dark);
3223
3537
  for (const key of lightKeys) {
3224
3538
  if (!darkKeys.has(key)) {
3225
- violations.push(violation8("medium", "design-md.parity.missing-dark", `token "${key}" is active in DESIGN.md but missing from DESIGN.dark.md — both files must define the same token set (design-md-spec §4 rule 3)`, "add the token to DESIGN.dark.md with a dark-appropriate value"));
3539
+ violations.push(violation9("medium", "design-md.parity.missing-dark", `token "${key}" is active in DESIGN.md but missing from DESIGN.dark.md — both files must define the same token set (design-md-spec §4 rule 3)`, "add the token to DESIGN.dark.md with a dark-appropriate value"));
3226
3540
  }
3227
3541
  }
3228
3542
  for (const key of darkKeys) {
3229
3543
  if (!lightKeys.has(key)) {
3230
- violations.push(violation8("medium", "design-md.parity.missing-light", `token "${key}" is active in DESIGN.dark.md but missing from DESIGN.md — DESIGN.md is the SSOT for token names (design-md-spec §4 rules 3–4)`, "add the token to DESIGN.md, or remove it from the dark file"));
3544
+ violations.push(violation9("medium", "design-md.parity.missing-light", `token "${key}" is active in DESIGN.dark.md but missing from DESIGN.md — DESIGN.md is the SSOT for token names (design-md-spec §4 rules 3–4)`, "add the token to DESIGN.md, or remove it from the dark file"));
3231
3545
  }
3232
3546
  }
3233
3547
  return { ok: violations.length === 0, violations };
@@ -3380,7 +3694,7 @@ function completenessLevel(frontmatterText, checklist) {
3380
3694
  const upgradeTo = candidate === undefined ? null : candidate;
3381
3695
  return { level, items, missing, placeholders, upgradeTo, bodyUnverified };
3382
3696
  }
3383
- function violation9(severity, code, message, fix) {
3697
+ function violation10(severity, code, message, fix) {
3384
3698
  return { ok: false, severity, code, message, fix };
3385
3699
  }
3386
3700
  var AUDIT_EFFORTS = ["XS", "S", "M", "L", "XL"];
@@ -3413,14 +3727,14 @@ function validateAuditStatusBlocks(planText) {
3413
3727
  const violations = [];
3414
3728
  const blocks = parseStatusBlocks(planText);
3415
3729
  if (blocks.length === 0) {
3416
- violations.push(violation9("medium", "audit.status.missing-block", "no `## Status` block found — audit plan files carry the Status block fields (mstar-audit SKILL.md § Plan output)", "add a `## Status` block with Priority, Effort, Risk, Depends on, Category, Planned at"));
3730
+ violations.push(violation10("medium", "audit.status.missing-block", "no `## Status` block found — audit plan files carry the Status block fields (mstar-audit SKILL.md § Plan output)", "add a `## Status` block with Priority, Effort, Risk, Depends on, Category, Planned at"));
3417
3731
  return { ok: false, violations };
3418
3732
  }
3419
3733
  blocks.forEach((block, index) => {
3420
3734
  const label = blocks.length > 1 ? ` #${index + 1}` : "";
3421
3735
  for (const field of AUDIT_STATUS_FIELDS) {
3422
3736
  if (!block.fields.has(field)) {
3423
- violations.push(violation9("medium", "audit.status.missing-field", `Status block${label} missing required field "${field}" (mstar-audit SKILL.md § Plan output)`, `add \`- **${field}**: <value>\` to the Status block`));
3737
+ violations.push(violation10("medium", "audit.status.missing-field", `Status block${label} missing required field "${field}" (mstar-audit SKILL.md § Plan output)`, `add \`- **${field}**: <value>\` to the Status block`));
3424
3738
  }
3425
3739
  }
3426
3740
  const check = (field, pattern, code, expected) => {
@@ -3428,7 +3742,7 @@ function validateAuditStatusBlocks(planText) {
3428
3742
  if (value === undefined)
3429
3743
  return;
3430
3744
  if (!pattern.test(value)) {
3431
- violations.push(violation9("medium", code, `Status block${label} "${field}" = "${value}" — expected ${expected} (mstar-audit SKILL.md § Plan output)`, `fix \`- **${field}**:\` to one of: ${expected}`));
3745
+ violations.push(violation10("medium", code, `Status block${label} "${field}" = "${value}" — expected ${expected} (mstar-audit SKILL.md § Plan output)`, `fix \`- **${field}**:\` to one of: ${expected}`));
3432
3746
  }
3433
3747
  };
3434
3748
  check("Priority", /^P[123]$/, "audit.status.invalid-priority", "P1 | P2 | P3");
@@ -3440,7 +3754,7 @@ function validateAuditStatusBlocks(planText) {
3440
3754
  });
3441
3755
  return { ok: violations.length === 0, violations };
3442
3756
  }
3443
- function violation10(severity, code, message, fix) {
3757
+ function violation11(severity, code, message, fix) {
3444
3758
  return { ok: false, severity, code, message, fix };
3445
3759
  }
3446
3760
  var KNOWLEDGE_REQUIRED_FIELDS = ["module", "date", "problem_type", "category", "severity"];
@@ -3605,36 +3919,36 @@ function validateSchemaYaml(frontmatterText) {
3605
3919
  const violations = [];
3606
3920
  const doc = parseYamlLite(frontmatterText);
3607
3921
  if (doc === null) {
3608
- violations.push(violation10("medium", "compound.schema.missing-frontmatter", "no `---` YAML frontmatter block found — knowledge docs must open with the schema.yaml contract (mstar-compound/references/schema.yaml)", "add the fenced frontmatter with module, date, problem_type, category, severity"));
3922
+ violations.push(violation11("medium", "compound.schema.missing-frontmatter", "no `---` YAML frontmatter block found — knowledge docs must open with the schema.yaml contract (mstar-compound/references/schema.yaml)", "add the fenced frontmatter with module, date, problem_type, category, severity"));
3609
3923
  return { ok: false, violations };
3610
3924
  }
3611
3925
  const isStr = (v) => typeof v === "string";
3612
- const missing = (field) => violations.push(violation10("medium", "compound.schema.missing-field", `missing required frontmatter field "${field}" (schema.yaml required_fields)`, `add \`${field}: <value>\` to the frontmatter`));
3926
+ const missing = (field) => violations.push(violation11("medium", "compound.schema.missing-field", `missing required frontmatter field "${field}" (schema.yaml required_fields)`, `add \`${field}: <value>\` to the frontmatter`));
3613
3927
  for (const field of KNOWLEDGE_REQUIRED_FIELDS) {
3614
3928
  if (!(field in doc) || doc[field] === "")
3615
3929
  missing(field);
3616
3930
  }
3617
3931
  if (doc.date !== undefined && (!isStr(doc.date) || !DATE_RE5.test(doc.date))) {
3618
- violations.push(violation10("medium", "compound.schema.invalid-date", `date "${String(doc.date)}" must be a YYYY-MM-DD string (schema.yaml required_fields.date)`, "use `YYYY-MM-DD`"));
3932
+ violations.push(violation11("medium", "compound.schema.invalid-date", `date "${String(doc.date)}" must be a YYYY-MM-DD string (schema.yaml required_fields.date)`, "use `YYYY-MM-DD`"));
3619
3933
  }
3620
3934
  const problemType = doc.problem_type;
3621
3935
  if (problemType !== undefined && !isStr(problemType)) {
3622
- violations.push(violation10("medium", "compound.schema.invalid-problem-type", `problem_type "${String(problemType)}" must be a string — one of the schema.yaml enum values (bug: build_error…config_error; knowledge: developer_experience…testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
3936
+ violations.push(violation11("medium", "compound.schema.invalid-problem-type", `problem_type "${String(problemType)}" must be a string — one of the schema.yaml enum values (bug: build_error…config_error; knowledge: developer_experience…testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
3623
3937
  }
3624
3938
  const problemTypeValid = isStr(problemType) && KNOWLEDGE_PROBLEM_TYPES.includes(problemType);
3625
3939
  if (isStr(problemType) && !problemTypeValid) {
3626
- violations.push(violation10("medium", "compound.schema.invalid-problem-type", `problem_type "${problemType}" is not one of the schema.yaml enum values (bug: build_error…config_error; knowledge: developer_experience…testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
3940
+ violations.push(violation11("medium", "compound.schema.invalid-problem-type", `problem_type "${problemType}" is not one of the schema.yaml enum values (bug: build_error…config_error; knowledge: developer_experience…testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
3627
3941
  }
3628
3942
  if (doc.severity !== undefined && !isStr(doc.severity)) {
3629
- violations.push(violation10("medium", "compound.schema.invalid-severity", `severity "${String(doc.severity)}" must be a string — critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
3943
+ violations.push(violation11("medium", "compound.schema.invalid-severity", `severity "${String(doc.severity)}" must be a string — critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
3630
3944
  }
3631
3945
  if (isStr(doc.severity) && !KNOWLEDGE_SEVERITIES.includes(doc.severity)) {
3632
- violations.push(violation10("medium", "compound.schema.invalid-severity", `severity "${doc.severity}" must be critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
3946
+ violations.push(violation11("medium", "compound.schema.invalid-severity", `severity "${doc.severity}" must be critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
3633
3947
  }
3634
3948
  if (problemTypeValid && isStr(doc.category)) {
3635
3949
  const expected = KNOWLEDGE_CATEGORY_MAP[problemType];
3636
3950
  if (doc.category !== expected) {
3637
- violations.push(violation10("medium", "compound.schema.category-mismatch", `category "${doc.category}" does not match problem_type "${problemType}" — category-mapping.md rule 1 maps it to "${expected}"`, `set \`category: ${expected}\` (the directory name under {KNOWLEDGE_DIR})`));
3951
+ violations.push(violation11("medium", "compound.schema.category-mismatch", `category "${doc.category}" does not match problem_type "${problemType}" — category-mapping.md rule 1 maps it to "${expected}"`, `set \`category: ${expected}\` (the directory name under {KNOWLEDGE_DIR})`));
3638
3952
  }
3639
3953
  }
3640
3954
  if (problemTypeValid) {
@@ -3642,37 +3956,37 @@ function validateSchemaYaml(frontmatterText) {
3642
3956
  if (isBug) {
3643
3957
  for (const field of ["symptoms", "root_cause", "resolution_type"]) {
3644
3958
  if (!(field in doc)) {
3645
- violations.push(violation10("medium", "compound.schema.missing-track-field", `bug-track doc missing required field "${field}" (schema.yaml track_rules.bug)`, `add \`${field}:\` to the frontmatter`));
3959
+ violations.push(violation11("medium", "compound.schema.missing-track-field", `bug-track doc missing required field "${field}" (schema.yaml track_rules.bug)`, `add \`${field}:\` to the frontmatter`));
3646
3960
  }
3647
3961
  }
3648
3962
  if (doc.symptoms !== undefined && !Array.isArray(doc.symptoms)) {
3649
- violations.push(violation10("medium", "compound.schema.invalid-symptoms", "bug-track `symptoms` must be a YAML list (schema.yaml track_rules.bug)", "list the observable symptoms under `symptoms:`"));
3963
+ violations.push(violation11("medium", "compound.schema.invalid-symptoms", "bug-track `symptoms` must be a YAML list (schema.yaml track_rules.bug)", "list the observable symptoms under `symptoms:`"));
3650
3964
  }
3651
3965
  if (doc.root_cause !== undefined && !isStr(doc.root_cause)) {
3652
- violations.push(violation10("medium", "compound.schema.invalid-root-cause", "bug-track `root_cause` must be a string (schema.yaml track_rules.bug)", "write the fundamental technical cause as a string"));
3966
+ violations.push(violation11("medium", "compound.schema.invalid-root-cause", "bug-track `root_cause` must be a string (schema.yaml track_rules.bug)", "write the fundamental technical cause as a string"));
3653
3967
  }
3654
3968
  if (isStr(doc.resolution_type) && !KNOWLEDGE_RESOLUTION_TYPES.includes(doc.resolution_type)) {
3655
- violations.push(violation10("medium", "compound.schema.invalid-resolution-type", `resolution_type "${doc.resolution_type}" is not one of the schema.yaml track_rules.bug enum values`, "use code_fix | migration | config_change | test_fix | dependency_update | environment_setup | workflow_improvement | documentation_update | tooling_addition"));
3969
+ violations.push(violation11("medium", "compound.schema.invalid-resolution-type", `resolution_type "${doc.resolution_type}" is not one of the schema.yaml track_rules.bug enum values`, "use code_fix | migration | config_change | test_fix | dependency_update | environment_setup | workflow_improvement | documentation_update | tooling_addition"));
3656
3970
  }
3657
3971
  } else if (doc.applies_when !== undefined && !Array.isArray(doc.applies_when)) {
3658
- violations.push(violation10("low", "compound.schema.invalid-applies-when", "knowledge-track `applies_when` must be a YAML list when present (schema.yaml track_rules.knowledge)", "list the conditions under `applies_when:`"));
3972
+ violations.push(violation11("low", "compound.schema.invalid-applies-when", "knowledge-track `applies_when` must be a YAML list when present (schema.yaml track_rules.knowledge)", "list the conditions under `applies_when:`"));
3659
3973
  }
3660
3974
  }
3661
3975
  if (doc.plan_id !== undefined && !isStr(doc.plan_id)) {
3662
- violations.push(violation10("low", "compound.schema.invalid-plan-id", "optional `plan_id` must be a string (schema.yaml optional_fields.plan_id)", "reference the status.json plan id as a string"));
3976
+ violations.push(violation11("low", "compound.schema.invalid-plan-id", "optional `plan_id` must be a string (schema.yaml optional_fields.plan_id)", "reference the status.json plan id as a string"));
3663
3977
  }
3664
3978
  if (doc.tags !== undefined) {
3665
3979
  if (!Array.isArray(doc.tags)) {
3666
- violations.push(violation10("low", "compound.schema.invalid-tags", "optional `tags` must be a YAML list (schema.yaml optional_fields.tags)", "list lowercase, hyphen-separated keywords"));
3980
+ violations.push(violation11("low", "compound.schema.invalid-tags", "optional `tags` must be a YAML list (schema.yaml optional_fields.tags)", "list lowercase, hyphen-separated keywords"));
3667
3981
  } else if (doc.tags.length > 8) {
3668
- violations.push(violation10("low", "compound.schema.tags-too-many", `tags has ${doc.tags.length} entries — max 8 (schema.yaml optional_fields.tags.max_items)`, "trim the tag list to at most 8 keywords"));
3982
+ violations.push(violation11("low", "compound.schema.tags-too-many", `tags has ${doc.tags.length} entries — max 8 (schema.yaml optional_fields.tags.max_items)`, "trim the tag list to at most 8 keywords"));
3669
3983
  }
3670
3984
  }
3671
3985
  if (doc.last_updated !== undefined && (!isStr(doc.last_updated) || !DATE_RE5.test(doc.last_updated))) {
3672
- violations.push(violation10("low", "compound.schema.invalid-last-updated", `last_updated "${String(doc.last_updated)}" must be YYYY-MM-DD (schema.yaml optional_fields.last_updated)`, "use `YYYY-MM-DD`"));
3986
+ violations.push(violation11("low", "compound.schema.invalid-last-updated", `last_updated "${String(doc.last_updated)}" must be YYYY-MM-DD (schema.yaml optional_fields.last_updated)`, "use `YYYY-MM-DD`"));
3673
3987
  }
3674
3988
  if (doc.related_components !== undefined && !Array.isArray(doc.related_components)) {
3675
- violations.push(violation10("low", "compound.schema.invalid-related-components", "optional `related_components` must be a YAML list (schema.yaml optional_fields.related_components)", "list the other components involved"));
3989
+ violations.push(violation11("low", "compound.schema.invalid-related-components", "optional `related_components` must be a YAML list (schema.yaml optional_fields.related_components)", "list the other components involved"));
3676
3990
  }
3677
3991
  return { ok: violations.length === 0, violations };
3678
3992
  }
@@ -3694,7 +4008,7 @@ function referenceExists(repoRoot, docText) {
3694
4008
  if (ref === "" || seen.has(ref))
3695
4009
  continue;
3696
4010
  seen.add(ref);
3697
- if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") || isAbsolute8(ref)) {
4011
+ if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") || isAbsolute9(ref)) {
3698
4012
  continue;
3699
4013
  }
3700
4014
  if (ref.includes("/") || REF_EXT_RE.test(ref)) {
@@ -3722,7 +4036,7 @@ function referenceExists(repoRoot, docText) {
3722
4036
  break;
3723
4037
  if (entry.isDirectory()) {
3724
4038
  if (!WALK_SKIP_DIRS.has(entry.name))
3725
- stack.push(join15(dir, entry.name));
4039
+ stack.push(join16(dir, entry.name));
3726
4040
  } else if (!entry.isSymbolicLink()) {
3727
4041
  const base = entry.name.replace(/\.(?:ts|tsx|js|jsx|mjs|cjs)$/, "");
3728
4042
  if (moduleNames.has(base))
@@ -3734,15 +4048,15 @@ function referenceExists(repoRoot, docText) {
3734
4048
  for (const { ref, isSymbol, module } of refs) {
3735
4049
  if (!isSymbol || module === undefined) {
3736
4050
  const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
3737
- if (existsSync10(resolve13(repoRoot, candidate))) {
4051
+ if (existsSync12(resolve13(repoRoot, candidate))) {
3738
4052
  checked++;
3739
4053
  } else {
3740
- 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"));
4054
+ violations.push(violation11("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"));
3741
4055
  }
3742
4056
  } else if (foundModules.has(module)) {
3743
4057
  checked++;
3744
4058
  } else {
3745
- violations.push(violation10("low", "compound.reference.module-missing", `symbol ref \`${ref}\` — heuristic: no ${module}.ts|tsx|js|jsx|mjs|cjs module file found under ${repoRoot} (compound-refresh Phase 2)`, "verify the module file exists, or update the reference"));
4059
+ violations.push(violation11("low", "compound.reference.module-missing", `symbol ref \`${ref}\` — heuristic: no ${module}.ts|tsx|js|jsx|mjs|cjs module file found under ${repoRoot} (compound-refresh Phase 2)`, "verify the module file exists, or update the reference"));
3746
4060
  }
3747
4061
  }
3748
4062
  return { ok: violations.length === 0, violations, checked };
@@ -3761,7 +4075,7 @@ function collectKnowledgeDocs(dir) {
3761
4075
  for (const entry of entries) {
3762
4076
  if (entry.isSymbolicLink())
3763
4077
  continue;
3764
- const full = join15(current, entry.name);
4078
+ const full = join16(current, entry.name);
3765
4079
  if (entry.isDirectory()) {
3766
4080
  stack.push(full);
3767
4081
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
@@ -3781,14 +4095,14 @@ function normalizeIndexRef(cell) {
3781
4095
  }
3782
4096
  function assertIndexRows(knowledgeDir) {
3783
4097
  const violations = [];
3784
- const readmePath = join15(knowledgeDir, "README.md");
3785
- if (!existsSync10(readmePath)) {
3786
- 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"));
4098
+ const readmePath = join16(knowledgeDir, "README.md");
4099
+ if (!existsSync12(readmePath)) {
4100
+ violations.push(violation11("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"));
3787
4101
  return { ok: false, violations };
3788
4102
  }
3789
4103
  const docs = collectKnowledgeDocs(knowledgeDir);
3790
4104
  const rows = new Set;
3791
- for (const line of readFileSync10(readmePath, "utf8").split(/\r?\n/)) {
4105
+ for (const line of readFileSync11(readmePath, "utf8").split(/\r?\n/)) {
3792
4106
  if (!line.trim().startsWith("|"))
3793
4107
  continue;
3794
4108
  const cells = line.split("|").map((c) => c.trim());
@@ -3800,13 +4114,13 @@ function assertIndexRows(knowledgeDir) {
3800
4114
  }
3801
4115
  for (const doc of docs) {
3802
4116
  if (!rows.has(doc)) {
3803
- violations.push(violation10("medium", "compound.index.missing-row", `knowledge doc "${doc}" has no row in knowledge/README.md index (mstar-compound Phase 6 index obligations)`, `add a row \`| [<title>](${doc}) | <source plan> | <description> | <status> |\` to knowledge/README.md`));
4117
+ violations.push(violation11("medium", "compound.index.missing-row", `knowledge doc "${doc}" has no row in knowledge/README.md index (mstar-compound Phase 6 index obligations)`, `add a row \`| [<title>](${doc}) | <source plan> | <description> | <status> |\` to knowledge/README.md`));
3804
4118
  }
3805
4119
  }
3806
4120
  return { ok: violations.length === 0, violations };
3807
4121
  }
3808
4122
  function isFileLikeRoot(root) {
3809
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename7(root));
4123
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename8(root));
3810
4124
  }
3811
4125
  function scopeGuard(path, allowedRoots) {
3812
4126
  const resolved = resolve13(path);
@@ -3822,11 +4136,11 @@ function scopeGuard(path, allowedRoots) {
3822
4136
  return {
3823
4137
  ok: false,
3824
4138
  violations: [
3825
- violation10("medium", "compound.scope.outside", `path "${path}" is outside the compound-refresh scope (allowed: ${allowedRoots.join(", ")}) — compound-refresh operates only on {HARNESS_DIR}/knowledge/**, {HARNESS_DIR}/knowledge/README.md, <repo-root>/CONCEPTS.md, {HARNESS_DIR}/status.json (mstar-compound-refresh SKILL.md § 产物与操作路径)`, "point the operation at one of the allowed paths")
4139
+ violation11("medium", "compound.scope.outside", `path "${path}" is outside the compound-refresh scope (allowed: ${allowedRoots.join(", ")}) — compound-refresh operates only on {HARNESS_DIR}/knowledge/**, {HARNESS_DIR}/knowledge/README.md, <repo-root>/CONCEPTS.md, {HARNESS_DIR}/status.json (mstar-compound-refresh SKILL.md § 产物与操作路径)`, "point the operation at one of the allowed paths")
3826
4140
  ]
3827
4141
  };
3828
4142
  }
3829
- function violation11(severity, code, message, fix) {
4143
+ function violation12(severity, code, message, fix) {
3830
4144
  return { ok: false, severity, code, message, fix };
3831
4145
  }
3832
4146
  var TASK_ARTIFACT_RE = /\btask-\d+(?:-(?:brief|report|fix-report|diff)|\.diff)\b/g;
@@ -3856,18 +4170,18 @@ function lintSkillFrontmatter(frontmatterText) {
3856
4170
  const violations = [];
3857
4171
  const fm = parseFrontmatter(frontmatterText);
3858
4172
  if (fm === null) {
3859
- violations.push(violation11("medium", "lint.frontmatter.missing", "no YAML frontmatter block found — a skill file must open with a `---` fenced frontmatter (mstar-skill-authoring § Frontmatter Contract)", "add a frontmatter block with `name` and `description` at the top of the file"));
4173
+ violations.push(violation12("medium", "lint.frontmatter.missing", "no YAML frontmatter block found — a skill file must open with a `---` fenced frontmatter (mstar-skill-authoring § Frontmatter Contract)", "add a frontmatter block with `name` and `description` at the top of the file"));
3860
4174
  return { ok: false, violations };
3861
4175
  }
3862
4176
  const name = fm.name ?? "";
3863
4177
  if (name === "") {
3864
- violations.push(violation11("medium", "lint.frontmatter.name.missing", "frontmatter `name` is missing — required (mstar-skill-authoring § Frontmatter Contract)", "add `name: <lowercase-hyphen-id>` to the frontmatter"));
4178
+ violations.push(violation12("medium", "lint.frontmatter.name.missing", "frontmatter `name` is missing — required (mstar-skill-authoring § Frontmatter Contract)", "add `name: <lowercase-hyphen-id>` to the frontmatter"));
3865
4179
  } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
3866
- violations.push(violation11("medium", "lint.frontmatter.name.format", `frontmatter \`name\` must be lowercase-hyphen ("${name}") — e.g. example-skill (mstar-skill-authoring § Frontmatter Contract)`, "rename to a stable lowercase-hyphen id, e.g. `name: example-skill`"));
4180
+ violations.push(violation12("medium", "lint.frontmatter.name.format", `frontmatter \`name\` must be lowercase-hyphen ("${name}") — e.g. example-skill (mstar-skill-authoring § Frontmatter Contract)`, "rename to a stable lowercase-hyphen id, e.g. `name: example-skill`"));
3867
4181
  }
3868
4182
  const description = fm.description ?? "";
3869
4183
  if (description === "") {
3870
- violations.push(violation11("medium", "lint.frontmatter.description.missing", "frontmatter `description` is missing — the trigger contract is required (mstar-skill-authoring § Frontmatter Contract)", "add a `description:` that states when the skill loads (symptoms, context, roles, exclusions)"));
4184
+ violations.push(violation12("medium", "lint.frontmatter.description.missing", "frontmatter `description` is missing — the trigger contract is required (mstar-skill-authoring § Frontmatter Contract)", "add a `description:` that states when the skill loads (symptoms, context, roles, exclusions)"));
3871
4185
  } else {
3872
4186
  const stripped = description.replace(/`[^`]*`/g, " ").replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
3873
4187
  let pronoun = null;
@@ -3878,15 +4192,15 @@ function lintSkillFrontmatter(frontmatterText) {
3878
4192
  break;
3879
4193
  }
3880
4194
  if (pronoun !== null) {
3881
- violations.push(violation11("low", "lint.frontmatter.description.person", `description uses first/second-person pronoun "${pronoun[0]}" — keep the trigger contract third person (mstar-skill-authoring § Frontmatter Contract)`, 'rewrite without I/we/you/my/our/your/us, e.g. "Use when the user asks …"'));
4195
+ violations.push(violation12("low", "lint.frontmatter.description.person", `description uses first/second-person pronoun "${pronoun[0]}" — keep the trigger contract third person (mstar-skill-authoring § Frontmatter Contract)`, 'rewrite without I/we/you/my/our/your/us, e.g. "Use when the user asks …"'));
3882
4196
  }
3883
4197
  const start = description.trim().replace(/^[*_#>]+/, "").replace(/^["'`]+/, "").trim();
3884
4198
  if (WORKFLOW_VERB_START_RE.test(start)) {
3885
- violations.push(violation11("low", "lint.frontmatter.description.workflow", 'description reads as a workflow summary ("Explains/Describes/Covers …") — the description is the trigger contract, not a summary of steps (mstar-skill-authoring § Frontmatter Contract)', "describe when to load the skill (symptoms, context, roles, exclusions) instead of summarizing its steps"));
4199
+ violations.push(violation12("low", "lint.frontmatter.description.workflow", 'description reads as a workflow summary ("Explains/Describes/Covers …") — the description is the trigger contract, not a summary of steps (mstar-skill-authoring § Frontmatter Contract)', "describe when to load the skill (symptoms, context, roles, exclusions) instead of summarizing its steps"));
3886
4200
  } else {
3887
4201
  const words = description.trim().split(/\s+/).filter(Boolean).length;
3888
4202
  if (words > DESCRIPTION_MAX_WORDS) {
3889
- violations.push(violation11("low", "lint.frontmatter.description.workflow", `description is ${words} words — paragraph-length summaries bury the trigger contract (threshold ${DESCRIPTION_MAX_WORDS}, above the longest corpus description at 114 words, mstar-design-md; mstar-skill-authoring § Frontmatter Contract)`, "trim the description to a scannable trigger contract and move detail into the body"));
4203
+ violations.push(violation12("low", "lint.frontmatter.description.workflow", `description is ${words} words — paragraph-length summaries bury the trigger contract (threshold ${DESCRIPTION_MAX_WORDS}, above the longest corpus description at 114 words, mstar-design-md; mstar-skill-authoring § Frontmatter Contract)`, "trim the description to a scannable trigger contract and move detail into the body"));
3890
4204
  }
3891
4205
  }
3892
4206
  }
@@ -3914,7 +4228,7 @@ function parseFrontmatter(text) {
3914
4228
  return null;
3915
4229
  return fields;
3916
4230
  }
3917
- function violation12(severity, code, message, fix) {
4231
+ function violation13(severity, code, message, fix) {
3918
4232
  return { ok: false, severity, code, message, fix };
3919
4233
  }
3920
4234
  var ROLE_MAPPING = [
@@ -3969,19 +4283,19 @@ function validateRoleMapping(rolesDir, options = {}) {
3969
4283
  const violations = [];
3970
4284
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
3971
4285
  for (const { agentId, reference } of mapping) {
3972
- if (!existsSync11(join16(rolesDir, reference))) {
3973
- 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 ${join16(rolesDir, reference)} or fix the mapping row`));
4286
+ if (!existsSync13(join17(rolesDir, reference))) {
4287
+ violations.push(violation13("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join17(rolesDir, reference)} or fix the mapping row`));
3974
4288
  }
3975
4289
  }
3976
4290
  for (const { family, memberIds } of families) {
3977
4291
  const absent = memberIds.filter((id) => !referenceById.has(id));
3978
4292
  for (const id of absent) {
3979
- violations.push(violation12("medium", "roles.mapping.family.member.missing", `shared family "${family}" member "${id}" is absent from the role mapping (mstar-roles § Role Reference Mapping)`, `add "${id}" to the mapping`));
4293
+ violations.push(violation13("medium", "roles.mapping.family.member.missing", `shared family "${family}" member "${id}" is absent from the role mapping (mstar-roles § Role Reference Mapping)`, `add "${id}" to the mapping`));
3980
4294
  }
3981
4295
  if (absent.length === 0) {
3982
4296
  const refs = new Set(memberIds.map((id) => referenceById.get(id)));
3983
4297
  if (refs.size !== 1) {
3984
- violations.push(violation12("medium", "roles.mapping.family.shared", `shared family "${family}" (${memberIds.join(", ")}) must resolve to ONE shared reference file — got ${[...refs].join(", ")} (mstar-roles § Maintenance Rules: "Keep shared-family roles on one shared reference file")`, `point every "${family}" member at the same references/<role>-shared.md`));
4298
+ violations.push(violation13("medium", "roles.mapping.family.shared", `shared family "${family}" (${memberIds.join(", ")}) must resolve to ONE shared reference file — got ${[...refs].join(", ")} (mstar-roles § Maintenance Rules: "Keep shared-family roles on one shared reference file")`, `point every "${family}" member at the same references/<role>-shared.md`));
3985
4299
  }
3986
4300
  }
3987
4301
  }
@@ -3990,12 +4304,12 @@ function validateRoleMapping(rolesDir, options = {}) {
3990
4304
  for (const row of rows) {
3991
4305
  const existing = tableByRole.get(row.roleId);
3992
4306
  if (existing !== undefined) {
3993
- violations.push(violation12("medium", "roles.param.role.duplicate", `role "${row.roleId}" appears in both the ${existing} and ${table} parameter rows (mstar-roles § Parameter Table (SSOT))`, "remove the duplicate row"));
4307
+ violations.push(violation13("medium", "roles.param.role.duplicate", `role "${row.roleId}" appears in both the ${existing} and ${table} parameter rows (mstar-roles § Parameter Table (SSOT))`, "remove the duplicate row"));
3994
4308
  } else {
3995
4309
  tableByRole.set(row.roleId, table);
3996
4310
  }
3997
4311
  if (!referenceById.has(row.roleId)) {
3998
- violations.push(violation12("medium", "roles.param.role.missing", `${table} parameter row references unknown role "${row.roleId}" (mstar-roles § Parameter Table (SSOT))`, `add "${row.roleId}" to the role mapping or drop the row`));
4312
+ violations.push(violation13("medium", "roles.param.role.missing", `${table} parameter row references unknown role "${row.roleId}" (mstar-roles § Parameter Table (SSOT))`, `add "${row.roleId}" to the role mapping or drop the row`));
3999
4313
  }
4000
4314
  }
4001
4315
  };
@@ -4003,20 +4317,20 @@ function validateRoleMapping(rolesDir, options = {}) {
4003
4317
  checkParamRoles(qcReviewers, "QC reviewer");
4004
4318
  for (const row of devTrack) {
4005
4319
  if (row.track !== "primary" && row.track !== "parallel_secondary") {
4006
- violations.push(violation12("medium", "roles.param.track", `dev track for "${row.roleId}" is "${String(row.track)}" — must be primary or parallel_secondary (mstar-roles § Parameter Table (SSOT))`, 'set track to "primary" or "parallel_secondary"'));
4320
+ violations.push(violation13("medium", "roles.param.track", `dev track for "${row.roleId}" is "${String(row.track)}" — must be primary or parallel_secondary (mstar-roles § Parameter Table (SSOT))`, 'set track to "primary" or "parallel_secondary"'));
4007
4321
  }
4008
4322
  }
4009
4323
  const indices = qcReviewers.map((r) => r.reviewerIndex).sort((a, b) => a - b);
4010
4324
  const unique = new Set(indices);
4011
4325
  if (indices.length !== 3 || unique.size !== 3 || indices[0] !== 1 || indices[1] !== 2 || indices[2] !== 3) {
4012
- violations.push(violation12("high", "roles.param.qc.index.set", `QC reviewer_index must be exactly {1, 2, 3} across the three qc-specialist* seats — got [${indices.join(", ")}] (mstar-roles § Parameter Table (SSOT))`, "assign reviewer_index 1/2/3 to qc-specialist / qc-specialist-2 / qc-specialist-3"));
4326
+ violations.push(violation13("high", "roles.param.qc.index.set", `QC reviewer_index must be exactly {1, 2, 3} across the three qc-specialist* seats — got [${indices.join(", ")}] (mstar-roles § Parameter Table (SSOT))`, "assign reviewer_index 1/2/3 to qc-specialist / qc-specialist-2 / qc-specialist-3"));
4013
4327
  }
4014
4328
  for (const row of qcReviewers) {
4015
4329
  if (row.focus.trim() === "") {
4016
- violations.push(violation12("medium", "roles.param.qc.focus.missing", `QC seat "${row.roleId}" (reviewer_index ${row.reviewerIndex}) has an empty focus (mstar-roles § Parameter Table (SSOT))`, "add the review focus"));
4330
+ violations.push(violation13("medium", "roles.param.qc.focus.missing", `QC seat "${row.roleId}" (reviewer_index ${row.reviewerIndex}) has an empty focus (mstar-roles § Parameter Table (SSOT))`, "add the review focus"));
4017
4331
  }
4018
4332
  if (row.reportSuffix !== `qc${row.reviewerIndex}`) {
4019
- violations.push(violation12("medium", "roles.param.qc.suffix", `QC seat "${row.roleId}" report_suffix "${row.reportSuffix}" must equal qc${row.reviewerIndex} — tri reports land at {SDD_DIR}/review/qc1.md…qc3.md (mstar-roles § Parameter Table (SSOT))`, `set report_suffix to qc${row.reviewerIndex}`));
4333
+ violations.push(violation13("medium", "roles.param.qc.suffix", `QC seat "${row.roleId}" report_suffix "${row.reportSuffix}" must equal qc${row.reviewerIndex} — tri reports land at {SDD_DIR}/review/qc1.md…qc3.md (mstar-roles § Parameter Table (SSOT))`, `set report_suffix to qc${row.reviewerIndex}`));
4020
4334
  }
4021
4335
  }
4022
4336
  return { ok: violations.length === 0, violations };
@@ -4063,22 +4377,22 @@ function lintLoadOrder(skillTexts) {
4063
4377
  continue;
4064
4378
  const section = extractLoadOrderSection(text);
4065
4379
  if (section === null) {
4066
- violations.push(violation12("medium", "roles.loadorder.section.missing", `skill "${name}" has no Load Order / First action section — every mstar-* topic skill must declare its first read (mstar-harness-core § 加载契约; mstar-roles § Load Order = single load-selection authority)`, `add a "## Load Order" section: topics name mstar-harness-core first; mstar-roles declares the Skill presets decision matrix`));
4380
+ violations.push(violation13("medium", "roles.loadorder.section.missing", `skill "${name}" has no Load Order / First action section — every mstar-* topic skill must declare its first read (mstar-harness-core § 加载契约; mstar-roles § Load Order = single load-selection authority)`, `add a "## Load Order" section: topics name mstar-harness-core first; mstar-roles declares the Skill presets decision matrix`));
4067
4381
  continue;
4068
4382
  }
4069
4383
  if (name === "mstar-roles") {
4070
4384
  const lower = section.toLowerCase();
4071
4385
  const missing = HUB_BOOTSTRAP_ASSERTIONS.filter((a) => !lower.includes(a.marker.toLowerCase()));
4072
4386
  if (missing.length > 0) {
4073
- violations.push(violation12("medium", "roles.loadorder.hub.bootstrap.missing", `skill "${name}" Load Order section is missing the hub bootstrap decision matrix: ${missing.map((m) => `"${m.marker}" (${m.why})`).join("; ")} (mstar-roles § Load Order = single load-selection authority)`, "declare identity-first, the Skill presets none/standard decision, role-owned methods, and the unknown-preset / missing-identity refusal in the Load Order section"));
4387
+ violations.push(violation13("medium", "roles.loadorder.hub.bootstrap.missing", `skill "${name}" Load Order section is missing the hub bootstrap decision matrix: ${missing.map((m) => `"${m.marker}" (${m.why})`).join("; ")} (mstar-roles § Load Order = single load-selection authority)`, "declare identity-first, the Skill presets none/standard decision, role-owned methods, and the unknown-preset / missing-identity refusal in the Load Order section"));
4074
4388
  }
4075
4389
  if (!section.includes("mstar-harness-core")) {
4076
- violations.push(violation12("medium", "roles.loadorder.core.missing", `skill "${name}" Load Order section does not point at mstar-harness-core — the hub bootstrap is exempt from core-first, but core remains the lifecycle/authorization conflict authority and global entry whenever loaded`, "keep the conditional mstar-harness-core pointer (global entry / conflict authority) in the Load Order section"));
4390
+ violations.push(violation13("medium", "roles.loadorder.core.missing", `skill "${name}" Load Order section does not point at mstar-harness-core — the hub bootstrap is exempt from core-first, but core remains the lifecycle/authorization conflict authority and global entry whenever loaded`, "keep the conditional mstar-harness-core pointer (global entry / conflict authority) in the Load Order section"));
4077
4391
  }
4078
4392
  continue;
4079
4393
  }
4080
4394
  if (!section.includes("mstar-harness-core")) {
4081
- violations.push(violation12("medium", "roles.loadorder.core.missing", `skill "${name}" Load Order section does not declare mstar-harness-core as its first dependency (mstar-harness-core § 加载契约: directly-invoked topics keep core as first dependency; the mstar-roles hub bootstrap is the only exception)`, "name mstar-harness-core first in the Load Order section"));
4395
+ violations.push(violation13("medium", "roles.loadorder.core.missing", `skill "${name}" Load Order section does not declare mstar-harness-core as its first dependency (mstar-harness-core § 加载契约: directly-invoked topics keep core as first dependency; the mstar-roles hub bootstrap is the only exception)`, "name mstar-harness-core first in the Load Order section"));
4082
4396
  }
4083
4397
  }
4084
4398
  return { ok: violations.length === 0, violations };
@@ -4104,7 +4418,7 @@ function resolveSkillRoot(host, paths) {
4104
4418
  return `deferred: pi has no plugin API in v1 — skill-root resolution lands with its adapter (roadmap §8.4)`;
4105
4419
  }
4106
4420
  }
4107
- function violation13(severity, code, message, fix) {
4421
+ function violation14(severity, code, message, fix) {
4108
4422
  return { ok: false, severity, code, message, fix };
4109
4423
  }
4110
4424
  var FIVE_QUESTION_SECTIONS = [
@@ -4175,7 +4489,7 @@ function lintFiveQuestion(bodyText, mode = "authoring") {
4175
4489
  const aliases = mode === "runtime" ? RUNTIME_HEADING_ALIASES[section.key] ?? [] : [];
4176
4490
  const covered = headings.some((heading) => heading.includes(label) || aliases.some((alias) => heading.includes(alias)));
4177
4491
  if (!covered) {
4178
- violations.push(violation13("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}" — no "${section.label}" section (mstar-skill-authoring § Body 必须回答的 5 问 / § 默认 Body 结构)`, `add a "## ${section.label}" section covering ${section.question}`));
4492
+ violations.push(violation14("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}" — no "${section.label}" section (mstar-skill-authoring § Body 必须回答的 5 问 / § 默认 Body 结构)`, `add a "## ${section.label}" section covering ${section.question}`));
4179
4493
  }
4180
4494
  }
4181
4495
  return { ok: violations.length === 0, violations };
@@ -4228,7 +4542,7 @@ class DshMstar extends Service {
4228
4542
 
4229
4543
  // src/gates/_shared.ts
4230
4544
  var import_schemastery = __toESM(require_lib2(), 1);
4231
- import { existsSync as existsSync3 } from "node:fs";
4545
+ import { existsSync as existsSync2 } from "node:fs";
4232
4546
  import { resolve as resolve4 } from "node:path";
4233
4547
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4234
4548
  var STATUS_FILE = "status.json";
@@ -4265,8 +4579,8 @@ var Config = import_schemastery.default.object({
4265
4579
  workflowGate: import_schemastery.default.union(["off", "warn", "ask", "hard"]).default("warn"),
4266
4580
  workflowNames: import_schemastery.default.array(import_schemastery.default.string()).default(undefined)
4267
4581
  });
4268
- function formatViolation(violation14) {
4269
- return `[${violation14.severity}] ${violation14.code}: ${violation14.message}${violation14.fix !== undefined ? ` (fix: ${violation14.fix})` : ""}`;
4582
+ function formatViolation(violation7) {
4583
+ return `[${violation7.severity}] ${violation7.code}: ${violation7.message}${violation7.fix !== undefined ? ` (fix: ${violation7.fix})` : ""}`;
4270
4584
  }
4271
4585
  function asRecord(value) {
4272
4586
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
@@ -4289,7 +4603,7 @@ function resolvePackagedSkillsDir(fileUrl) {
4289
4603
  for (const rel of ["../harness-skills", "../../harness-skills"]) {
4290
4604
  try {
4291
4605
  const dir = fileURLToPath2(new URL(rel, fileUrl));
4292
- if (existsSync3(dir))
4606
+ if (existsSync2(dir))
4293
4607
  return dir;
4294
4608
  } catch {}
4295
4609
  }
@@ -4308,7 +4622,7 @@ function resolvePackagedAgentsDir(fileUrl) {
4308
4622
  for (const rel of ["../harness-agents", "../../harness-agents"]) {
4309
4623
  try {
4310
4624
  const dir = fileURLToPath2(new URL(rel, fileUrl));
4311
- if (existsSync3(dir))
4625
+ if (existsSync2(dir))
4312
4626
  return dir;
4313
4627
  } catch {}
4314
4628
  }
@@ -4406,21 +4720,21 @@ function skillLocalConfig(config) {
4406
4720
  }
4407
4721
 
4408
4722
  // src/gates/catalog.ts
4409
- import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync4, realpathSync as realpathSync7 } from "node:fs";
4410
- import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative7, sep as sep3 } from "node:path";
4723
+ import { existsSync as existsSync16, readFileSync as readFileSync12, readdirSync as readdirSync4, realpathSync as realpathSync7 } from "node:fs";
4724
+ import { basename as basename4, isAbsolute as isAbsolute8, join as join18, relative as relative7, sep as sep3 } from "node:path";
4411
4725
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
4412
4726
 
4413
4727
  // src/gates/agent-flow.ts
4414
- import { appendFileSync, closeSync, existsSync as existsSync12, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync5, readSync, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4415
- import { join as join6 } from "node:path";
4728
+ import { appendFileSync, closeSync, existsSync as existsSync14, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync6, readSync, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4729
+ import { join as join13 } from "node:path";
4416
4730
 
4417
4731
  // src/gates/dispatch.ts
4418
- import { existsSync as existsSync8 } from "node:fs";
4419
- import { basename as basename2, join as join5, resolve as resolve6 } from "node:path";
4732
+ import { existsSync as existsSync10 } from "node:fs";
4733
+ import { basename as basename2, join as join7, resolve as resolve6 } from "node:path";
4420
4734
 
4421
4735
  // src/gates/workflow-selection.ts
4422
- import { existsSync as existsSync5, readdirSync as readdirSync2, realpathSync as realpathSync5, statSync as statSync2 } from "node:fs";
4423
- import { dirname as dirname7, isAbsolute as isAbsolute3, join as join3, normalize, relative as relative4, sep as sep2 } from "node:path";
4736
+ import { existsSync as existsSync6, readdirSync, realpathSync as realpathSync5, statSync as statSync2 } from "node:fs";
4737
+ import { dirname as dirname6, isAbsolute as isAbsolute3, join as join4, normalize, relative as relative4, sep as sep2 } from "node:path";
4424
4738
  var TERMINAL_STATUS_CACHE_MAX = 64;
4425
4739
  var terminalStatusCache = new Map;
4426
4740
  function terminalStatusOf(snapshotPath) {
@@ -4501,11 +4815,20 @@ function validLeases(snapshot) {
4501
4815
  return leases;
4502
4816
  }
4503
4817
  function readSnapshot(harnessDir, entry) {
4504
- const snapshotPath = join3(harnessDir, entry.dir, WORKFLOW_SNAPSHOT_FILE);
4505
- if (!existsSync5(snapshotPath))
4818
+ const snapshotPath = join4(harnessDir, entry.dir, WORKFLOW_SNAPSHOT_FILE);
4819
+ if (!existsSync6(snapshotPath))
4506
4820
  return;
4507
4821
  try {
4508
- return asRecord(readJson(snapshotPath));
4822
+ const snapshot = asRecord(readJson(snapshotPath));
4823
+ if (snapshot === undefined)
4824
+ return;
4825
+ if ("control_worktree_path" in snapshot && "integration_worktree_path" in snapshot)
4826
+ return;
4827
+ if ("control_worktree_path" in snapshot) {
4828
+ const { control_worktree_path, ...rest } = snapshot;
4829
+ return { ...rest, integration_worktree_path: control_worktree_path };
4830
+ }
4831
+ return snapshot;
4509
4832
  } catch {
4510
4833
  return;
4511
4834
  }
@@ -4537,8 +4860,8 @@ function automaticBinding(harnessDir, entries, hint) {
4537
4860
  if (holder !== undefined && leases.some((lease) => lease.holder === holder) || cwd !== undefined && leases.some((lease) => within(String(lease.worktree_path), cwd))) {
4538
4861
  leaseMatches.set(entry.id, entry);
4539
4862
  }
4540
- const controlWorktree = snapshot.control_worktree_path;
4541
- if (cwd !== undefined && typeof controlWorktree === "string" && within(controlWorktree, cwd)) {
4863
+ const integrationWorktree = snapshot.integration_worktree_path;
4864
+ if (cwd !== undefined && typeof integrationWorktree === "string" && within(integrationWorktree, cwd)) {
4542
4865
  cwdMatches.set(entry.id, entry);
4543
4866
  }
4544
4867
  }
@@ -4549,8 +4872,8 @@ function automaticBinding(harnessDir, entries, hint) {
4549
4872
  return byCwd === undefined ? undefined : { kind: "active", workflowId: byCwd.id, dir: byCwd.dir };
4550
4873
  }
4551
4874
  function resolveActiveWorkflow(harnessDir, hint) {
4552
- const statusPath = join3(harnessDir, STATUS_FILE);
4553
- if (!existsSync5(statusPath)) {
4875
+ const statusPath = join4(harnessDir, STATUS_FILE);
4876
+ if (!existsSync6(statusPath)) {
4554
4877
  return {
4555
4878
  kind: "error",
4556
4879
  code: "status.missing",
@@ -4597,7 +4920,7 @@ function resolveActiveWorkflow(harnessDir, hint) {
4597
4920
  return {
4598
4921
  kind: "error",
4599
4922
  code: "workflow.selection.invalid-entry",
4600
- message: `workflows[] entry fails the v2 workflow-entry contract: ${gate2.violations.map((violation14) => violation14.message).join("; ")}`
4923
+ message: `workflows[] entry fails the v2 workflow-entry contract: ${gate2.violations.map((violation7) => violation7.message).join("; ")}`
4601
4924
  };
4602
4925
  }
4603
4926
  const entry = raw;
@@ -4640,7 +4963,7 @@ function resolveReadWorkflow(harnessDir, hint) {
4640
4963
  if (active.kind === "error" && active.code !== "workflow.selection.no-active")
4641
4964
  return active;
4642
4965
  const workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
4643
- if (!existsSync5(workflowsDir)) {
4966
+ if (!existsSync6(workflowsDir)) {
4644
4967
  return {
4645
4968
  kind: "error",
4646
4969
  code: "workflow.selection.no-snapshot",
@@ -4649,7 +4972,7 @@ function resolveReadWorkflow(harnessDir, hint) {
4649
4972
  }
4650
4973
  let entries;
4651
4974
  try {
4652
- entries = readdirSync2(workflowsDir, { withFileTypes: true });
4975
+ entries = readdirSync(workflowsDir, { withFileTypes: true });
4653
4976
  } catch {
4654
4977
  return {
4655
4978
  kind: "error",
@@ -4661,8 +4984,8 @@ function resolveReadWorkflow(harnessDir, hint) {
4661
4984
  for (const entry of entries) {
4662
4985
  if (!entry.isDirectory())
4663
4986
  continue;
4664
- const snapshotPath = join3(workflowsDir, entry.name, WORKFLOW_SNAPSHOT_FILE);
4665
- if (!existsSync5(snapshotPath)) {
4987
+ const snapshotPath = join4(workflowsDir, entry.name, WORKFLOW_SNAPSHOT_FILE);
4988
+ if (!existsSync6(snapshotPath)) {
4666
4989
  terminalStatusCache.delete(snapshotPath);
4667
4990
  continue;
4668
4991
  }
@@ -4670,11 +4993,11 @@ function resolveReadWorkflow(harnessDir, hint) {
4670
4993
  if (verdict === undefined || !verdict.terminal)
4671
4994
  continue;
4672
4995
  if (best === undefined || verdict.mtimeMs > best.mtimeMs) {
4673
- best = { workflowId: entry.name, dir: join3(relative4(harnessDir, workflowsDir), entry.name), mtimeMs: verdict.mtimeMs };
4996
+ best = { workflowId: entry.name, dir: join4(relative4(harnessDir, workflowsDir), entry.name), mtimeMs: verdict.mtimeMs };
4674
4997
  }
4675
4998
  }
4676
4999
  for (const cachedPath of terminalStatusCache.keys()) {
4677
- if (!existsSync5(join3(dirname7(cachedPath), WORKFLOW_SNAPSHOT_FILE))) {
5000
+ if (!existsSync6(join4(dirname6(cachedPath), WORKFLOW_SNAPSHOT_FILE))) {
4678
5001
  terminalStatusCache.delete(cachedPath);
4679
5002
  }
4680
5003
  }
@@ -4873,12 +5196,12 @@ function leaseGateViolations(harnessDir, exec, writable, prompt, hint) {
4873
5196
  if (!sdd)
4874
5197
  return [];
4875
5198
  if (selection.kind === "error" && selection.code === "status.unreadable") {
4876
- return [leaseViolation("lease.dispatch.unreadable", `cannot read ${join5(harnessDir, STATUS_FILE)}: the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "restore a valid status.json (the status gate refuses invalid writes)")];
5199
+ return [leaseViolation("lease.dispatch.unreadable", `cannot read ${join7(harnessDir, STATUS_FILE)}: the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "restore a valid status.json (the status gate refuses invalid writes)")];
4877
5200
  }
4878
- return [leaseViolation("lease.dispatch.unverifiable", `${join5(harnessDir, STATUS_FILE)}: ${selection.kind === "error" ? selection.message : "no active workflow"} — the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "create a valid v2 status.json registering an active workflow (first implement dispatch requires a plan row + lease)")];
5201
+ return [leaseViolation("lease.dispatch.unverifiable", `${join7(harnessDir, STATUS_FILE)}: ${selection.kind === "error" ? selection.message : "no active workflow"} — the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "create a valid v2 status.json registering an active workflow (first implement dispatch requires a plan row + lease)")];
4879
5202
  }
4880
- const snapshotPath = join5(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
4881
- if (!existsSync8(snapshotPath)) {
5203
+ const snapshotPath = join7(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
5204
+ if (!existsSync10(snapshotPath)) {
4882
5205
  if (!sdd)
4883
5206
  return [];
4884
5207
  return [leaseViolation("lease.dispatch.unverifiable", `${snapshotPath} is missing — the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "create a valid workflow snapshot registering the plan row (first implement dispatch requires a plan row)")];
@@ -4952,30 +5275,29 @@ function worktreeL2Violations(header) {
4952
5275
  return violations;
4953
5276
  }
4954
5277
  function activeSnapshotRows(harnessDir, hint) {
4955
- if (!existsSync8(join5(harnessDir, STATUS_FILE)))
4956
- return { rows: null, controlWorktreePath: undefined, unreadable: false };
5278
+ if (!existsSync10(join7(harnessDir, STATUS_FILE)))
5279
+ return { rows: null, snapshot: null, workflowId: null, unreadable: false };
4957
5280
  const selection = resolveActiveWorkflow(harnessDir, hint);
4958
5281
  if (selection.kind !== "active") {
4959
5282
  return {
4960
5283
  rows: null,
4961
- controlWorktreePath: undefined,
5284
+ snapshot: null,
5285
+ workflowId: null,
4962
5286
  unreadable: selection.kind === "error" && selection.code !== "workflow.selection.no-active"
4963
5287
  };
4964
5288
  }
4965
- const snapshotPath = join5(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
4966
- if (!existsSync8(snapshotPath))
4967
- return { rows: null, controlWorktreePath: undefined, unreadable: true };
4968
- let doc;
5289
+ const snapshotDir = join7(harnessDir, selection.dir);
5290
+ if (!existsSync10(join7(snapshotDir, WORKFLOW_SNAPSHOT_FILE))) {
5291
+ return { rows: null, snapshot: null, workflowId: selection.workflowId, unreadable: true };
5292
+ }
5293
+ let snapshot;
4969
5294
  try {
4970
- doc = readJson(snapshotPath);
5295
+ snapshot = readWorkflowSnapshot(snapshotDir).snapshot;
4971
5296
  } catch {
4972
- return { rows: null, controlWorktreePath: undefined, unreadable: true };
5297
+ return { rows: null, snapshot: null, workflowId: selection.workflowId, unreadable: true };
4973
5298
  }
4974
- if (!Array.isArray(doc.plans))
4975
- return { rows: null, controlWorktreePath: undefined, unreadable: true };
4976
- const controlWorktreePath = typeof doc.control_worktree_path === "string" ? doc.control_worktree_path : undefined;
4977
- const rows = doc.plans.map((row) => asRecord(row)).filter((row) => row !== undefined);
4978
- return { rows, controlWorktreePath, unreadable: false };
5299
+ const rows = (Array.isArray(snapshot.plans) ? snapshot.plans : []).map((row) => asRecord(row)).filter((row) => row !== undefined);
5300
+ return { rows, snapshot, workflowId: selection.workflowId, unreadable: false };
4979
5301
  }
4980
5302
  function worktreeL1Violations(harnessDir, header, hint) {
4981
5303
  if (harnessDir === null)
@@ -4984,10 +5306,7 @@ function worktreeL1Violations(harnessDir, header, hint) {
4984
5306
  if (planId === undefined || planId === "")
4985
5307
  return [];
4986
5308
  const read = activeSnapshotRows(harnessDir, hint);
4987
- if (read.rows === null)
4988
- return [];
4989
- const controlWorktreePath = read.controlWorktreePath;
4990
- if (controlWorktreePath === undefined || controlWorktreePath.trim() === "")
5309
+ if (read.rows === null || read.snapshot === null)
4991
5310
  return [];
4992
5311
  const row = read.rows.find((r) => r?.id === planId || r?.plan_id === planId);
4993
5312
  const lease = asRecord(row?.execution_lease);
@@ -4996,7 +5315,26 @@ function worktreeL1Violations(harnessDir, header, hint) {
4996
5315
  if (leaseWorktreePath === undefined || leaseWorktreePath.trim() === "" || leaseWorkingBranch === undefined || leaseWorkingBranch.trim() === "") {
4997
5316
  return [];
4998
5317
  }
4999
- return l1PreDispatchCheck({ controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId }).violations;
5318
+ const snapshot = read.snapshot;
5319
+ const lifecycleBranches = new Set(collectActiveLifecycleBranches([snapshot]));
5320
+ const siblingScan = scanActiveLifecycleBranches(harnessDir, read.workflowId);
5321
+ if (siblingScan.kind === "refusal") {
5322
+ return [worktreeViolation(siblingScan.code, siblingScan.detail, "repair or unregister the unreadable lifecycle state before dispatch")];
5323
+ }
5324
+ for (const branch of siblingScan.branches)
5325
+ lifecycleBranches.add(branch);
5326
+ const expectedMainBranch = assignmentHeaderValue(header, "Main worktree branch") ?? (typeof snapshot.branch?.base === "string" ? snapshot.branch.base : "");
5327
+ return l1PreDispatchCheck({
5328
+ workflowType: snapshot.type,
5329
+ integrationWorktreePath: typeof snapshot.integration_worktree_path === "string" ? snapshot.integration_worktree_path : "",
5330
+ integrationBranch: typeof snapshot.branch?.integration === "string" ? snapshot.branch.integration : "",
5331
+ mainWorktree: readMainWorktree(harnessDir),
5332
+ expectedMainBranch,
5333
+ lifecycleBranches: [...lifecycleBranches],
5334
+ leaseWorktreePath,
5335
+ leaseWorkingBranch,
5336
+ planId
5337
+ }).violations;
5000
5338
  }
5001
5339
  function writableFanOutUncovered(harnessDir, hint) {
5002
5340
  if (harnessDir === null)
@@ -5053,7 +5391,7 @@ function gateWorkflow(ctx, harnessDir, config, adapter, exec, hintReadOf) {
5053
5391
  }
5054
5392
  const pb = writableFanOutUncovered(harnessDir, hintReadOf().hint);
5055
5393
  if (pb.unreadable) {
5056
- const statusLabel = harnessDir === null ? STATUS_FILE : join5(harnessDir, STATUS_FILE);
5394
+ const statusLabel = harnessDir === null ? STATUS_FILE : join7(harnessDir, STATUS_FILE);
5057
5395
  ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (fail-open): cannot read ${statusLabel} — P-b lease attribution degraded; call allowed`);
5058
5396
  }
5059
5397
  const verdict = workflowPolicy(config, adapter.workflowAskCache, {
@@ -5258,7 +5596,7 @@ function callPairingKey(exec) {
5258
5596
  function resolveAgentFlowWriteTarget(harnessDir, hint) {
5259
5597
  const selection = resolveActiveWorkflow(harnessDir, hint);
5260
5598
  if (selection.kind === "active")
5261
- return { dir: join6(harnessDir, selection.dir), selection };
5599
+ return { dir: join13(harnessDir, selection.dir), selection };
5262
5600
  const message = selection.kind === "error" ? selection.message : "no active lifecycle (unexpected terminal selection — the active-set resolver has no history view)";
5263
5601
  if (!noActiveWarned) {
5264
5602
  noActiveWarned = true;
@@ -5278,7 +5616,7 @@ function sleepSync(ms) {
5278
5616
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
5279
5617
  }
5280
5618
  function withWorkflowDirLock(workflowDir, fn, opts = {}) {
5281
- const lockDir = join6(workflowDir, WORKFLOW_LEDGER_LOCKDIR);
5619
+ const lockDir = join13(workflowDir, WORKFLOW_LEDGER_LOCKDIR);
5282
5620
  if (heldWorkflowLocks.has(lockDir)) {
5283
5621
  throw new Error(`${lockDir} is already held by this process — withWorkflowDirLock is not reentrant; a nested acquisition on the same workflow dir is a bug`);
5284
5622
  }
@@ -5304,7 +5642,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
5304
5642
  heldWorkflowLocks.add(lockDir);
5305
5643
  try {
5306
5644
  try {
5307
- writeFileSync2(join6(lockDir, WORKFLOW_LOCKER_HOLDER_PID), String(process.pid), "utf8");
5645
+ writeFileSync2(join13(lockDir, WORKFLOW_LOCKER_HOLDER_PID), String(process.pid), "utf8");
5308
5646
  } catch {}
5309
5647
  return fn();
5310
5648
  } finally {
@@ -5313,7 +5651,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
5313
5651
  const current = statSync5(lockDir);
5314
5652
  if (acquired !== null && current.dev === acquired.dev && current.ino === acquired.ino) {
5315
5653
  try {
5316
- unlinkSync2(join6(lockDir, WORKFLOW_LOCKER_HOLDER_PID));
5654
+ unlinkSync2(join13(lockDir, WORKFLOW_LOCKER_HOLDER_PID));
5317
5655
  } catch {}
5318
5656
  rmdirSync(lockDir);
5319
5657
  }
@@ -5321,13 +5659,13 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
5321
5659
  }
5322
5660
  }
5323
5661
  function appendEvent(workflowDir, event) {
5324
- const file = join6(workflowDir, AGENT_FLOW_FILE);
5662
+ const file = join13(workflowDir, AGENT_FLOW_FILE);
5325
5663
  withWorkflowDirLock(workflowDir, () => {
5326
5664
  appendFileSync(file, `${JSON.stringify(event)}
5327
5665
  `);
5328
5666
  if (statSync5(file).size <= AGENT_FLOW_SIZE_GATE_BYTES)
5329
5667
  return;
5330
- const content = readFileSync5(file, "utf8");
5668
+ const content = readFileSync6(file, "utf8");
5331
5669
  const lines = content.replace(/\n$/, "").split(`
5332
5670
  `);
5333
5671
  if (lines.length > AGENT_FLOW_MAX_EVENTS) {
@@ -5731,7 +6069,7 @@ function summaryOf(events) {
5731
6069
  function ledgerContent(file, n) {
5732
6070
  const size = statSync5(file).size;
5733
6071
  if (size <= AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES) {
5734
- return readFileSync5(file, "utf8");
6072
+ return readFileSync6(file, "utf8");
5735
6073
  }
5736
6074
  let windowBytes = AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES;
5737
6075
  const fd = openSync(file, "r");
@@ -5763,8 +6101,8 @@ function ledgerContent(file, n) {
5763
6101
  }
5764
6102
  }
5765
6103
  function readAgentFlow(workflowDir, limit) {
5766
- const file = join6(workflowDir, AGENT_FLOW_FILE);
5767
- if (!existsSync12(file)) {
6104
+ const file = join13(workflowDir, AGENT_FLOW_FILE);
6105
+ if (!existsSync14(file)) {
5768
6106
  return { events: [], summary: [] };
5769
6107
  }
5770
6108
  const n = limit === undefined ? AGENT_FLOW_DEFAULT_LIMIT : Math.max(0, Math.floor(limit));
@@ -6087,8 +6425,8 @@ function registerSubagentCatalogListener(ctx, pairing) {
6087
6425
  }
6088
6426
 
6089
6427
  // src/engine-status-store.ts
6090
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync as renameSync3, rmSync, statSync as statSync7, writeFileSync as writeFileSync5 } from "node:fs";
6091
- import { dirname as dirname9, join as join8 } from "node:path";
6428
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync3, rmSync, statSync as statSync7, writeFileSync as writeFileSync5 } from "node:fs";
6429
+ import { dirname as dirname9, join as join15 } from "node:path";
6092
6430
  var ENGINE_STATUS_SNAPSHOT_VERSION = 1;
6093
6431
  var ENGINE_STATUS_SNAPSHOT_ENTRY_VERSION = 1;
6094
6432
  var ENGINE_STATUS_SNAPSHOT_MAX_PER_SESSION = 50;
@@ -6097,7 +6435,7 @@ var ENGINE_STATUS_SNAPSHOT_MAX_BYTES = 16 * 1024 * 1024;
6097
6435
  var ENGINE_STATUS_SNAPSHOT_LOCK_TIMEOUT_MS = 250;
6098
6436
  var ENGINE_STATUS_SNAPSHOT_RELATIVE_PATH = "snapshots/engine-status.json";
6099
6437
  function engineStatusSnapshotPath(harnessDir) {
6100
- return join8(harnessDir, ENGINE_STATUS_SNAPSHOT_RELATIVE_PATH);
6438
+ return join15(harnessDir, ENGINE_STATUS_SNAPSHOT_RELATIVE_PATH);
6101
6439
  }
6102
6440
  function engineStatusUnavailable(reason) {
6103
6441
  return { kind: "unavailable", reason };
@@ -6155,7 +6493,7 @@ function readEngineStatusSnapshot(harnessDir, sessionId) {
6155
6493
  return engineStatusUnavailable("no-session-entry");
6156
6494
  let raw;
6157
6495
  try {
6158
- raw = readFileSync8(engineStatusSnapshotPath(harnessDir), "utf8");
6496
+ raw = readFileSync9(engineStatusSnapshotPath(harnessDir), "utf8");
6159
6497
  } catch (error) {
6160
6498
  const code = error.code;
6161
6499
  return engineStatusUnavailable(code === "ENOENT" ? "absent" : "unreadable");
@@ -6195,7 +6533,7 @@ function serializeEnvelope(entries, bindings) {
6195
6533
  function loadForWrite(harnessDir) {
6196
6534
  let raw;
6197
6535
  try {
6198
- raw = readFileSync8(engineStatusSnapshotPath(harnessDir), "utf8");
6536
+ raw = readFileSync9(engineStatusSnapshotPath(harnessDir), "utf8");
6199
6537
  } catch (error) {
6200
6538
  if (error.code === "ENOENT") {
6201
6539
  return {
@@ -6294,7 +6632,7 @@ function writeEngineStatusSnapshot(harnessDir, input) {
6294
6632
  const file = engineStatusSnapshotPath(harnessDir);
6295
6633
  const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
6296
6634
  try {
6297
- mkdirSync3(dir, { recursive: true });
6635
+ mkdirSync4(dir, { recursive: true });
6298
6636
  return withWorkflowDirLock(dir, () => {
6299
6637
  const loaded = loadForWrite(harnessDir);
6300
6638
  if (loaded.kind === "refused")
@@ -6353,7 +6691,7 @@ var bindingStoreMemos = new Map;
6353
6691
  function parseBindingStore(file) {
6354
6692
  let raw;
6355
6693
  try {
6356
- raw = readFileSync8(file, "utf8");
6694
+ raw = readFileSync9(file, "utf8");
6357
6695
  } catch {
6358
6696
  return { kind: "unavailable", reason: "store-unreadable" };
6359
6697
  }
@@ -6430,7 +6768,7 @@ function updateWorkflowSessionBinding(harnessDir, sessionId, cwd, update) {
6430
6768
  const dir = dirname9(file);
6431
6769
  const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
6432
6770
  try {
6433
- mkdirSync3(dir, { recursive: true });
6771
+ mkdirSync4(dir, { recursive: true });
6434
6772
  return withWorkflowDirLock(dir, () => {
6435
6773
  const loaded = loadForWrite(harnessDir);
6436
6774
  if (loaded.kind === "refused")
@@ -6479,7 +6817,7 @@ function residualSeverityIndex(severity) {
6479
6817
  function pluginVersion() {
6480
6818
  for (const rel of ["../package.json", "../../package.json"]) {
6481
6819
  try {
6482
- const pkg = JSON.parse(readFileSync11(new URL(rel, import.meta.url), "utf8"));
6820
+ const pkg = JSON.parse(readFileSync12(new URL(rel, import.meta.url), "utf8"));
6483
6821
  if (typeof pkg.version === "string" && pkg.version !== "")
6484
6822
  return pkg.version;
6485
6823
  } catch {}
@@ -6606,7 +6944,7 @@ function renderEngineStatusCatalog(source) {
6606
6944
  const policy = [
6607
6945
  state.pushPolicy !== null ? `push ${state.pushPolicy}` : null,
6608
6946
  state.worktreeMode !== null ? `worktree ${state.worktreeMode}` : null,
6609
- state.controlWorktreePath !== null ? `control ${state.controlWorktreePath}` : null
6947
+ state.integrationWorktreePath !== null ? `integration ${state.integrationWorktreePath}` : null
6610
6948
  ].filter((part) => part !== null).join("; ");
6611
6949
  if (policy !== "")
6612
6950
  lines.push(`policy: ${policy}`);
@@ -6645,8 +6983,8 @@ function hhmm(ts) {
6645
6983
  return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
6646
6984
  }
6647
6985
  function harnessStateSource(harnessDir, selection) {
6648
- const statusPath = join12(harnessDir, STATUS_FILE);
6649
- if (!existsSync14(statusPath))
6986
+ const statusPath = join18(harnessDir, STATUS_FILE);
6987
+ if (!existsSync16(statusPath))
6650
6988
  return null;
6651
6989
  try {
6652
6990
  const rollup = residualRollup(harnessDir);
@@ -6655,9 +6993,9 @@ function harnessStateSource(harnessDir, selection) {
6655
6993
  if (selection.kind === "error") {
6656
6994
  return selectionErrorState(selection, rollup, harnessDir);
6657
6995
  }
6658
- const snapshotPath = join12(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
6996
+ const snapshotPath = join18(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
6659
6997
  let snapshot;
6660
- if (!existsSync14(snapshotPath)) {
6998
+ if (!existsSync16(snapshotPath)) {
6661
6999
  return selectionErrorState({
6662
7000
  kind: "error",
6663
7001
  code: "workflow.selection.snapshot-unreadable",
@@ -6665,7 +7003,7 @@ function harnessStateSource(harnessDir, selection) {
6665
7003
  }, rollup, harnessDir);
6666
7004
  }
6667
7005
  try {
6668
- snapshot = readJson(snapshotPath);
7006
+ snapshot = readWorkflowSnapshot(join18(harnessDir, selection.dir)).snapshot;
6669
7007
  } catch {
6670
7008
  return selectionErrorState({
6671
7009
  kind: "error",
@@ -6717,11 +7055,11 @@ function harnessStateSource(harnessDir, selection) {
6717
7055
  specIntegrationBranch: str(branch?.integration),
6718
7056
  pushPolicy: str(executionPolicy?.push_policy),
6719
7057
  worktreeMode: str(executionPolicy?.worktree_mode),
6720
- controlWorktreePath: str(snapshot.control_worktree_path),
7058
+ integrationWorktreePath: str(snapshot.integration_worktree_path),
6721
7059
  leases,
6722
7060
  knowledge: knowledgeDigest(harnessDir),
6723
7061
  direction: compass !== undefined ? compassDirection(compass.compassPath) : null,
6724
- agentFlow: readAgentFlow(join12(harnessDir, selection.dir), 50)
7062
+ agentFlow: readAgentFlow(join18(harnessDir, selection.dir), 50)
6725
7063
  };
6726
7064
  } catch {
6727
7065
  return null;
@@ -6741,7 +7079,7 @@ function selectionErrorState(selection, rollup, harnessDir) {
6741
7079
  specIntegrationBranch: null,
6742
7080
  pushPolicy: null,
6743
7081
  worktreeMode: null,
6744
- controlWorktreePath: null,
7082
+ integrationWorktreePath: null,
6745
7083
  leases: [],
6746
7084
  knowledge: knowledgeDigest(harnessDir),
6747
7085
  direction: null,
@@ -6751,7 +7089,7 @@ function selectionErrorState(selection, rollup, harnessDir) {
6751
7089
  function projectRollupSource(harnessDir, residuals) {
6752
7090
  const milestones = [];
6753
7091
  const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
6754
- if (existsSync14(projectsDir)) {
7092
+ if (existsSync16(projectsDir)) {
6755
7093
  let entries;
6756
7094
  try {
6757
7095
  entries = readdirSync4(projectsDir, { withFileTypes: true });
@@ -6761,11 +7099,11 @@ function projectRollupSource(harnessDir, residuals) {
6761
7099
  for (const entry of entries) {
6762
7100
  if (!entry.isDirectory())
6763
7101
  continue;
6764
- const roadmapPath = join12(projectsDir, entry.name, PROJECT_ROADMAP_FILE);
6765
- if (!existsSync14(roadmapPath))
7102
+ const roadmapPath = join18(projectsDir, entry.name, PROJECT_ROADMAP_FILE);
7103
+ if (!existsSync16(roadmapPath))
6766
7104
  continue;
6767
7105
  try {
6768
- const doc = parseCompassFrontmatterText(readFileSync11(roadmapPath, "utf8"), roadmapPath);
7106
+ const doc = parseCompassFrontmatterText(readFileSync12(roadmapPath, "utf8"), roadmapPath);
6769
7107
  if (Array.isArray(doc.milestones)) {
6770
7108
  for (const milestone of doc.milestones) {
6771
7109
  if (typeof milestone === "string" && milestone.trim() !== "")
@@ -6827,7 +7165,7 @@ function residualRollup(harnessDir) {
6827
7165
  }
6828
7166
  function projectRegisters(harnessDir) {
6829
7167
  const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
6830
- if (!existsSync14(projectsDir))
7168
+ if (!existsSync16(projectsDir))
6831
7169
  return [];
6832
7170
  let entries;
6833
7171
  try {
@@ -6839,8 +7177,8 @@ function projectRegisters(harnessDir) {
6839
7177
  for (const entry of entries) {
6840
7178
  if (!entry.isDirectory())
6841
7179
  continue;
6842
- const registerPath = join12(projectsDir, entry.name, PROJECT_REGISTER_FILE);
6843
- if (!existsSync14(registerPath))
7180
+ const registerPath = join18(projectsDir, entry.name, PROJECT_REGISTER_FILE);
7181
+ if (!existsSync16(registerPath))
6844
7182
  continue;
6845
7183
  try {
6846
7184
  registers.push({ projectId: entry.name, doc: readJson(registerPath) });
@@ -6851,13 +7189,13 @@ function projectRegisters(harnessDir) {
6851
7189
  return registers;
6852
7190
  }
6853
7191
  function knowledgeDigest(harnessDir) {
6854
- const indexPath = join12(harnessDir, "knowledge", "README.md");
6855
- if (!existsSync14(indexPath))
7192
+ const indexPath = join18(harnessDir, "knowledge", "README.md");
7193
+ if (!existsSync16(indexPath))
6856
7194
  return null;
6857
7195
  try {
6858
7196
  const categories = new Set;
6859
7197
  let docCount = 0;
6860
- for (const line of readFileSync11(indexPath, "utf8").split(/\r?\n/)) {
7198
+ for (const line of readFileSync12(indexPath, "utf8").split(/\r?\n/)) {
6861
7199
  const row = line.trim().match(/^\|(.+)\|$/);
6862
7200
  if (row === null)
6863
7201
  continue;
@@ -6880,7 +7218,7 @@ function knowledgeDigest(harnessDir) {
6880
7218
  }
6881
7219
  function compassDirection(compassPath) {
6882
7220
  try {
6883
- const content = readFileSync11(compassPath, "utf8");
7221
+ const content = readFileSync12(compassPath, "utf8");
6884
7222
  const section = content.match(/^## Direction lock[^\n]*\n+([\s\S]*?)(?=\n## |$)/m);
6885
7223
  if (section === null)
6886
7224
  return null;
@@ -6897,14 +7235,14 @@ function compassDirection(compassPath) {
6897
7235
  }
6898
7236
  function selectedCompass(harnessDir, workflowId, snapshot) {
6899
7237
  const ref = snapshot.compass_ref;
6900
- if (typeof ref !== "string" || ref.trim() === "" || isAbsolute7(ref))
7238
+ if (typeof ref !== "string" || ref.trim() === "" || isAbsolute8(ref))
6901
7239
  return;
6902
- const compassPath = join12(harnessDir, ref);
7240
+ const compassPath = join18(harnessDir, ref);
6903
7241
  if (!pathInside(harnessDir, compassPath))
6904
7242
  return;
6905
- if (basename5(compassPath) !== "delivery-compass.md")
7243
+ if (basename4(compassPath) !== "delivery-compass.md")
6906
7244
  return;
6907
- if (!existsSync14(compassPath))
7245
+ if (!existsSync16(compassPath))
6908
7246
  return;
6909
7247
  let resolvedHarness;
6910
7248
  let resolvedCompass;
@@ -6930,15 +7268,15 @@ function selectedCompass(harnessDir, workflowId, snapshot) {
6930
7268
  }
6931
7269
  function pathInside(root, candidate) {
6932
7270
  const rel = relative7(root, candidate);
6933
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute7(rel);
7271
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute8(rel);
6934
7272
  }
6935
7273
  function iterationGateSource(harnessDir, selection) {
6936
7274
  if (harnessDir === null || selection === undefined || selection.kind === "error")
6937
7275
  return;
6938
- const statusPath = join12(harnessDir, STATUS_FILE);
6939
- if (!existsSync14(statusPath))
7276
+ const statusPath = join18(harnessDir, STATUS_FILE);
7277
+ if (!existsSync16(statusPath))
6940
7278
  return;
6941
- const snapshotPath = join12(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
7279
+ const snapshotPath = join18(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
6942
7280
  try {
6943
7281
  const snapshotDoc = readJson(snapshotPath);
6944
7282
  const compass = selectedCompass(harnessDir, selection.workflowId, snapshotDoc);
@@ -7028,7 +7366,7 @@ function persistEngineStatusSnapshot(ctx, harnessDir, cwd, turn, catalogPayload,
7028
7366
  }
7029
7367
 
7030
7368
  // src/engine-status-endpoint.ts
7031
- import { isAbsolute as isAbsolute9 } from "node:path";
7369
+ import { isAbsolute as isAbsolute10 } from "node:path";
7032
7370
 
7033
7371
  // ../../node_modules/.bun/@deepseek-ai+dsh-typert-protocol@0.1.5-rc.2+1e4e7adc57ac8890/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
7034
7372
  import { Service as Service2 } from "@deepseek-ai/cordis";
@@ -7084,7 +7422,7 @@ function liveSeqOf(value) {
7084
7422
  function isRefusedPath(cwd) {
7085
7423
  if (cwd.includes("\x00"))
7086
7424
  return true;
7087
- if (!isAbsolute9(cwd))
7425
+ if (!isAbsolute10(cwd))
7088
7426
  return true;
7089
7427
  return cwd.split(/[\\/]+/).includes("..");
7090
7428
  }
@@ -7320,12 +7658,12 @@ function installEngineStatusEndpoint(ctx, options) {
7320
7658
  }
7321
7659
 
7322
7660
  // src/gates/status.ts
7323
- import { existsSync as existsSync15, readdirSync as readdirSync7 } from "node:fs";
7324
- import { basename as basename8, join as join14, relative as relative9, resolve as resolve10 } from "node:path";
7661
+ import { existsSync as existsSync17, readdirSync as readdirSync7 } from "node:fs";
7662
+ import { basename as basename6, join as join20, relative as relative9, resolve as resolve10 } from "node:path";
7325
7663
  var LOGGER_NAME = "mstar/status-gate";
7326
7664
  function harnessDocKindOfTarget(harnessDir, targetPath) {
7327
7665
  const resolved = resolve10(targetPath);
7328
- const name = basename8(resolved);
7666
+ const name = basename6(resolved);
7329
7667
  if (name !== STATUS_FILE && name !== WORKFLOW_SNAPSHOT_FILE && name !== PROJECT_REGISTER_FILE)
7330
7668
  return null;
7331
7669
  const rel = relative9(harnessDir, resolved);
@@ -7337,8 +7675,8 @@ function harnessDocKindOfTarget(harnessDir, targetPath) {
7337
7675
  workflowDir = resolveWorkflowDir(harnessDir, { harnessDir });
7338
7676
  projectDir = resolveProjectDir(harnessDir, { harnessDir });
7339
7677
  } catch {
7340
- workflowDir = join14(harnessDir, "workflows");
7341
- projectDir = join14(harnessDir, "projects");
7678
+ workflowDir = join20(harnessDir, "workflows");
7679
+ projectDir = join20(harnessDir, "projects");
7342
7680
  }
7343
7681
  if (name === WORKFLOW_SNAPSHOT_FILE && /^[^/]+\/snapshot\.json$/.test(relative9(workflowDir, resolved)))
7344
7682
  return "snapshot";
@@ -7402,8 +7740,8 @@ function projectRegisterDocs(harnessDir) {
7402
7740
  for (const entry of entries) {
7403
7741
  if (!entry.isDirectory())
7404
7742
  continue;
7405
- const registerPath = join14(projectsDir, entry.name, PROJECT_REGISTER_FILE);
7406
- if (!existsSync15(registerPath))
7743
+ const registerPath = join20(projectsDir, entry.name, PROJECT_REGISTER_FILE);
7744
+ if (!existsSync17(registerPath))
7407
7745
  continue;
7408
7746
  try {
7409
7747
  registers.push({ projectId: entry.name, doc: readJson(registerPath) });
@@ -7476,8 +7814,8 @@ async function editIntentListener(ctx, resolver, config, adapter, target, actor,
7476
7814
  }
7477
7815
 
7478
7816
  // src/gates/skill-lint.ts
7479
- import { existsSync as existsSync16, readFileSync as readFileSync13 } from "node:fs";
7480
- import { basename as basename10, dirname as dirname10, resolve as resolve12, sep as sep5 } from "node:path";
7817
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
7818
+ import { basename as basename7, dirname as dirname11, resolve as resolve12, sep as sep5 } from "node:path";
7481
7819
  var SKILL_LINT_LOGGER = "mstar/skill-lint";
7482
7820
 
7483
7821
  class SkillLintVetoError extends Error {
@@ -7518,7 +7856,7 @@ function lintSkillDoc(doc, options = {}) {
7518
7856
  return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
7519
7857
  }
7520
7858
  function lintSkillWrite(doc, options) {
7521
- const skillId = basename10(dirname10(resolve12(options.target)));
7859
+ const skillId = basename7(dirname11(resolve12(options.target)));
7522
7860
  const result = lintSkillDoc(doc, { skillId });
7523
7861
  if (options.hard && !result.ok) {
7524
7862
  throw new SkillLintVetoError(options.target, result.violations);
@@ -7531,13 +7869,13 @@ function skillRootsOf(config) {
7531
7869
  return roots.map((root) => root.trim()).filter((root) => root !== "");
7532
7870
  }
7533
7871
  function isSkillTarget(roots, target) {
7534
- if (basename10(target.displayPath) !== "SKILL.md")
7872
+ if (basename7(target.displayPath) !== "SKILL.md")
7535
7873
  return false;
7536
7874
  const resolvedPath = resolve12(target.displayPath);
7537
7875
  return roots.some((root) => resolvedPath.startsWith(resolve12(root) + sep5));
7538
7876
  }
7539
7877
  function skillNameOf(target) {
7540
- return basename10(dirname10(target.displayPath));
7878
+ return basename7(dirname11(target.displayPath));
7541
7879
  }
7542
7880
  function skillCanonicalForm(target) {
7543
7881
  return resolveSkillRoot("dsh", { skill: skillNameOf(target), rel: "SKILL.md" });
@@ -7550,11 +7888,11 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
7550
7888
  if (!isSkillTarget(roots, target))
7551
7889
  return;
7552
7890
  const skillPath = resolve12(target.displayPath);
7553
- if (!existsSync16(skillPath))
7891
+ if (!existsSync18(skillPath))
7554
7892
  return;
7555
7893
  let doc;
7556
7894
  try {
7557
- doc = readFileSync13(skillPath, "utf8");
7895
+ doc = readFileSync14(skillPath, "utf8");
7558
7896
  } catch (error) {
7559
7897
  ctx.logger(SKILL_LINT_LOGGER).error(`skill lint degraded to allow (cannot read ${skillPath}): ${error.message}`);
7560
7898
  ctx.emit("mstar/skill-lint", {
@@ -7567,7 +7905,7 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
7567
7905
  });
7568
7906
  return;
7569
7907
  }
7570
- const result = lintSkillDoc(doc, { skillId: basename10(dirname10(skillPath)) });
7908
+ const result = lintSkillDoc(doc, { skillId: basename7(dirname11(skillPath)) });
7571
7909
  if (result.ok)
7572
7910
  return;
7573
7911
  const hard = resolveSeamHard(harnessDir, config);
@@ -7601,8 +7939,8 @@ async function skillWriteIntentListener(ctx, resolver, config, target, actor, ne
7601
7939
  }
7602
7940
 
7603
7941
  // src/gates/seams.ts
7604
- import { existsSync as existsSync17, readFileSync as readFileSync14, readdirSync as readdirSync10 } from "node:fs";
7605
- import { basename as basename11, dirname as dirname11, join as join17, resolve as resolve14, sep as sep7 } from "node:path";
7942
+ import { existsSync as existsSync19, readFileSync as readFileSync15, readdirSync as readdirSync10 } from "node:fs";
7943
+ import { basename as basename9, dirname as dirname12, join as join21, resolve as resolve14, sep as sep7 } from "node:path";
7606
7944
 
7607
7945
  // ../engine/dist/audit.js
7608
7946
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -7801,7 +8139,7 @@ var SEAM_LOGGERS = {
7801
8139
  roles: "mstar/roles-lint"
7802
8140
  };
7803
8141
  function isIndexFile(path) {
7804
- const base = basename11(path);
8142
+ const base = basename9(path);
7805
8143
  return base === "README.md" || base === "index.md";
7806
8144
  }
7807
8145
  function isMarkdownDoc(path) {
@@ -7834,14 +8172,14 @@ function rolesDirOf(path) {
7834
8172
  const segments = resolve14(path).split(sep7);
7835
8173
  const idx = segments.indexOf("mstar-roles");
7836
8174
  if (idx === -1)
7837
- return dirname11(path);
7838
- return idx === 0 ? sep7 : join17(sep7, ...segments.slice(1, idx + 1));
8175
+ return dirname12(path);
8176
+ return idx === 0 ? sep7 : join21(sep7, ...segments.slice(1, idx + 1));
7839
8177
  }
7840
8178
  function isSeamTarget(seam, harnessDir, target) {
7841
8179
  const path = resolve14(target.displayPath);
7842
8180
  switch (seam) {
7843
8181
  case "design-md": {
7844
- const base = basename11(path);
8182
+ const base = basename9(path);
7845
8183
  return base === "DESIGN.md" || base === "DESIGN.dark.md";
7846
8184
  }
7847
8185
  case "audit":
@@ -7849,7 +8187,7 @@ function isSeamTarget(seam, harnessDir, target) {
7849
8187
  case "compound": {
7850
8188
  if (harnessDir === null)
7851
8189
  return false;
7852
- return path.startsWith(resolve14(join17(harnessDir, "knowledge")) + sep7) && isMarkdownDoc(path);
8190
+ return path.startsWith(resolve14(join21(harnessDir, "knowledge")) + sep7) && isMarkdownDoc(path);
7853
8191
  }
7854
8192
  case "roles":
7855
8193
  return isRolesTarget(path);
@@ -7866,11 +8204,11 @@ function auditSecretsViolations(findings, file) {
7866
8204
  }
7867
8205
  function validateDesignDoc(doc, path) {
7868
8206
  const violations = [...validateDesignTokenFrontmatter(doc).violations];
7869
- const isDark = basename11(path) === "DESIGN.dark.md";
7870
- const sibling = join17(dirname11(path), isDark ? "DESIGN.md" : "DESIGN.dark.md");
7871
- if (existsSync17(sibling)) {
7872
- const light = isDark ? readFileSync14(sibling, "utf8") : doc;
7873
- const dark = isDark ? doc : readFileSync14(sibling, "utf8");
8207
+ const isDark = basename9(path) === "DESIGN.dark.md";
8208
+ const sibling = join21(dirname12(path), isDark ? "DESIGN.md" : "DESIGN.dark.md");
8209
+ if (existsSync19(sibling)) {
8210
+ const light = isDark ? readFileSync15(sibling, "utf8") : doc;
8211
+ const dark = isDark ? doc : readFileSync15(sibling, "utf8");
7874
8212
  violations.push(...assertLightDarkParity(light, dark).violations);
7875
8213
  }
7876
8214
  return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
@@ -7884,21 +8222,21 @@ function validateAuditDoc(doc, path) {
7884
8222
  function validateCompoundDoc(doc, _path, harnessDir) {
7885
8223
  const violations = [...validateSchemaYaml(doc).violations];
7886
8224
  if (harnessDir !== null) {
7887
- violations.push(...referenceExists(dirname11(harnessDir), doc).violations);
8225
+ violations.push(...referenceExists(dirname12(harnessDir), doc).violations);
7888
8226
  }
7889
8227
  return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
7890
8228
  }
7891
- function validateRolesState(rolesDir, skillsRoot = dirname11(rolesDir)) {
8229
+ function validateRolesState(rolesDir, skillsRoot = dirname12(rolesDir)) {
7892
8230
  const violations = [...validateRoleMapping(rolesDir).violations];
7893
8231
  const skillTexts = {};
7894
8232
  for (const entry of readdirSync10(skillsRoot, { withFileTypes: true })) {
7895
8233
  if (!entry.isDirectory() || !entry.name.startsWith("mstar-"))
7896
8234
  continue;
7897
- const skillFile = join17(skillsRoot, entry.name, "SKILL.md");
7898
- if (!existsSync17(skillFile))
8235
+ const skillFile = join21(skillsRoot, entry.name, "SKILL.md");
8236
+ if (!existsSync19(skillFile))
7899
8237
  continue;
7900
8238
  try {
7901
- skillTexts[entry.name] = readFileSync14(skillFile, "utf8");
8239
+ skillTexts[entry.name] = readFileSync15(skillFile, "utf8");
7902
8240
  } catch {}
7903
8241
  }
7904
8242
  violations.push(...lintLoadOrder(skillTexts).violations);
@@ -7929,11 +8267,11 @@ function gateSeamIntent(ctx, harnessDir, config, seam, target) {
7929
8267
  if (!isSeamTarget(seam, harnessDir, target))
7930
8268
  return;
7931
8269
  const path = resolve14(target.displayPath);
7932
- if (!existsSync17(path))
8270
+ if (!existsSync19(path))
7933
8271
  return;
7934
8272
  let doc;
7935
8273
  try {
7936
- doc = readFileSync14(path, "utf8");
8274
+ doc = readFileSync15(path, "utf8");
7937
8275
  } catch (error) {
7938
8276
  logger.error(`seam lint degraded to allow (cannot read ${path}): ${error.message}`);
7939
8277
  emitSeamAdvisory(ctx, seam, target.displayPath, { ok: true, violations: [] }, false, { degraded: true });
@@ -7945,12 +8283,12 @@ function gateSeamIntent(ctx, harnessDir, config, seam, target) {
7945
8283
  const hard = resolveSeamHard(harnessDir, config);
7946
8284
  const verdict = applyEnforcement(result, { hard });
7947
8285
  if (verdict.hardBlocked) {
7948
- logger.error(`${basename11(path)} write to ${target.displayPath} ALLOWED as repair (Enforcement: hard; the current on-disk document is already invalid — the intent carries no incoming content, so the vetoable signal is only the pre-write state):
8286
+ logger.error(`${basename9(path)} write to ${target.displayPath} ALLOWED as repair (Enforcement: hard; the current on-disk document is already invalid — the intent carries no incoming content, so the vetoable signal is only the pre-write state):
7949
8287
  ${verdict.violations.map(formatViolation).join(`
7950
8288
  `)}`);
7951
8289
  emitSeamAdvisory(ctx, seam, target.displayPath, verdict, hard, { repair: true });
7952
8290
  } else {
7953
- logger.warn(`${basename11(path)} write to ${target.displayPath} (advisory):
8291
+ logger.warn(`${basename9(path)} write to ${target.displayPath} (advisory):
7954
8292
  ${verdict.violations.map(formatViolation).join(`
7955
8293
  `)}`);
7956
8294
  emitSeamAdvisory(ctx, seam, target.displayPath, verdict, hard);
@@ -7999,8 +8337,8 @@ function lintRolesWrite(doc, options) {
7999
8337
  }
8000
8338
 
8001
8339
  // src/gates/tools.ts
8002
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
8003
- import { join as join19, resolve as resolve16 } from "node:path";
8340
+ import { existsSync as existsSync20, readFileSync as readFileSync16 } from "node:fs";
8341
+ import { join as join22, resolve as resolve16 } from "node:path";
8004
8342
  import { defineTool } from "@deepseek-ai/dsh-tools";
8005
8343
  var ITERATION_VIOLATION_SCHEMA = {
8006
8344
  type: "object",
@@ -8165,9 +8503,9 @@ function registerSddIterationTools(ctx, resolver) {
8165
8503
  presentResult: (_args, _result) => ({ card: "generic", title: "Iteration gate evaluation" }),
8166
8504
  isConcurrencySafe: () => false,
8167
8505
  async execute(args) {
8168
- if (!existsSync18(args.snapshot_path))
8506
+ if (!existsSync20(args.snapshot_path))
8169
8507
  throw new Error(`workflow snapshot not found: ${args.snapshot_path}`);
8170
- if (!existsSync18(args.compass_path))
8508
+ if (!existsSync20(args.compass_path))
8171
8509
  throw new Error(`compass file not found: ${args.compass_path}`);
8172
8510
  const snapshotDoc = readJson(args.snapshot_path);
8173
8511
  const compassDoc = parseCompassFrontmatter(args.compass_path);
@@ -8221,10 +8559,10 @@ function registerSeamTools(ctx, resolver) {
8221
8559
  isConcurrencySafe: () => false,
8222
8560
  async execute(args) {
8223
8561
  const abs = resolve16(args.dir);
8224
- const lightPath = join19(abs, "DESIGN.md");
8225
- if (!existsSync18(lightPath))
8562
+ const lightPath = join22(abs, "DESIGN.md");
8563
+ if (!existsSync20(lightPath))
8226
8564
  throw new Error(`design file not found: ${lightPath}`);
8227
- const light = readFileSync15(lightPath, "utf8");
8565
+ const light = readFileSync16(lightPath, "utf8");
8228
8566
  const result = validateDesignDoc(light, lightPath);
8229
8567
  const level = completenessLevel(light);
8230
8568
  return {
@@ -8275,9 +8613,9 @@ function registerSeamTools(ctx, resolver) {
8275
8613
  isConcurrencySafe: () => false,
8276
8614
  async execute(args) {
8277
8615
  const abs = resolve16(args.plan_path);
8278
- if (!existsSync18(abs))
8616
+ if (!existsSync20(abs))
8279
8617
  throw new Error(`plan file not found: ${abs}`);
8280
- const text = readFileSync15(abs, "utf8");
8618
+ const text = readFileSync16(abs, "utf8");
8281
8619
  const result = validateAuditDoc(text, abs);
8282
8620
  return { ok: result.ok, violations: result.violations.map(iterationViolationView), secrets: result.findings };
8283
8621
  }
@@ -8315,9 +8653,9 @@ function registerSeamTools(ctx, resolver) {
8315
8653
  isConcurrencySafe: () => false,
8316
8654
  async execute(args, exec) {
8317
8655
  const abs = resolve16(args.doc_path);
8318
- if (!existsSync18(abs))
8656
+ if (!existsSync20(abs))
8319
8657
  throw new Error(`knowledge doc not found: ${abs}`);
8320
- const text = readFileSync15(abs, "utf8");
8658
+ const text = readFileSync16(abs, "utf8");
8321
8659
  const base = validateCompoundDoc(text, abs, args.repo_root !== undefined ? null : resolver.forAgent(exec.agent));
8322
8660
  const violations = [...base.violations];
8323
8661
  if (args.repo_root !== undefined) {
@@ -8360,7 +8698,7 @@ function registerSeamTools(ctx, resolver) {
8360
8698
  isConcurrencySafe: () => false,
8361
8699
  async execute(args) {
8362
8700
  const rolesDir = resolve16(args.roles_dir);
8363
- if (!existsSync18(join19(rolesDir, "SKILL.md")))
8701
+ if (!existsSync20(join22(rolesDir, "SKILL.md")))
8364
8702
  throw new Error(`roles dir not found: ${rolesDir}`);
8365
8703
  const result = validateRolesState(rolesDir, args.skills_root !== undefined ? resolve16(args.skills_root) : undefined);
8366
8704
  return { ok: result.ok, violations: result.violations.map(iterationViolationView) };
@@ -8370,8 +8708,8 @@ function registerSeamTools(ctx, resolver) {
8370
8708
  }
8371
8709
 
8372
8710
  // src/gates/adapter.ts
8373
- import { existsSync as existsSync19 } from "node:fs";
8374
- import { join as join20 } from "node:path";
8711
+ import { existsSync as existsSync21 } from "node:fs";
8712
+ import { join as join23 } from "node:path";
8375
8713
  import { Service as Service3 } from "@deepseek-ai/cordis";
8376
8714
  var HOST_LOGGER = "mstar/host-adapter";
8377
8715
  function mergeLeasesMatch(left, right) {
@@ -8407,7 +8745,7 @@ class DshHostAdapter extends Service3 {
8407
8745
  this.logSink(level, msg);
8408
8746
  }
8409
8747
  statusGate(path, kind, harnessDir) {
8410
- if (!existsSync19(path))
8748
+ if (!existsSync21(path))
8411
8749
  return { ok: true, violations: [] };
8412
8750
  return validateStatusDoc(path, kind, harnessDir);
8413
8751
  }
@@ -8486,7 +8824,7 @@ class DshHostAdapter extends Service3 {
8486
8824
  if (harnessDir !== null) {
8487
8825
  const selection = resolveActiveWorkflow(harnessDir);
8488
8826
  if (selection.kind === "active") {
8489
- const snapshotPath = join20(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
8827
+ const snapshotPath = join23(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
8490
8828
  try {
8491
8829
  const snapshot = readJson(snapshotPath);
8492
8830
  const stored = snapshot?.integration_merge_lease;
@@ -8512,8 +8850,8 @@ class DshHostAdapter extends Service3 {
8512
8850
  }
8513
8851
 
8514
8852
  // src/gates/agent-personas.ts
8515
- import { readFileSync as readFileSync16, readdirSync as readdirSync12, statSync as statSync8 } from "node:fs";
8516
- import { join as join21, resolve as resolve17, sep as sep8 } from "node:path";
8853
+ import { readFileSync as readFileSync17, readdirSync as readdirSync12, statSync as statSync8 } from "node:fs";
8854
+ import { join as join24, resolve as resolve17, sep as sep8 } from "node:path";
8517
8855
  var ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
8518
8856
  var defaultCache = new Map;
8519
8857
  function personaFor(roleId, lookup, warn) {
@@ -8541,7 +8879,7 @@ function subagentRoleIds(agentsDir) {
8541
8879
  const roleId = name.slice(0, -3);
8542
8880
  let content;
8543
8881
  try {
8544
- content = readFileSync16(join21(agentsDir, name), "utf8");
8882
+ content = readFileSync17(join24(agentsDir, name), "utf8");
8545
8883
  } catch {
8546
8884
  continue;
8547
8885
  }
@@ -8571,7 +8909,7 @@ function defaultFromMirror(agentsDir, roleId, warn) {
8571
8909
  return hit.text;
8572
8910
  let content;
8573
8911
  try {
8574
- content = readFileSync16(file, "utf8");
8912
+ content = readFileSync17(file, "utf8");
8575
8913
  } catch {
8576
8914
  return;
8577
8915
  }
@@ -8802,8 +9140,8 @@ function log2(level, message) {
8802
9140
  }
8803
9141
 
8804
9142
  // src/gates/workflow-ledger.ts
8805
- import { readFileSync as readFileSync17, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "node:fs";
8806
- import { join as join22 } from "node:path";
9143
+ import { readFileSync as readFileSync18, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "node:fs";
9144
+ import { join as join25 } from "node:path";
8807
9145
  var WORKFLOW_LEDGER_LOGGER = "mstar/workflow-ledger";
8808
9146
  var TOOL_WORKFLOW_RUN_START = "tool-workflow/run-start";
8809
9147
  var TOOL_WORKFLOW_AGENT_START = "tool-workflow/agent-start";
@@ -8863,7 +9201,7 @@ function loadWatermark(workflowDir, fresh = false) {
8863
9201
  return cached;
8864
9202
  const watermark = new Map;
8865
9203
  try {
8866
- const raw = readFileSync17(join22(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
9204
+ const raw = readFileSync18(join25(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
8867
9205
  const record = asRecord(JSON.parse(raw));
8868
9206
  const cursors = asRecord(record?.cursors);
8869
9207
  if (cursors !== undefined) {
@@ -8886,7 +9224,7 @@ function loadWatermark(workflowDir, fresh = false) {
8886
9224
  }
8887
9225
  function saveWatermark(workflowDir, watermark) {
8888
9226
  try {
8889
- const file = join22(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE);
9227
+ const file = join25(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE);
8890
9228
  const tmp = `${file}.tmp`;
8891
9229
  writeFileSync7(tmp, JSON.stringify({ v: 1, cursors: Object.fromEntries(watermark) }));
8892
9230
  renameSync4(tmp, file);
@@ -9205,8 +9543,8 @@ function registerWorkflowLedger(ctx, resolver, workflowAskCache) {
9205
9543
  }
9206
9544
 
9207
9545
  // src/gates/goal-bridge.ts
9208
- import { existsSync as existsSync20, readdirSync as readdirSync13 } from "node:fs";
9209
- import { join as join23 } from "node:path";
9546
+ import { existsSync as existsSync22, readdirSync as readdirSync13 } from "node:fs";
9547
+ import { join as join26 } from "node:path";
9210
9548
  var GOAL_BRIDGE_LOGGER = "mstar/goal-bridge";
9211
9549
  var GOAL_CHANGE_EVENT_TYPE = "goal/change";
9212
9550
  var GOAL_CHANGE_VERSION = 1;
@@ -9231,12 +9569,12 @@ function projectRegisterPointer(harnessDir) {
9231
9569
  for (const entry of readdirSync13(projectsDir, { withFileTypes: true })) {
9232
9570
  if (!entry.isDirectory())
9233
9571
  continue;
9234
- const registerPath = join23(projectsDir, entry.name, PROJECT_REGISTER_FILE);
9235
- if (existsSync20(registerPath))
9572
+ const registerPath = join26(projectsDir, entry.name, PROJECT_REGISTER_FILE);
9573
+ if (existsSync22(registerPath))
9236
9574
  return registerPath;
9237
9575
  }
9238
9576
  } catch {}
9239
- return join23(projectsDir, _DEFAULT_PROJECT, PROJECT_REGISTER_FILE);
9577
+ return join26(projectsDir, _DEFAULT_PROJECT, PROJECT_REGISTER_FILE);
9240
9578
  }
9241
9579
  function blockedAdvisoryOf(envelope) {
9242
9580
  const record = asRecord(envelope);
@@ -9287,8 +9625,8 @@ function registerGoalBridge(ctx, resolver) {
9287
9625
  }
9288
9626
 
9289
9627
  // src/gates/plan-mode-bridge.ts
9290
- import { existsSync as existsSync21 } from "node:fs";
9291
- import { isAbsolute as isAbsolute10, join as join24, relative as relative10, sep as sep9 } from "node:path";
9628
+ import { existsSync as existsSync23 } from "node:fs";
9629
+ import { isAbsolute as isAbsolute11, join as join27, relative as relative10, sep as sep9 } from "node:path";
9292
9630
 
9293
9631
  // src/gates/steering.ts
9294
9632
  function isRootLikeAgent(agent) {
@@ -9332,7 +9670,7 @@ function errorMessage5(error) {
9332
9670
  return error instanceof Error ? error.message : String(error);
9333
9671
  }
9334
9672
  function selectedLifecycle(harnessDir, hint) {
9335
- if (!existsSync21(join24(harnessDir, STATUS_FILE)))
9673
+ if (!existsSync23(join27(harnessDir, STATUS_FILE)))
9336
9674
  return { kind: "none" };
9337
9675
  const selection = resolveReadWorkflow(harnessDir, hint);
9338
9676
  if (selection.kind === "error") {
@@ -9340,7 +9678,7 @@ function selectedLifecycle(harnessDir, hint) {
9340
9678
  }
9341
9679
  let doc;
9342
9680
  try {
9343
- doc = readJson(join24(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE));
9681
+ doc = readJson(join27(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE));
9344
9682
  } catch {
9345
9683
  return { kind: "none" };
9346
9684
  }
@@ -9350,13 +9688,13 @@ function selectedLifecycle(harnessDir, hint) {
9350
9688
  }
9351
9689
  function selectedCompassSteers(harnessDir, lifecycle) {
9352
9690
  const ref = lifecycle.doc.compass_ref;
9353
- if (typeof ref !== "string" || ref.trim() === "" || isAbsolute10(ref))
9691
+ if (typeof ref !== "string" || ref.trim() === "" || isAbsolute11(ref))
9354
9692
  return false;
9355
- const compassPath = join24(harnessDir, ref);
9693
+ const compassPath = join27(harnessDir, ref);
9356
9694
  const inside = relative10(harnessDir, compassPath);
9357
- if (inside === "" || inside === ".." || inside.startsWith(`..${sep9}`) || isAbsolute10(inside))
9695
+ if (inside === "" || inside === ".." || inside.startsWith(`..${sep9}`) || isAbsolute11(inside))
9358
9696
  return false;
9359
- if (!existsSync21(compassPath))
9697
+ if (!existsSync23(compassPath))
9360
9698
  return false;
9361
9699
  let fields;
9362
9700
  try {
@@ -9869,7 +10207,7 @@ var inject = ["loader"];
9869
10207
  function packagedCommandsDir() {
9870
10208
  try {
9871
10209
  const dir = fileURLToPath3(new URL("../harness-commands", import.meta.url));
9872
- return existsSync22(dir) ? dir : undefined;
10210
+ return existsSync24(dir) ? dir : undefined;
9873
10211
  } catch {
9874
10212
  return;
9875
10213
  }
@@ -9925,7 +10263,7 @@ function registerMstarCommands(ctx) {
9925
10263
  for (const file of readdirSync14(dir).sort()) {
9926
10264
  if (!file.endsWith(".md"))
9927
10265
  continue;
9928
- const parsed = parseCommandMarkdown(readFileSync18(join25(dir, file), "utf8"));
10266
+ const parsed = parseCommandMarkdown(readFileSync19(join28(dir, file), "utf8"));
9929
10267
  if (parsed === undefined)
9930
10268
  continue;
9931
10269
  commandsCtx.commands.register({