@mstar-harness/dsh 3.6.3 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +8 -8
  3. package/README.zh.md +7 -5
  4. package/dist/client/panel/PanelView.d.ts +8 -11
  5. package/dist/client/panel/TabNav.d.ts +2 -2
  6. package/dist/client/panel/graph/event-log.d.ts +5 -9
  7. package/dist/client/panel/graph/project-graph.d.ts +53 -72
  8. package/dist/client/panel/graph/schema.d.ts +31 -37
  9. package/dist/client/panel/locale.d.ts +19 -19
  10. package/dist/client/panel/pages/AgentCanvasPage.d.ts +40 -58
  11. package/dist/client/panel/pages/EventLogPage.d.ts +6 -6
  12. package/dist/client/panel/pages/IterationInfoSection.d.ts +9 -11
  13. package/dist/client/panel/pages/IterationTaskPage.d.ts +3 -3
  14. package/dist/client/panel/plan-sort.d.ts +3 -6
  15. package/dist/client/panel/state-section.d.ts +1 -1
  16. package/dist/client/panel/zones/Legend.d.ts +2 -2
  17. package/dist/client/panel/zones/ProjectRollup.d.ts +1 -2
  18. package/dist/client/panel/zones/TaskBoard.d.ts +1 -2
  19. package/dist/gates/_shared.d.ts +24 -16
  20. package/dist/gates/adapter.d.ts +6 -9
  21. package/dist/gates/agent-flow.d.ts +49 -59
  22. package/dist/gates/agent-personas.d.ts +1 -2
  23. package/dist/gates/catalog.d.ts +2 -5
  24. package/dist/gates/dispatch.d.ts +9 -11
  25. package/dist/gates/fallbacks-advisory.d.ts +55 -18
  26. package/dist/gates/fallbacks-probe.d.ts +2 -2
  27. package/dist/gates/fallbacks-seeds.d.ts +1 -1
  28. package/dist/gates/fallbacks-structural.d.ts +1 -1
  29. package/dist/gates/goal-bridge.d.ts +6 -9
  30. package/dist/gates/role-persona.d.ts +123 -7
  31. package/dist/gates/skill-lint.d.ts +19 -2
  32. package/dist/gates/system-prompt.d.ts +7 -11
  33. package/dist/gates/tools.d.ts +1 -1
  34. package/dist/gates/workflow-ledger.d.ts +21 -28
  35. package/dist/gates/workflow-policy.d.ts +15 -19
  36. package/dist/gates/workflow-selection.d.ts +3 -3
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.js +472 -183
  39. package/dist/types.d.ts +4 -7
  40. package/harness-commands/iteration-drive.md +6 -6
  41. package/harness-commands/iteration-loop.md +7 -7
  42. package/harness-commands/iteration-start.md +8 -8
  43. package/harness-skills/mstar-coding-behavior/SKILL.md +3 -20
  44. package/harness-skills/mstar-dispatch-gates/SKILL.md +7 -13
  45. package/harness-skills/mstar-dispatch-gates/references/leaf-executor-checklist.md +1 -1
  46. package/harness-skills/mstar-harness-core/SKILL.md +16 -53
  47. package/harness-skills/mstar-host/references/zcode.md +7 -0
  48. package/harness-skills/mstar-iteration/SKILL.md +32 -318
  49. package/harness-skills/mstar-iteration/references/command-shared-invariants.md +1 -1
  50. package/harness-skills/mstar-iteration/references/phase-1-prepare.md +155 -0
  51. package/harness-skills/mstar-iteration/references/phase-2-worktree-lease.md +113 -3
  52. package/harness-skills/mstar-iteration/references/phase5-helper-discovery.md +1 -1
  53. package/harness-skills/mstar-roles/SKILL.md +14 -12
  54. package/harness-skills/mstar-roles/references/_shared/leaf-executor-core.md +5 -6
  55. package/harness-skills/mstar-roles/references/architect.md +1 -1
  56. package/harness-skills/mstar-roles/references/code-reviewer.md +11 -1
  57. package/harness-skills/mstar-roles/references/frontend-dev.md +1 -1
  58. package/harness-skills/mstar-roles/references/fullstack-dev-shared.md +1 -1
  59. package/harness-skills/mstar-roles/references/ops-engineer.md +1 -1
  60. package/harness-skills/mstar-roles/references/product-manager.md +1 -1
  61. package/harness-skills/mstar-roles/references/project-manager/dispatch-and-assignment.md +4 -0
  62. package/harness-skills/mstar-roles/references/project-manager.md +6 -4
  63. package/harness-skills/mstar-roles/references/prompt-engineer.md +1 -1
  64. package/harness-skills/mstar-roles/references/qa-engineer.md +1 -1
  65. package/harness-skills/mstar-roles/references/qc-specialist-shared.md +1 -1
  66. package/harness-skills/mstar-roles/references/writing-specialist.md +1 -1
  67. package/harness-skills/mstar-sdd/references/file-handoffs.md +40 -7
  68. package/harness-skills/mstar-sdd/references/implementer-continuation-prompt.md +9 -0
  69. package/harness-skills/mstar-sdd/references/implementer-prompt.md +9 -0
  70. package/harness-skills/mstar-sdd/references/task-reviewer-prompt.md +7 -0
  71. 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 existsSync21, readFileSync as readFileSync18, readdirSync as readdirSync14 } from "node:fs";
1126
- import { join as join23 } from "node:path";
1125
+ import { existsSync as existsSync22, readFileSync as readFileSync18, readdirSync as readdirSync14 } from "node:fs";
1126
+ import { join as join24 } from "node:path";
1127
1127
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1128
1128
  import {
1129
1129
  apply as applySkillLocal,
@@ -1147,15 +1147,15 @@ import { AsyncLocalStorage } from "node:async_hooks";
1147
1147
  import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync } from "node:fs";
1148
1148
  import { dirname as dirname4, join as join7, resolve as resolve5, sep } from "node:path";
1149
1149
  import { execFileSync as execFileSync2 } from "node:child_process";
1150
- import { existsSync as existsSync7 } from "node:fs";
1151
- import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
1152
- import { execFileSync as execFileSync3 } from "node:child_process";
1153
- import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
1154
- import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute6, join as join10, resolve as resolve9 } from "node:path";
1155
- import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync10 } from "node:fs";
1156
- import { basename as basename6, isAbsolute as isAbsolute8, join as join13, relative as relative4, resolve as resolve12, sep as sep4 } from "node:path";
1157
- import { existsSync as existsSync10 } from "node:fs";
1158
- import { join as join14 } from "node:path";
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 { 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";
1159
1159
  var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
1160
1160
  function applyEnforcement(gate, opts) {
1161
1161
  return { ...gate, hardBlocked: opts.hard && gate.violations.length > 0 };
@@ -2467,6 +2467,25 @@ function assertSafePathComponent(value, what) {
2467
2467
  throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\") — got ${JSON.stringify(value)}`);
2468
2468
  }
2469
2469
  }
2470
+ function canonicalizeNearestExisting(path) {
2471
+ const abs = resolve7(path);
2472
+ let dir = abs;
2473
+ const tail = [];
2474
+ for (;; ) {
2475
+ if (existsSync6(dir)) {
2476
+ try {
2477
+ return join9(realpathSync2(dir), ...tail);
2478
+ } catch {
2479
+ return abs;
2480
+ }
2481
+ }
2482
+ const parent = dirname5(dir);
2483
+ if (parent === dir)
2484
+ return abs;
2485
+ tail.unshift(basename3(dir));
2486
+ dir = parent;
2487
+ }
2488
+ }
2470
2489
  function resolveSddDir(harnessDir, planId) {
2471
2490
  assertSafePathComponent(planId, "planId");
2472
2491
  const base = resolve7(harnessDir);
@@ -2564,6 +2583,28 @@ function probeBranch(worktreePath, opts) {
2564
2583
  return { error: detail };
2565
2584
  }
2566
2585
  }
2586
+ function probeCheckout(worktreePath, opts) {
2587
+ const timeout = opts.timeoutMs ?? probeTimeoutMs();
2588
+ try {
2589
+ const stdout = execFileSync2(opts.gitPath ?? "git", ["-C", worktreePath, "rev-parse", "--git-dir"], {
2590
+ encoding: "utf8",
2591
+ stdio: ["ignore", "pipe", "pipe"],
2592
+ timeout
2593
+ });
2594
+ const raw = stdout.trim();
2595
+ if (raw === "")
2596
+ return { error: `no git dir reported at "${worktreePath}"` };
2597
+ const abs = isAbsolute5(raw) ? raw : join10(worktreePath, raw);
2598
+ return { gitDir: realpathSync3(abs) };
2599
+ } catch (err) {
2600
+ const e = err;
2601
+ if (e.killed === true || e.signal !== undefined) {
2602
+ return { error: `git probe timed out after ${timeout}ms (killed by ${e.signal ?? "SIGTERM"})` };
2603
+ }
2604
+ const detail = (e.stderr !== undefined ? e.stderr.toString().trim() : "") || e.message || "git probe failed";
2605
+ return { error: detail };
2606
+ }
2607
+ }
2567
2608
  function l1PreDispatchCheck(input, opts = {}) {
2568
2609
  const violations = [];
2569
2610
  const { controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId } = input;
@@ -2576,8 +2617,20 @@ function l1PreDispatchCheck(input, opts = {}) {
2576
2617
  if (leaseWorkingBranch.trim() === "") {
2577
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"));
2578
2619
  }
2579
- if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
2580
- 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"));
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"));
2632
+ }
2633
+ }
2581
2634
  }
2582
2635
  if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
2583
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>`));
@@ -2626,6 +2679,16 @@ function l2PreDispatchCheck(input, opts = {}) {
2626
2679
  });
2627
2680
  return gate(violations);
2628
2681
  }
2682
+ function assertBranchAlignment(worktreePath, expectedBranch, opts = {}) {
2683
+ const violations = [];
2684
+ const probe = probeBranch(worktreePath, opts);
2685
+ 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)"));
2687
+ } 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`));
2689
+ }
2690
+ return gate(violations);
2691
+ }
2629
2692
  class SddScriptError extends Error {
2630
2693
  exitCode;
2631
2694
  constructor(message, exitCode) {
@@ -2662,14 +2725,14 @@ function gitOut(cwd, args) {
2662
2725
  }
2663
2726
  }
2664
2727
  function probeHarnessWithStatus(root) {
2665
- if (isFile2(join10(root, ".mstar", "status.json")))
2666
- return join10(root, ".mstar");
2667
- if (isFile2(join10(root, ".agents", "status.json")))
2668
- return join10(root, ".agents");
2669
- if (hasWorkflowSnapshot(join10(root, ".mstar")))
2670
- return join10(root, ".mstar");
2671
- if (hasWorkflowSnapshot(join10(root, ".agents")))
2672
- return join10(root, ".agents");
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");
2673
2736
  return null;
2674
2737
  }
2675
2738
  function hasWorkflowSnapshot(harnessDir) {
@@ -2677,13 +2740,13 @@ function hasWorkflowSnapshot(harnessDir) {
2677
2740
  try {
2678
2741
  workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
2679
2742
  } catch {
2680
- workflowsDir = join10(harnessDir, "workflows");
2743
+ workflowsDir = join11(harnessDir, "workflows");
2681
2744
  }
2682
2745
  if (!isDirectory2(workflowsDir))
2683
2746
  return false;
2684
2747
  try {
2685
2748
  for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
2686
- if (entry.isDirectory() && isFile2(join10(workflowsDir, entry.name, "snapshot.json")))
2749
+ if (entry.isDirectory() && isFile2(join11(workflowsDir, entry.name, "snapshot.json")))
2687
2750
  return true;
2688
2751
  }
2689
2752
  } catch {
@@ -2696,14 +2759,14 @@ function isLinkedWorktree(root) {
2696
2759
  const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
2697
2760
  if (gitDirRaw === null || commonRaw === null)
2698
2761
  return false;
2699
- const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join10(root, gitDirRaw);
2700
- const common = isAbsolute6(commonRaw) ? commonRaw : join10(root, commonRaw);
2762
+ const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join11(root, gitDirRaw);
2763
+ const common = isAbsolute6(commonRaw) ? commonRaw : join11(root, commonRaw);
2701
2764
  if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
2702
2765
  return true;
2703
2766
  try {
2704
- const gdParent = realpathSync3(dirname6(gitDir));
2705
- const cmAbs = realpathSync3(common);
2706
- return join10(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
2767
+ const gdParent = realpathSync4(dirname6(gitDir));
2768
+ const cmAbs = realpathSync4(common);
2769
+ return join11(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
2707
2770
  } catch {
2708
2771
  return false;
2709
2772
  }
@@ -2720,10 +2783,10 @@ function sddWorkspace(planId, opts = {}) {
2720
2783
  if (!isDirectory2(controlRoot)) {
2721
2784
  throw new SddScriptError(`mstar sdd workspace: CONTROL_ROOT / MSTAR_CONTROL_ROOT is not a directory: ${controlRoot}`, 1);
2722
2785
  }
2723
- root = realpathSync3(controlRoot);
2786
+ root = realpathSync4(controlRoot);
2724
2787
  } else {
2725
2788
  const topLevel = gitOut(cwd, ["rev-parse", "--show-toplevel"]);
2726
- root = realpathSync3(topLevel ?? cwd);
2789
+ root = realpathSync4(topLevel ?? cwd);
2727
2790
  }
2728
2791
  if (!controlRoot && isLinkedWorktree(root)) {
2729
2792
  throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
@@ -2744,25 +2807,35 @@ function sddWorkspace(planId, opts = {}) {
2744
2807
  const probed = probeHarnessWithStatus(root);
2745
2808
  if (probed) {
2746
2809
  harnessDir = probed;
2747
- } else if (isDirectory2(join10(root, ".mstar"))) {
2748
- harnessDir = join10(root, ".mstar");
2749
- } else if (isDirectory2(join10(root, ".agents"))) {
2750
- harnessDir = join10(root, ".agents");
2810
+ } else if (isDirectory2(join11(root, ".mstar"))) {
2811
+ harnessDir = join11(root, ".mstar");
2812
+ } else if (isDirectory2(join11(root, ".agents"))) {
2813
+ harnessDir = join11(root, ".agents");
2751
2814
  } else {
2752
- harnessDir = join10(root, ".mstar");
2815
+ harnessDir = join11(root, ".mstar");
2753
2816
  }
2754
2817
  }
2755
2818
  }
2756
2819
  const sddDir = resolveSddDir(harnessDir, planId);
2757
2820
  mkdirSync6(sddDir, { recursive: true });
2758
- writeFileSync4(join10(sddDir, ".gitignore"), `*
2821
+ writeFileSync4(join11(sddDir, ".gitignore"), `*
2759
2822
  `);
2760
- return realpathSync3(sddDir);
2823
+ return realpathSync4(sddDir);
2761
2824
  }
2762
2825
  function taskBrief(planFile, taskN, outFile, opts = {}) {
2763
2826
  if (!planFile || !Number.isInteger(taskN) || taskN < 1) {
2764
2827
  throw new SddScriptError("usage: mstar sdd task-brief PLAN_FILE TASK_NUMBER [OUTFILE]", 2);
2765
2828
  }
2829
+ const bound = opts.context;
2830
+ const observedCwd = opts.cwd ?? process.cwd();
2831
+ if (bound) {
2832
+ const inputPlan = canonicalizeNearestExisting(resolve9(observedCwd, planFile));
2833
+ if (inputPlan !== canonicalizeNearestExisting(bound.planFile)) {
2834
+ throwGateFail([
2835
+ contextViolation("high", "sdd.context.plan-file-mismatch", `plan file "${planFile}" does not match the bound context plan file "${bound.planFile}" — ` + "bound mode extracts only the resolved context's plan; refused before any read or write")
2836
+ ]);
2837
+ }
2838
+ }
2766
2839
  let content;
2767
2840
  try {
2768
2841
  content = readFileSync7(planFile, "utf8");
@@ -2770,15 +2843,26 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2770
2843
  throw new SddScriptError(`no such plan file: ${planFile}`, 2);
2771
2844
  }
2772
2845
  let out;
2846
+ let mkdirAfterGate = null;
2773
2847
  if (outFile) {
2774
- out = outFile;
2848
+ out = bound ? resolve9(observedCwd, outFile) : outFile;
2849
+ } else if (bound) {
2850
+ out = join11(bound.sddDir, `task-${taskN}-brief.md`);
2851
+ mkdirAfterGate = bound.sddDir;
2775
2852
  } else {
2776
2853
  const sddDir = opts.sddDir ?? process.env.SDD_DIR;
2777
2854
  if (!sddDir) {
2778
2855
  throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
2779
2856
  }
2780
2857
  mkdirSync6(sddDir, { recursive: true });
2781
- out = join10(sddDir, `task-${taskN}-brief.md`);
2858
+ out = join11(sddDir, `task-${taskN}-brief.md`);
2859
+ }
2860
+ if (bound) {
2861
+ const gate2 = checkSddAction(bound, { kind: "artifact", cwd: observedCwd, target: out });
2862
+ if (!gate2.ok)
2863
+ throwGateFail(gate2.violations);
2864
+ if (mkdirAfterGate !== null)
2865
+ mkdirSync6(mkdirAfterGate, { recursive: true });
2782
2866
  }
2783
2867
  const records = content.endsWith(`
2784
2868
  `) ? content.split(`
@@ -2804,7 +2888,98 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
2804
2888
  if (printed.length === 0) {
2805
2889
  throw new SddScriptError(`task ${taskN} not found in ${planFile} (no heading matching Task ${taskN})`, 3);
2806
2890
  }
2807
- return out;
2891
+ return bound ? resolve9(observedCwd, out) : out;
2892
+ }
2893
+ function isInside(child, ancestor) {
2894
+ const rel = relative3(ancestor, child);
2895
+ return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
2896
+ }
2897
+ function canonicalDir(path) {
2898
+ try {
2899
+ return statSync4(path).isDirectory() ? realpathSync4(path) : null;
2900
+ } catch {
2901
+ return null;
2902
+ }
2903
+ }
2904
+ function contextViolation(severity, code, message, fix) {
2905
+ return { ok: false, severity, code, message, fix };
2906
+ }
2907
+ function throwGateFail(violations) {
2908
+ const detail = violations.map((v) => `${v.code}: ${v.message}${v.fix ? ` (fix: ${v.fix})` : ""}`).join(`
2909
+ `);
2910
+ throw new SddScriptError(`SDD execution context rejected:
2911
+ ${detail}`, 1);
2912
+ }
2913
+ function checkSddAction(context, action) {
2914
+ const violations = [];
2915
+ const add = (code, message, fix) => {
2916
+ violations.push(contextViolation("high", code, message, fix));
2917
+ };
2918
+ if (action.kind !== "source" && action.kind !== "artifact" && action.kind !== "launch") {
2919
+ add("sdd.context.kind-unknown", `unknown action kind ${JSON.stringify(action.kind)} — expected "source" | "artifact" | "launch"`);
2920
+ return { ok: false, violations };
2921
+ }
2922
+ if (typeof action.cwd !== "string" || action.cwd.trim() === "") {
2923
+ add("sdd.context.cwd-missing", "action.cwd (the observed invocation cwd) is required — never an Assignment echo");
2924
+ return { ok: false, violations };
2925
+ }
2926
+ const featureReal = canonicalDir(context.featureCwd);
2927
+ const canonicalSddDir = canonicalizeNearestExisting(context.sddDir);
2928
+ const canonicalPlanFile = canonicalizeNearestExisting(context.planFile);
2929
+ const cwdResolved = resolve9(action.cwd);
2930
+ const checkTarget = (baseDir, target, kind) => {
2931
+ if (featureReal === null)
2932
+ return;
2933
+ const targetAbs = resolve9(baseDir, target);
2934
+ const canonical = canonicalizeNearestExisting(targetAbs);
2935
+ if (!isInside(canonical, featureReal)) {
2936
+ const declaredPrefix = targetAbs === featureReal || targetAbs.startsWith(`${featureReal}/`);
2937
+ if (declaredPrefix) {
2938
+ add("sdd.context.target-symlink-escape", `${kind} target "${target}" canonicalizes to "${canonical}", outside the feature worktree — symlink escape refused before mutation`);
2939
+ } else {
2940
+ add(`sdd.context.${kind}-target-outside-feature`, `${kind} target "${target}" resolves to "${targetAbs}", outside the feature worktree "${featureReal}" — refused before mutation`);
2941
+ }
2942
+ }
2943
+ };
2944
+ if (action.kind === "source") {
2945
+ const cwdReal = canonicalDir(cwdResolved);
2946
+ if (cwdReal === null) {
2947
+ add("sdd.context.cwd-missing", `observed source cwd "${action.cwd}" does not exist or is not a directory`);
2948
+ return { ok: false, violations };
2949
+ }
2950
+ if (featureReal === null || !isInside(cwdReal, featureReal)) {
2951
+ add("sdd.context.source-cwd-outside-feature", `observed source cwd "${cwdReal}" is outside the feature worktree "${context.featureCwd}" — a declared-correct context does not make a wrong-checkout write safe (A3)`, `run the source action from inside ${context.featureCwd}`);
2952
+ return { ok: false, violations };
2953
+ }
2954
+ if (action.target !== undefined)
2955
+ checkTarget(cwdReal, action.target, "source");
2956
+ } else if (action.kind === "artifact") {
2957
+ if (typeof action.target !== "string" || action.target.trim() === "") {
2958
+ add("sdd.context.target-missing", "artifact checks require the destination target");
2959
+ return { ok: false, violations };
2960
+ }
2961
+ const targetAbs = resolve9(cwdResolved, action.target);
2962
+ const canonical = canonicalizeNearestExisting(targetAbs);
2963
+ if (!isInside(canonical, canonicalSddDir) && canonical !== canonicalPlanFile) {
2964
+ const rawSddDir = resolve9(context.sddDir);
2965
+ const declaredPrefix = targetAbs === rawSddDir || targetAbs.startsWith(`${rawSddDir}/`) || targetAbs === canonicalSddDir || targetAbs.startsWith(`${canonicalSddDir}/`) || targetAbs === canonicalPlanFile;
2966
+ if (declaredPrefix) {
2967
+ add("sdd.context.artifact-symlink-escape", `artifact target "${targetAbs}" canonicalizes to "${canonical}", outside the plan's control sddDir — symlink escape refused before write`);
2968
+ } else {
2969
+ add("sdd.context.artifact-outside-plan", `artifact target "${targetAbs}" is outside the plan's control sddDir "${context.sddDir}" and is not the declared planFile "${context.planFile}" — legitimate control artifact edits stay inside the plan's artifacts; arbitrary control source edits are not allowed (A3)`);
2970
+ }
2971
+ }
2972
+ } else {
2973
+ if (featureReal === null) {
2974
+ add("sdd.context.launch-cwd-missing", `feature worktree "${context.featureCwd}" does not exist or is not a directory — cannot bind the child's starting cwd`);
2975
+ } else {
2976
+ violations.push(...assertBranchAlignment(featureReal, context.workingBranch).violations);
2977
+ }
2978
+ if (action.target !== undefined && action.target.trim() !== "" && featureReal !== null) {
2979
+ checkTarget(featureReal, action.target, "launch");
2980
+ }
2981
+ }
2982
+ return { ok: violations.length === 0, violations };
2808
2983
  }
2809
2984
  function violation8(severity, code, message, fix) {
2810
2985
  return { ok: false, severity, code, message, fix };
@@ -3547,7 +3722,7 @@ function referenceExists(repoRoot, docText) {
3547
3722
  break;
3548
3723
  if (entry.isDirectory()) {
3549
3724
  if (!WALK_SKIP_DIRS.has(entry.name))
3550
- stack.push(join13(dir, entry.name));
3725
+ stack.push(join15(dir, entry.name));
3551
3726
  } else if (!entry.isSymbolicLink()) {
3552
3727
  const base = entry.name.replace(/\.(?:ts|tsx|js|jsx|mjs|cjs)$/, "");
3553
3728
  if (moduleNames.has(base))
@@ -3559,7 +3734,7 @@ function referenceExists(repoRoot, docText) {
3559
3734
  for (const { ref, isSymbol, module } of refs) {
3560
3735
  if (!isSymbol || module === undefined) {
3561
3736
  const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
3562
- if (existsSync9(resolve12(repoRoot, candidate))) {
3737
+ if (existsSync10(resolve13(repoRoot, candidate))) {
3563
3738
  checked++;
3564
3739
  } else {
3565
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"));
@@ -3586,11 +3761,11 @@ function collectKnowledgeDocs(dir) {
3586
3761
  for (const entry of entries) {
3587
3762
  if (entry.isSymbolicLink())
3588
3763
  continue;
3589
- const full = join13(current, entry.name);
3764
+ const full = join15(current, entry.name);
3590
3765
  if (entry.isDirectory()) {
3591
3766
  stack.push(full);
3592
3767
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
3593
- docs.push(relative4(dir, full).split(sep4).join("/"));
3768
+ docs.push(relative6(dir, full).split(sep4).join("/"));
3594
3769
  }
3595
3770
  }
3596
3771
  }
@@ -3606,8 +3781,8 @@ function normalizeIndexRef(cell) {
3606
3781
  }
3607
3782
  function assertIndexRows(knowledgeDir) {
3608
3783
  const violations = [];
3609
- const readmePath = join13(knowledgeDir, "README.md");
3610
- if (!existsSync9(readmePath)) {
3784
+ const readmePath = join15(knowledgeDir, "README.md");
3785
+ if (!existsSync10(readmePath)) {
3611
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"));
3612
3787
  return { ok: false, violations };
3613
3788
  }
@@ -3631,12 +3806,12 @@ function assertIndexRows(knowledgeDir) {
3631
3806
  return { ok: violations.length === 0, violations };
3632
3807
  }
3633
3808
  function isFileLikeRoot(root) {
3634
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename6(root));
3809
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename7(root));
3635
3810
  }
3636
3811
  function scopeGuard(path, allowedRoots) {
3637
- const resolved = resolve12(path);
3812
+ const resolved = resolve13(path);
3638
3813
  for (const root of allowedRoots) {
3639
- const r = resolve12(root);
3814
+ const r = resolve13(root);
3640
3815
  if (isFileLikeRoot(r)) {
3641
3816
  if (resolved === r)
3642
3817
  return { ok: true, violations: [] };
@@ -3794,8 +3969,8 @@ function validateRoleMapping(rolesDir, options = {}) {
3794
3969
  const violations = [];
3795
3970
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
3796
3971
  for (const { agentId, reference } of mapping) {
3797
- if (!existsSync10(join14(rolesDir, reference))) {
3798
- violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join14(rolesDir, reference)} or fix the mapping row`));
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`));
3799
3974
  }
3800
3975
  }
3801
3976
  for (const { family, memberIds } of families) {
@@ -3847,6 +4022,14 @@ function validateRoleMapping(rolesDir, options = {}) {
3847
4022
  return { ok: violations.length === 0, violations };
3848
4023
  }
3849
4024
  var LOAD_ORDER_HEADING_RE = /^#{1,6}\s+[^\r\n]*\b(?:load[\s-]*order|first\s+action)\b[^\r\n]*$/i;
4025
+ var HUB_BOOTSTRAP_ASSERTIONS = [
4026
+ { marker: "identity-first", why: "identity boundary before any skill list" },
4027
+ { marker: "skill presets", why: "Assignment Skill presets decision field" },
4028
+ { marker: "none", why: "explicit none => identity only, no optional topic preset" },
4029
+ { marker: "standard", why: "omitted on a substantive round => standard preset" },
4030
+ { marker: "role-owned", why: "role-owned methods / evidence obligations load regardless of preset" },
4031
+ { marker: "unknown preset", why: "unknown preset / missing identity => Needs Context / Blocked, never infer PM" }
4032
+ ];
3850
4033
  function extractLoadOrderSection(text) {
3851
4034
  const lines = text.split(/\r?\n/);
3852
4035
  let start = -1;
@@ -3880,11 +4063,22 @@ function lintLoadOrder(skillTexts) {
3880
4063
  continue;
3881
4064
  const section = extractLoadOrderSection(text);
3882
4065
  if (section === null) {
3883
- 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 (Required))`, `add a "## Load Order" section naming mstar-harness-core as the first read`));
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`));
4067
+ continue;
4068
+ }
4069
+ if (name === "mstar-roles") {
4070
+ const lower = section.toLowerCase();
4071
+ const missing = HUB_BOOTSTRAP_ASSERTIONS.filter((a) => !lower.includes(a.marker.toLowerCase()));
4072
+ 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"));
4074
+ }
4075
+ 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"));
4077
+ }
3884
4078
  continue;
3885
4079
  }
3886
4080
  if (!section.includes("mstar-harness-core")) {
3887
- 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 § 加载约定: 凡 mstar-*(name ≠ mstar-harness-core)假定读者已 Read 本 skill)`, "name mstar-harness-core first in the Load Order section"));
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"));
3888
4082
  }
3889
4083
  }
3890
4084
  return { ok: violations.length === 0, violations };
@@ -3920,6 +4114,17 @@ var FIVE_QUESTION_SECTIONS = [
3920
4114
  { key: "evidence", label: "Evidence", question: "what a correct result looks like (success criteria / evidence)" },
3921
4115
  { key: "references", label: "References", question: "additional resources to open when the main path is not enough" }
3922
4116
  ];
4117
+ function stripFrontmatter(text) {
4118
+ const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
4119
+ if (lines.length === 0 || lines[0].trim() !== "---")
4120
+ return text;
4121
+ for (let i = 1;i < lines.length; i++) {
4122
+ if (lines[i].trim() === "---")
4123
+ return lines.slice(i + 1).join(`
4124
+ `);
4125
+ }
4126
+ return text;
4127
+ }
3923
4128
  var HEADING_RE = /^#{1,6}\s+[^\r\n]+$/;
3924
4129
  function collectHeadings(bodyText) {
3925
4130
  const headings = [];
@@ -3935,6 +4140,16 @@ function collectHeadings(bodyText) {
3935
4140
  }
3936
4141
  return headings;
3937
4142
  }
4143
+ function classifySkillLint(skillId) {
4144
+ const id = skillId ?? "";
4145
+ if (id === "mstar-harness-core")
4146
+ return { kind: "core", mode: null };
4147
+ if (id === "mstar-skill-authoring")
4148
+ return { kind: "authoring", mode: "authoring" };
4149
+ if (id.startsWith("mstar-"))
4150
+ return { kind: "runtime", mode: "runtime" };
4151
+ return { kind: "authoring", mode: "authoring" };
4152
+ }
3938
4153
  var RUNTIME_HEADING_ALIASES = {
3939
4154
  workflow: ["process", "playbook"],
3940
4155
  "decision-rules": [
@@ -4162,12 +4377,12 @@ function skillLocalConfig(config) {
4162
4377
  }
4163
4378
 
4164
4379
  // src/gates/catalog.ts
4165
- import { existsSync as existsSync13, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
4380
+ import { existsSync as existsSync14, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
4166
4381
  import { join as join8 } from "node:path";
4167
4382
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
4168
4383
 
4169
4384
  // src/gates/agent-flow.ts
4170
- import { appendFileSync, closeSync, existsSync as existsSync11, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync5, readSync, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4385
+ 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";
4171
4386
  import { join as join6 } from "node:path";
4172
4387
 
4173
4388
  // src/gates/dispatch.ts
@@ -4176,7 +4391,7 @@ import { basename as basename2, join as join5, resolve as resolve6 } from "node:
4176
4391
 
4177
4392
  // src/gates/workflow-selection.ts
4178
4393
  import { existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
4179
- import { dirname as dirname7, join as join3, relative as relative3 } from "node:path";
4394
+ import { dirname as dirname7, join as join3, relative as relative4 } from "node:path";
4180
4395
  var TERMINAL_STATUS_CACHE_MAX = 64;
4181
4396
  var terminalStatusCache = new Map;
4182
4397
  function terminalStatusOf(snapshotPath) {
@@ -4298,7 +4513,7 @@ function resolveReadWorkflow(harnessDir) {
4298
4513
  if (verdict === undefined || !verdict.terminal)
4299
4514
  continue;
4300
4515
  if (best === undefined || verdict.mtimeMs > best.mtimeMs) {
4301
- best = { workflowId: entry.name, dir: join3(relative3(harnessDir, workflowsDir), entry.name), mtimeMs: verdict.mtimeMs };
4516
+ best = { workflowId: entry.name, dir: join3(relative4(harnessDir, workflowsDir), entry.name), mtimeMs: verdict.mtimeMs };
4302
4517
  }
4303
4518
  }
4304
4519
  for (const cachedPath of terminalStatusCache.keys()) {
@@ -5315,7 +5530,7 @@ function ledgerContent(file, n) {
5315
5530
  }
5316
5531
  function readAgentFlow(workflowDir, limit) {
5317
5532
  const file = join6(workflowDir, AGENT_FLOW_FILE);
5318
- if (!existsSync11(file)) {
5533
+ if (!existsSync12(file)) {
5319
5534
  return { events: [], summary: [] };
5320
5535
  }
5321
5536
  const n = limit === undefined ? AGENT_FLOW_DEFAULT_LIMIT : Math.max(0, Math.floor(limit));
@@ -5570,7 +5785,7 @@ function harnessStateSource(harnessDir) {
5570
5785
  if (harnessDir === null)
5571
5786
  return null;
5572
5787
  const statusPath = join8(harnessDir, STATUS_FILE);
5573
- if (!existsSync13(statusPath))
5788
+ if (!existsSync14(statusPath))
5574
5789
  return null;
5575
5790
  try {
5576
5791
  const selection = resolveReadWorkflow(harnessDir);
@@ -5591,7 +5806,7 @@ function harnessStateSource(harnessDir) {
5591
5806
  }
5592
5807
  const snapshotPath = join8(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
5593
5808
  let snapshot;
5594
- if (!existsSync13(snapshotPath)) {
5809
+ if (!existsSync14(snapshotPath)) {
5595
5810
  return selectionErrorState({
5596
5811
  kind: "error",
5597
5812
  code: "workflow.selection.snapshot-unreadable",
@@ -5683,7 +5898,7 @@ function selectionErrorState(selection, rollup, harnessDir, compass) {
5683
5898
  function projectRollupSource(harnessDir, residuals) {
5684
5899
  const milestones = [];
5685
5900
  const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
5686
- if (existsSync13(projectsDir)) {
5901
+ if (existsSync14(projectsDir)) {
5687
5902
  let entries;
5688
5903
  try {
5689
5904
  entries = readdirSync4(projectsDir, { withFileTypes: true });
@@ -5694,7 +5909,7 @@ function projectRollupSource(harnessDir, residuals) {
5694
5909
  if (!entry.isDirectory())
5695
5910
  continue;
5696
5911
  const roadmapPath = join8(projectsDir, entry.name, PROJECT_ROADMAP_FILE);
5697
- if (!existsSync13(roadmapPath))
5912
+ if (!existsSync14(roadmapPath))
5698
5913
  continue;
5699
5914
  try {
5700
5915
  const doc = parseCompassFrontmatterText(readFileSync8(roadmapPath, "utf8"), roadmapPath);
@@ -5759,7 +5974,7 @@ function residualRollup(harnessDir) {
5759
5974
  }
5760
5975
  function projectRegisters(harnessDir) {
5761
5976
  const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
5762
- if (!existsSync13(projectsDir))
5977
+ if (!existsSync14(projectsDir))
5763
5978
  return [];
5764
5979
  let entries;
5765
5980
  try {
@@ -5772,7 +5987,7 @@ function projectRegisters(harnessDir) {
5772
5987
  if (!entry.isDirectory())
5773
5988
  continue;
5774
5989
  const registerPath = join8(projectsDir, entry.name, PROJECT_REGISTER_FILE);
5775
- if (!existsSync13(registerPath))
5990
+ if (!existsSync14(registerPath))
5776
5991
  continue;
5777
5992
  try {
5778
5993
  registers.push({ projectId: entry.name, doc: readJson(registerPath) });
@@ -5784,7 +5999,7 @@ function projectRegisters(harnessDir) {
5784
5999
  }
5785
6000
  function knowledgeDigest(harnessDir) {
5786
6001
  const indexPath = join8(harnessDir, "knowledge", "README.md");
5787
- if (!existsSync13(indexPath))
6002
+ if (!existsSync14(indexPath))
5788
6003
  return null;
5789
6004
  try {
5790
6005
  const categories = new Set;
@@ -5829,7 +6044,7 @@ function compassDirection(compassPath) {
5829
6044
  }
5830
6045
  function steeringCompassPath(harnessDir) {
5831
6046
  const iterationsDir = resolveIterationDir(harnessDir);
5832
- if (!existsSync13(iterationsDir))
6047
+ if (!existsSync14(iterationsDir))
5833
6048
  return;
5834
6049
  let entries;
5835
6050
  try {
@@ -5841,7 +6056,7 @@ function steeringCompassPath(harnessDir) {
5841
6056
  if (!entry.isDirectory())
5842
6057
  continue;
5843
6058
  const compassPath = join8(iterationsDir, entry.name, "delivery-compass.md");
5844
- if (!existsSync13(compassPath))
6059
+ if (!existsSync14(compassPath))
5845
6060
  continue;
5846
6061
  let content;
5847
6062
  try {
@@ -5860,7 +6075,7 @@ function iterationGateSource(harnessDir) {
5860
6075
  if (harnessDir === null)
5861
6076
  return;
5862
6077
  const statusPath = join8(harnessDir, STATUS_FILE);
5863
- if (!existsSync13(statusPath))
6078
+ if (!existsSync14(statusPath))
5864
6079
  return;
5865
6080
  const compass = steeringCompassPath(harnessDir);
5866
6081
  if (compass === undefined)
@@ -5922,15 +6137,15 @@ function agentDigestKey(agent, cwd) {
5922
6137
  }
5923
6138
 
5924
6139
  // src/gates/status.ts
5925
- import { existsSync as existsSync14, readdirSync as readdirSync7 } from "node:fs";
5926
- import { basename as basename5, join as join11, relative as relative5, resolve as resolve10 } from "node:path";
6140
+ import { existsSync as existsSync15, readdirSync as readdirSync7 } from "node:fs";
6141
+ import { basename as basename5, join as join12, relative as relative7, resolve as resolve10 } from "node:path";
5927
6142
  var LOGGER_NAME = "mstar/status-gate";
5928
6143
  function harnessDocKindOfTarget(harnessDir, targetPath) {
5929
6144
  const resolved = resolve10(targetPath);
5930
6145
  const name = basename5(resolved);
5931
6146
  if (name !== STATUS_FILE && name !== WORKFLOW_SNAPSHOT_FILE && name !== PROJECT_REGISTER_FILE)
5932
6147
  return null;
5933
- const rel = relative5(harnessDir, resolved);
6148
+ const rel = relative7(harnessDir, resolved);
5934
6149
  if (name === STATUS_FILE && rel === STATUS_FILE)
5935
6150
  return "status";
5936
6151
  let workflowDir;
@@ -5939,12 +6154,12 @@ function harnessDocKindOfTarget(harnessDir, targetPath) {
5939
6154
  workflowDir = resolveWorkflowDir(harnessDir, { harnessDir });
5940
6155
  projectDir = resolveProjectDir(harnessDir, { harnessDir });
5941
6156
  } catch {
5942
- workflowDir = join11(harnessDir, "workflows");
5943
- projectDir = join11(harnessDir, "projects");
6157
+ workflowDir = join12(harnessDir, "workflows");
6158
+ projectDir = join12(harnessDir, "projects");
5944
6159
  }
5945
- if (name === WORKFLOW_SNAPSHOT_FILE && /^[^/]+\/snapshot\.json$/.test(relative5(workflowDir, resolved)))
6160
+ if (name === WORKFLOW_SNAPSHOT_FILE && /^[^/]+\/snapshot\.json$/.test(relative7(workflowDir, resolved)))
5946
6161
  return "snapshot";
5947
- if (name === PROJECT_REGISTER_FILE && /^[^/]+\/residuals\.json$/.test(relative5(projectDir, resolved)))
6162
+ if (name === PROJECT_REGISTER_FILE && /^[^/]+\/residuals\.json$/.test(relative7(projectDir, resolved)))
5948
6163
  return "register";
5949
6164
  return null;
5950
6165
  }
@@ -6004,8 +6219,8 @@ function projectRegisterDocs(harnessDir) {
6004
6219
  for (const entry of entries) {
6005
6220
  if (!entry.isDirectory())
6006
6221
  continue;
6007
- const registerPath = join11(projectsDir, entry.name, PROJECT_REGISTER_FILE);
6008
- if (!existsSync14(registerPath))
6222
+ const registerPath = join12(projectsDir, entry.name, PROJECT_REGISTER_FILE);
6223
+ if (!existsSync15(registerPath))
6009
6224
  continue;
6010
6225
  try {
6011
6226
  registers.push({ projectId: entry.name, doc: readJson(registerPath) });
@@ -6078,8 +6293,8 @@ async function editIntentListener(ctx, resolver, config, adapter, target, actor,
6078
6293
  }
6079
6294
 
6080
6295
  // src/gates/skill-lint.ts
6081
- import { existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
6082
- import { basename as basename7, dirname as dirname9, resolve as resolve13, sep as sep2 } from "node:path";
6296
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
6297
+ import { basename as basename8, dirname as dirname9, resolve as resolve12, sep as sep2 } from "node:path";
6083
6298
  var SKILL_LINT_LOGGER = "mstar/skill-lint";
6084
6299
 
6085
6300
  class SkillLintVetoError extends Error {
@@ -6093,17 +6308,6 @@ ${violations.map(formatViolation).join(`
6093
6308
  this.violations = violations;
6094
6309
  }
6095
6310
  }
6096
- function stripFrontmatter(text) {
6097
- const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
6098
- if (lines.length === 0 || lines[0].trim() !== "---")
6099
- return text;
6100
- for (let i = 1;i < lines.length; i++) {
6101
- if (lines[i].trim() === "---")
6102
- return lines.slice(i + 1).join(`
6103
- `);
6104
- }
6105
- return text;
6106
- }
6107
6311
  var EPHEMERAL_CODES = {
6108
6312
  "task-artifact": "skill.ephemeral.task-artifact",
6109
6313
  "sdd-deeplink": "skill.ephemeral.sdd-deeplink"
@@ -6116,19 +6320,23 @@ function ephemeralViolation(citation) {
6116
6320
  message: `ephemeral citation \`${citation.match}\` on line ${citation.line} — durable skill text must reference in-repo artifacts only (knowledge conventions/skill-content-porting-discipline.md §3)`
6117
6321
  };
6118
6322
  }
6119
- function lintSkillDoc(doc) {
6323
+ function lintSkillDoc(doc, options = {}) {
6120
6324
  const violations = [];
6121
6325
  const frontmatter = lintSkillFrontmatter(doc);
6122
6326
  if (!frontmatter.ok)
6123
6327
  violations.push(...frontmatter.violations);
6124
- const body = lintFiveQuestion(stripFrontmatter(doc));
6125
- if (!body.ok)
6126
- violations.push(...body.violations);
6328
+ const profile = classifySkillLint(options.skillId);
6329
+ if (profile.mode !== null) {
6330
+ const body = lintFiveQuestion(stripFrontmatter(doc), profile.mode);
6331
+ if (!body.ok)
6332
+ violations.push(...body.violations);
6333
+ }
6127
6334
  violations.push(...findEphemeralCitations(doc).map(ephemeralViolation));
6128
6335
  return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
6129
6336
  }
6130
6337
  function lintSkillWrite(doc, options) {
6131
- const result = lintSkillDoc(doc);
6338
+ const skillId = basename8(dirname9(resolve12(options.target)));
6339
+ const result = lintSkillDoc(doc, { skillId });
6132
6340
  if (options.hard && !result.ok) {
6133
6341
  throw new SkillLintVetoError(options.target, result.violations);
6134
6342
  }
@@ -6140,13 +6348,13 @@ function skillRootsOf(config) {
6140
6348
  return roots.map((root) => root.trim()).filter((root) => root !== "");
6141
6349
  }
6142
6350
  function isSkillTarget(roots, target) {
6143
- if (basename7(target.displayPath) !== "SKILL.md")
6351
+ if (basename8(target.displayPath) !== "SKILL.md")
6144
6352
  return false;
6145
- const resolvedPath = resolve13(target.displayPath);
6146
- return roots.some((root) => resolvedPath.startsWith(resolve13(root) + sep2));
6353
+ const resolvedPath = resolve12(target.displayPath);
6354
+ return roots.some((root) => resolvedPath.startsWith(resolve12(root) + sep2));
6147
6355
  }
6148
6356
  function skillNameOf(target) {
6149
- return basename7(dirname9(target.displayPath));
6357
+ return basename8(dirname9(target.displayPath));
6150
6358
  }
6151
6359
  function skillCanonicalForm(target) {
6152
6360
  return resolveSkillRoot("dsh", { skill: skillNameOf(target), rel: "SKILL.md" });
@@ -6158,8 +6366,8 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
6158
6366
  return;
6159
6367
  if (!isSkillTarget(roots, target))
6160
6368
  return;
6161
- const skillPath = resolve13(target.displayPath);
6162
- if (!existsSync15(skillPath))
6369
+ const skillPath = resolve12(target.displayPath);
6370
+ if (!existsSync16(skillPath))
6163
6371
  return;
6164
6372
  let doc;
6165
6373
  try {
@@ -6176,7 +6384,7 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
6176
6384
  });
6177
6385
  return;
6178
6386
  }
6179
- const result = lintSkillDoc(doc);
6387
+ const result = lintSkillDoc(doc, { skillId: basename8(dirname9(skillPath)) });
6180
6388
  if (result.ok)
6181
6389
  return;
6182
6390
  const hard = resolveSeamHard(harnessDir, config);
@@ -6210,8 +6418,8 @@ async function skillWriteIntentListener(ctx, resolver, config, target, actor, ne
6210
6418
  }
6211
6419
 
6212
6420
  // src/gates/seams.ts
6213
- import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as readdirSync10 } from "node:fs";
6214
- import { basename as basename8, dirname as dirname10, join as join15, resolve as resolve15, sep as sep3 } from "node:path";
6421
+ import { existsSync as existsSync17, readFileSync as readFileSync13, readdirSync as readdirSync10 } from "node:fs";
6422
+ import { basename as basename10, dirname as dirname10, join as join14, resolve as resolve14, sep as sep3 } from "node:path";
6215
6423
 
6216
6424
  // ../engine/dist/audit.js
6217
6425
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -6410,7 +6618,7 @@ var SEAM_LOGGERS = {
6410
6618
  roles: "mstar/roles-lint"
6411
6619
  };
6412
6620
  function isIndexFile(path) {
6413
- const base = basename8(path);
6621
+ const base = basename10(path);
6414
6622
  return base === "README.md" || base === "index.md";
6415
6623
  }
6416
6624
  function isMarkdownDoc(path) {
@@ -6419,7 +6627,7 @@ function isMarkdownDoc(path) {
6419
6627
  function isAuditPlanTarget(path) {
6420
6628
  if (!isMarkdownDoc(path))
6421
6629
  return false;
6422
- const segments = resolve15(path).split(sep3);
6630
+ const segments = resolve14(path).split(sep3);
6423
6631
  for (let i = 0;i < segments.length; i++) {
6424
6632
  if (segments[i] !== "plans")
6425
6633
  continue;
@@ -6430,7 +6638,7 @@ function isAuditPlanTarget(path) {
6430
6638
  return false;
6431
6639
  }
6432
6640
  function isRolesTarget(path) {
6433
- const segments = resolve15(path).split(sep3);
6641
+ const segments = resolve14(path).split(sep3);
6434
6642
  const idx = segments.indexOf("mstar-roles");
6435
6643
  if (idx === -1)
6436
6644
  return false;
@@ -6440,17 +6648,17 @@ function isRolesTarget(path) {
6440
6648
  return rest[0] === "SKILL.md" || rest[0] === "references" && rest[rest.length - 1].endsWith(".md");
6441
6649
  }
6442
6650
  function rolesDirOf(path) {
6443
- const segments = resolve15(path).split(sep3);
6651
+ const segments = resolve14(path).split(sep3);
6444
6652
  const idx = segments.indexOf("mstar-roles");
6445
6653
  if (idx === -1)
6446
6654
  return dirname10(path);
6447
- return idx === 0 ? sep3 : join15(sep3, ...segments.slice(1, idx + 1));
6655
+ return idx === 0 ? sep3 : join14(sep3, ...segments.slice(1, idx + 1));
6448
6656
  }
6449
6657
  function isSeamTarget(seam, harnessDir, target) {
6450
- const path = resolve15(target.displayPath);
6658
+ const path = resolve14(target.displayPath);
6451
6659
  switch (seam) {
6452
6660
  case "design-md": {
6453
- const base = basename8(path);
6661
+ const base = basename10(path);
6454
6662
  return base === "DESIGN.md" || base === "DESIGN.dark.md";
6455
6663
  }
6456
6664
  case "audit":
@@ -6458,7 +6666,7 @@ function isSeamTarget(seam, harnessDir, target) {
6458
6666
  case "compound": {
6459
6667
  if (harnessDir === null)
6460
6668
  return false;
6461
- return path.startsWith(resolve15(join15(harnessDir, "knowledge")) + sep3) && isMarkdownDoc(path);
6669
+ return path.startsWith(resolve14(join14(harnessDir, "knowledge")) + sep3) && isMarkdownDoc(path);
6462
6670
  }
6463
6671
  case "roles":
6464
6672
  return isRolesTarget(path);
@@ -6475,9 +6683,9 @@ function auditSecretsViolations(findings, file) {
6475
6683
  }
6476
6684
  function validateDesignDoc(doc, path) {
6477
6685
  const violations = [...validateDesignTokenFrontmatter(doc).violations];
6478
- const isDark = basename8(path) === "DESIGN.dark.md";
6479
- const sibling = join15(dirname10(path), isDark ? "DESIGN.md" : "DESIGN.dark.md");
6480
- if (existsSync16(sibling)) {
6686
+ const isDark = basename10(path) === "DESIGN.dark.md";
6687
+ const sibling = join14(dirname10(path), isDark ? "DESIGN.md" : "DESIGN.dark.md");
6688
+ if (existsSync17(sibling)) {
6481
6689
  const light = isDark ? readFileSync13(sibling, "utf8") : doc;
6482
6690
  const dark = isDark ? doc : readFileSync13(sibling, "utf8");
6483
6691
  violations.push(...assertLightDarkParity(light, dark).violations);
@@ -6503,8 +6711,8 @@ function validateRolesState(rolesDir, skillsRoot = dirname10(rolesDir)) {
6503
6711
  for (const entry of readdirSync10(skillsRoot, { withFileTypes: true })) {
6504
6712
  if (!entry.isDirectory() || !entry.name.startsWith("mstar-"))
6505
6713
  continue;
6506
- const skillFile = join15(skillsRoot, entry.name, "SKILL.md");
6507
- if (!existsSync16(skillFile))
6714
+ const skillFile = join14(skillsRoot, entry.name, "SKILL.md");
6715
+ if (!existsSync17(skillFile))
6508
6716
  continue;
6509
6717
  try {
6510
6718
  skillTexts[entry.name] = readFileSync13(skillFile, "utf8");
@@ -6537,8 +6745,8 @@ function gateSeamIntent(ctx, harnessDir, config, seam, target) {
6537
6745
  try {
6538
6746
  if (!isSeamTarget(seam, harnessDir, target))
6539
6747
  return;
6540
- const path = resolve15(target.displayPath);
6541
- if (!existsSync16(path))
6748
+ const path = resolve14(target.displayPath);
6749
+ if (!existsSync17(path))
6542
6750
  return;
6543
6751
  let doc;
6544
6752
  try {
@@ -6554,12 +6762,12 @@ function gateSeamIntent(ctx, harnessDir, config, seam, target) {
6554
6762
  const hard = resolveSeamHard(harnessDir, config);
6555
6763
  const verdict = applyEnforcement(result, { hard });
6556
6764
  if (verdict.hardBlocked) {
6557
- logger.error(`${basename8(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):
6765
+ logger.error(`${basename10(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):
6558
6766
  ${verdict.violations.map(formatViolation).join(`
6559
6767
  `)}`);
6560
6768
  emitSeamAdvisory(ctx, seam, target.displayPath, verdict, hard, { repair: true });
6561
6769
  } else {
6562
- logger.warn(`${basename8(path)} write to ${target.displayPath} (advisory):
6770
+ logger.warn(`${basename10(path)} write to ${target.displayPath} (advisory):
6563
6771
  ${verdict.violations.map(formatViolation).join(`
6564
6772
  `)}`);
6565
6773
  emitSeamAdvisory(ctx, seam, target.displayPath, verdict, hard);
@@ -6608,7 +6816,7 @@ function lintRolesWrite(doc, options) {
6608
6816
  }
6609
6817
 
6610
6818
  // src/gates/tools.ts
6611
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
6819
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
6612
6820
  import { join as join17, resolve as resolve16 } from "node:path";
6613
6821
  import { defineTool } from "@deepseek-ai/dsh-tools";
6614
6822
  var ITERATION_VIOLATION_SCHEMA = {
@@ -6774,9 +6982,9 @@ function registerSddIterationTools(ctx, resolver) {
6774
6982
  presentResult: (_args, _result) => ({ card: "generic", title: "Iteration gate evaluation" }),
6775
6983
  isConcurrencySafe: () => false,
6776
6984
  async execute(args) {
6777
- if (!existsSync17(args.snapshot_path))
6985
+ if (!existsSync18(args.snapshot_path))
6778
6986
  throw new Error(`workflow snapshot not found: ${args.snapshot_path}`);
6779
- if (!existsSync17(args.compass_path))
6987
+ if (!existsSync18(args.compass_path))
6780
6988
  throw new Error(`compass file not found: ${args.compass_path}`);
6781
6989
  const snapshotDoc = readJson(args.snapshot_path);
6782
6990
  const compassDoc = parseCompassFrontmatter(args.compass_path);
@@ -6831,7 +7039,7 @@ function registerSeamTools(ctx, resolver) {
6831
7039
  async execute(args) {
6832
7040
  const abs = resolve16(args.dir);
6833
7041
  const lightPath = join17(abs, "DESIGN.md");
6834
- if (!existsSync17(lightPath))
7042
+ if (!existsSync18(lightPath))
6835
7043
  throw new Error(`design file not found: ${lightPath}`);
6836
7044
  const light = readFileSync14(lightPath, "utf8");
6837
7045
  const result = validateDesignDoc(light, lightPath);
@@ -6884,7 +7092,7 @@ function registerSeamTools(ctx, resolver) {
6884
7092
  isConcurrencySafe: () => false,
6885
7093
  async execute(args) {
6886
7094
  const abs = resolve16(args.plan_path);
6887
- if (!existsSync17(abs))
7095
+ if (!existsSync18(abs))
6888
7096
  throw new Error(`plan file not found: ${abs}`);
6889
7097
  const text = readFileSync14(abs, "utf8");
6890
7098
  const result = validateAuditDoc(text, abs);
@@ -6924,7 +7132,7 @@ function registerSeamTools(ctx, resolver) {
6924
7132
  isConcurrencySafe: () => false,
6925
7133
  async execute(args, exec) {
6926
7134
  const abs = resolve16(args.doc_path);
6927
- if (!existsSync17(abs))
7135
+ if (!existsSync18(abs))
6928
7136
  throw new Error(`knowledge doc not found: ${abs}`);
6929
7137
  const text = readFileSync14(abs, "utf8");
6930
7138
  const base = validateCompoundDoc(text, abs, args.repo_root !== undefined ? null : resolver.forAgent(exec.agent));
@@ -6969,7 +7177,7 @@ function registerSeamTools(ctx, resolver) {
6969
7177
  isConcurrencySafe: () => false,
6970
7178
  async execute(args) {
6971
7179
  const rolesDir = resolve16(args.roles_dir);
6972
- if (!existsSync17(join17(rolesDir, "SKILL.md")))
7180
+ if (!existsSync18(join17(rolesDir, "SKILL.md")))
6973
7181
  throw new Error(`roles dir not found: ${rolesDir}`);
6974
7182
  const result = validateRolesState(rolesDir, args.skills_root !== undefined ? resolve16(args.skills_root) : undefined);
6975
7183
  return { ok: result.ok, violations: result.violations.map(iterationViolationView) };
@@ -6979,8 +7187,8 @@ function registerSeamTools(ctx, resolver) {
6979
7187
  }
6980
7188
 
6981
7189
  // src/gates/adapter.ts
6982
- import { existsSync as existsSync18 } from "node:fs";
6983
- import { join as join18 } from "node:path";
7190
+ import { existsSync as existsSync19 } from "node:fs";
7191
+ import { join as join19 } from "node:path";
6984
7192
  import { Service as Service2 } from "@deepseek-ai/cordis";
6985
7193
  var HOST_LOGGER = "mstar/host-adapter";
6986
7194
  function mergeLeasesMatch(left, right) {
@@ -7016,7 +7224,7 @@ class DshHostAdapter extends Service2 {
7016
7224
  this.logSink(level, msg);
7017
7225
  }
7018
7226
  statusGate(path, kind, harnessDir) {
7019
- if (!existsSync18(path))
7227
+ if (!existsSync19(path))
7020
7228
  return { ok: true, violations: [] };
7021
7229
  return validateStatusDoc(path, kind, harnessDir);
7022
7230
  }
@@ -7071,7 +7279,7 @@ class DshHostAdapter extends Service2 {
7071
7279
  if (harnessDir !== null) {
7072
7280
  const selection = resolveActiveWorkflow(harnessDir);
7073
7281
  if (selection.kind === "active") {
7074
- const snapshotPath = join18(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
7282
+ const snapshotPath = join19(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
7075
7283
  try {
7076
7284
  const snapshot = readJson(snapshotPath);
7077
7285
  const stored = snapshot?.integration_merge_lease;
@@ -7098,7 +7306,7 @@ class DshHostAdapter extends Service2 {
7098
7306
 
7099
7307
  // src/gates/agent-personas.ts
7100
7308
  import { readFileSync as readFileSync15, readdirSync as readdirSync12, statSync as statSync7 } from "node:fs";
7101
- import { join as join19, resolve as resolve17, sep as sep5 } from "node:path";
7309
+ import { join as join20, resolve as resolve17, sep as sep5 } from "node:path";
7102
7310
  var ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
7103
7311
  var defaultCache = new Map;
7104
7312
  function personaFor(roleId, lookup, warn) {
@@ -7126,7 +7334,7 @@ function subagentRoleIds(agentsDir) {
7126
7334
  const roleId = name.slice(0, -3);
7127
7335
  let content;
7128
7336
  try {
7129
- content = readFileSync15(join19(agentsDir, name), "utf8");
7337
+ content = readFileSync15(join20(agentsDir, name), "utf8");
7130
7338
  } catch {
7131
7339
  continue;
7132
7340
  }
@@ -7237,6 +7445,8 @@ function unquote(value) {
7237
7445
  // src/gates/role-persona.ts
7238
7446
  var ROLE_PERSONA_LOGGER = "mstar/role-persona";
7239
7447
  var SUBAGENTS_SERVICE = "subagents";
7448
+ var PERSONA_SEAM_EVENT = "internal/get";
7449
+ var ROLE_PERSONA_WRAPPER_BRAND = Symbol("mstar.role-persona.wrapper");
7240
7450
  var rolePersonaLogSink = () => {};
7241
7451
  function setRolePersonaLogger(sink) {
7242
7452
  const prior = rolePersonaLogSink;
@@ -7254,7 +7464,7 @@ function setRolePersonaAgentsDir(dir) {
7254
7464
  var wrapperCache = new WeakMap;
7255
7465
  function registerRolePersonaChannel(ctx, config) {
7256
7466
  const rolePersonas = config.rolePersonas;
7257
- ctx.on("internal/get", (_readCtx, name, _error, next) => {
7467
+ ctx.on(PERSONA_SEAM_EVENT, (_readCtx, name, _error, next) => {
7258
7468
  const value = next();
7259
7469
  if (name !== SUBAGENTS_SERVICE)
7260
7470
  return value;
@@ -7266,6 +7476,47 @@ function registerRolePersonaChannel(ctx, config) {
7266
7476
  }
7267
7477
  });
7268
7478
  }
7479
+ var SEAM_WARN = `role persona channel not installed — cordis '${PERSONA_SEAM_EVENT}' seam missing or unrecognized (rolePersonas will not be merged into subagent starts)`;
7480
+ function evaluateSeamProbe({ dispatched, readError, value }) {
7481
+ if (!dispatched)
7482
+ return { ok: false, reason: "seam-absent" };
7483
+ if (readError !== undefined)
7484
+ return { ok: true, reason: "service-absent" };
7485
+ if (typeof value === "object" && value !== null && value[ROLE_PERSONA_WRAPPER_BRAND] === true) {
7486
+ return { ok: true };
7487
+ }
7488
+ return { ok: false, reason: "wrap-skipped" };
7489
+ }
7490
+ function probeRolePersonaSeam(ctx) {
7491
+ try {
7492
+ let dispatched = false;
7493
+ let readError;
7494
+ let value;
7495
+ const disposeCanary = ctx.on(PERSONA_SEAM_EVENT, (_readCtx, _name, _error, next) => {
7496
+ dispatched = true;
7497
+ return next();
7498
+ });
7499
+ try {
7500
+ value = ctx.subagents;
7501
+ } catch (error) {
7502
+ readError = error;
7503
+ } finally {
7504
+ disposeCanary();
7505
+ }
7506
+ const result = evaluateSeamProbe({ dispatched, readError, value });
7507
+ if (!result.ok) {
7508
+ log2("warn", `${SEAM_WARN} (reason: ${result.reason})`);
7509
+ } else if (result.reason === "service-absent") {
7510
+ log2("debug", `role persona seam probe: 'subagents' unresolved at apply (the apply ctx does not resolve it — cordis resolves the service in dispatch scopes, where reads are intercepted per read)`);
7511
+ }
7512
+ return result;
7513
+ } catch (error) {
7514
+ try {
7515
+ log2("debug", `role persona seam probe failed internally — fail-open (observation only): ${errorMessage2(error)}`);
7516
+ } catch {}
7517
+ return { ok: true };
7518
+ }
7519
+ }
7269
7520
  function wrapSubagentsService(value, rolePersonas) {
7270
7521
  if (typeof value !== "object" || value === null)
7271
7522
  return value;
@@ -7276,6 +7527,7 @@ function wrapSubagentsService(value, rolePersonas) {
7276
7527
  if (cached !== undefined && cached.rolePersonas === rolePersonas)
7277
7528
  return cached.wrapper;
7278
7529
  const wrapper = Object.create(value);
7530
+ Object.defineProperty(wrapper, ROLE_PERSONA_WRAPPER_BRAND, { value: true, enumerable: false });
7279
7531
  wrapper.start = (name, request) => {
7280
7532
  let merged = request;
7281
7533
  try {
@@ -7344,7 +7596,7 @@ function log2(level, message) {
7344
7596
 
7345
7597
  // src/gates/workflow-ledger.ts
7346
7598
  import { readFileSync as readFileSync16, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
7347
- import { join as join20 } from "node:path";
7599
+ import { join as join21 } from "node:path";
7348
7600
  var WORKFLOW_LEDGER_LOGGER = "mstar/workflow-ledger";
7349
7601
  var TOOL_WORKFLOW_RUN_START = "tool-workflow/run-start";
7350
7602
  var TOOL_WORKFLOW_AGENT_START = "tool-workflow/agent-start";
@@ -7388,7 +7640,7 @@ function loadWatermark(workflowDir, fresh = false) {
7388
7640
  return cached;
7389
7641
  const watermark = new Map;
7390
7642
  try {
7391
- const raw = readFileSync16(join20(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
7643
+ const raw = readFileSync16(join21(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
7392
7644
  const record = asRecord(JSON.parse(raw));
7393
7645
  const cursors = asRecord(record?.cursors);
7394
7646
  if (cursors !== undefined) {
@@ -7411,7 +7663,7 @@ function loadWatermark(workflowDir, fresh = false) {
7411
7663
  }
7412
7664
  function saveWatermark(workflowDir, watermark) {
7413
7665
  try {
7414
- const file = join20(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE);
7666
+ const file = join21(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE);
7415
7667
  const tmp = `${file}.tmp`;
7416
7668
  writeFileSync5(tmp, JSON.stringify({ v: 1, cursors: Object.fromEntries(watermark) }));
7417
7669
  renameSync3(tmp, file);
@@ -7627,8 +7879,8 @@ function registerWorkflowLedger(ctx, resolver, workflowAskCache) {
7627
7879
  }
7628
7880
 
7629
7881
  // src/gates/goal-bridge.ts
7630
- import { existsSync as existsSync19, readFileSync as readFileSync17, readdirSync as readdirSync13 } from "node:fs";
7631
- import { join as join21 } from "node:path";
7882
+ import { existsSync as existsSync20, readFileSync as readFileSync17, readdirSync as readdirSync13 } from "node:fs";
7883
+ import { join as join22 } from "node:path";
7632
7884
  var GOAL_BRIDGE_LOGGER = "mstar/goal-bridge";
7633
7885
  var DEFAULT_MAX_GOAL_ROUNDS = 256;
7634
7886
  var FLOW_SEQUENCE = "iteration-start → per-plan cycles → iteration-close → PR delivery → merge-ready";
@@ -7655,12 +7907,12 @@ function projectRegisterPointer(harnessDir) {
7655
7907
  for (const entry of readdirSync13(projectsDir, { withFileTypes: true })) {
7656
7908
  if (!entry.isDirectory())
7657
7909
  continue;
7658
- const registerPath = join21(projectsDir, entry.name, PROJECT_REGISTER_FILE);
7659
- if (existsSync19(registerPath))
7910
+ const registerPath = join22(projectsDir, entry.name, PROJECT_REGISTER_FILE);
7911
+ if (existsSync20(registerPath))
7660
7912
  return registerPath;
7661
7913
  }
7662
7914
  } catch {}
7663
- return join21(projectsDir, _DEFAULT_PROJECT, PROJECT_REGISTER_FILE);
7915
+ return join22(projectsDir, _DEFAULT_PROJECT, PROJECT_REGISTER_FILE);
7664
7916
  }
7665
7917
  function isRootLikeAgent(agent) {
7666
7918
  const header = agent?.session?.header;
@@ -7671,7 +7923,7 @@ function iterationGoalObjective(iterationId) {
7671
7923
  }
7672
7924
  function steeringCompass(harnessDir) {
7673
7925
  const iterationsDir = resolveIterationDir(harnessDir);
7674
- if (!existsSync19(iterationsDir))
7926
+ if (!existsSync20(iterationsDir))
7675
7927
  return;
7676
7928
  let entries;
7677
7929
  try {
@@ -7682,8 +7934,8 @@ function steeringCompass(harnessDir) {
7682
7934
  for (const entry of entries) {
7683
7935
  if (!entry.isDirectory())
7684
7936
  continue;
7685
- const compassPath = join21(iterationsDir, entry.name, "delivery-compass.md");
7686
- if (!existsSync19(compassPath))
7937
+ const compassPath = join22(iterationsDir, entry.name, "delivery-compass.md");
7938
+ if (!existsSync20(compassPath))
7687
7939
  continue;
7688
7940
  let content;
7689
7941
  try {
@@ -7863,8 +8115,8 @@ function registerGoalBridge(ctx, resolver, config) {
7863
8115
  }
7864
8116
 
7865
8117
  // src/gates/plan-mode-bridge.ts
7866
- import { existsSync as existsSync20 } from "node:fs";
7867
- import { join as join22 } from "node:path";
8118
+ import { existsSync as existsSync21 } from "node:fs";
8119
+ import { join as join23 } from "node:path";
7868
8120
  var PLAN_MODE_BRIDGE_LOGGER = "mstar/plan-mode-bridge";
7869
8121
  var PLAN_STATUS_TODO = "Todo";
7870
8122
  var planModeBridgeLogSink = () => {};
@@ -7880,13 +8132,13 @@ function errorMessage5(error) {
7880
8132
  return error instanceof Error ? error.message : String(error);
7881
8133
  }
7882
8134
  function hasPrepareWindow(harnessDir) {
7883
- const statusPath = join22(harnessDir, STATUS_FILE);
7884
- if (!existsSync20(statusPath))
8135
+ const statusPath = join23(harnessDir, STATUS_FILE);
8136
+ if (!existsSync21(statusPath))
7885
8137
  return false;
7886
8138
  const selection = resolveReadWorkflow(harnessDir);
7887
8139
  if (selection.kind === "error")
7888
8140
  return false;
7889
- const snapshotPath = join22(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
8141
+ const snapshotPath = join23(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
7890
8142
  let doc;
7891
8143
  try {
7892
8144
  doc = readJson(snapshotPath);
@@ -8057,6 +8309,10 @@ function setAdvisoryLogger(sink) {
8057
8309
  return prior;
8058
8310
  }
8059
8311
  var ADVISORY_ID_LIST_CAP = 20;
8312
+ var advisoryAbortWarned = false;
8313
+ function resetAdvisoryAbortWarn() {
8314
+ advisoryAbortWarned = false;
8315
+ }
8060
8316
  function capIdList(items) {
8061
8317
  if (items.length <= ADVISORY_ID_LIST_CAP)
8062
8318
  return items.join(", ");
@@ -8065,11 +8321,11 @@ function capIdList(items) {
8065
8321
  async function runFallbacksAdvisory(ctx, agentsDir) {
8066
8322
  try {
8067
8323
  if (!fallbacksMounted(ctx))
8068
- return false;
8324
+ return { ran: false, converged: false };
8069
8325
  const config = readRowConfig(ctx);
8070
8326
  if (config === undefined) {
8071
8327
  log6("debug", "fallbacks row config unreadable (absent or non-object) — adoption advisory skipped");
8072
- return true;
8328
+ return { ran: true, converged: true };
8073
8329
  }
8074
8330
  const service = fallbacksService(ctx);
8075
8331
  if (service !== undefined) {
@@ -8077,8 +8333,11 @@ async function runFallbacksAdvisory(ctx, agentsDir) {
8077
8333
  }
8078
8334
  return runStructuralAdvisory(config, agentsDir);
8079
8335
  } catch (error) {
8080
- log6("warn", `fallbacks adoption advisory aborted (degraded — warn-only, deployment config untouched): ${errorMessage7(error)}`);
8081
- return true;
8336
+ if (!advisoryAbortWarned) {
8337
+ advisoryAbortWarned = true;
8338
+ log6("warn", `fallbacks adoption advisory aborted (degraded — warn-only, deployment config untouched): ${errorMessage7(error)}`);
8339
+ }
8340
+ return { ran: true, converged: false };
8082
8341
  }
8083
8342
  }
8084
8343
  async function runSeedsAdvisory(service, config, agentsDir) {
@@ -8088,12 +8347,12 @@ async function runSeedsAdvisory(service, config, agentsDir) {
8088
8347
  }
8089
8348
  if (agentsDir === undefined) {
8090
8349
  log6("debug", "harness-agents mirror absent — fallbacks adoption taxonomy check skipped (config-only advisory)");
8091
- return true;
8350
+ return { ran: true, converged: true };
8092
8351
  }
8093
8352
  const mstarIds = subagentRoleIds(agentsDir);
8094
8353
  if (mstarIds.length === 0) {
8095
8354
  log6("debug", "harness-agents mirror has no subagent-mode shells — fallbacks adoption taxonomy check skipped");
8096
- return true;
8355
+ return { ran: true, converged: true };
8097
8356
  }
8098
8357
  const seedsLog = (_level, message) => log6("debug", message);
8099
8358
  const view = await declareMstarSeeds(service, { agentsDir, log: seedsLog });
@@ -8102,26 +8361,26 @@ async function runSeedsAdvisory(service, config, agentsDir) {
8102
8361
  readback = service.getEffectiveRoles();
8103
8362
  } catch (error) {
8104
8363
  log6("warn", `fallbacks effective-state readback failed — adoption state unavailable: ${errorMessage7(error)}`);
8105
- return true;
8364
+ return { ran: true, converged: true };
8106
8365
  }
8107
8366
  reportEffectiveState(mstarIds, readback);
8108
8367
  reportDeclareOutcome(view);
8109
- return true;
8368
+ return { ran: true, converged: true };
8110
8369
  }
8111
8370
  function runStructuralAdvisory(config, agentsDir) {
8112
8371
  if (agentsDir === undefined) {
8113
8372
  log6("debug", "harness-agents mirror absent — fallbacks adoption taxonomy check skipped (config-only advisory)");
8114
- return true;
8373
+ return { ran: true, converged: true };
8115
8374
  }
8116
8375
  const mstarIds = subagentRoleIds(agentsDir);
8117
8376
  if (mstarIds.length === 0) {
8118
8377
  log6("debug", "harness-agents mirror has no subagent-mode shells — fallbacks adoption taxonomy check skipped");
8119
- return true;
8378
+ return { ran: true, converged: true };
8120
8379
  }
8121
8380
  const list = readRolesList(config);
8122
8381
  if (list === undefined) {
8123
8382
  log6("debug", "fallbacks roles block not structurally readable (roles.list absent or non-array) — adoption advisory skipped");
8124
- return true;
8383
+ return { ran: true, converged: true };
8125
8384
  }
8126
8385
  const declared = new Map;
8127
8386
  for (let index = 0;index < list.length; index++) {
@@ -8146,7 +8405,7 @@ function runStructuralAdvisory(config, agentsDir) {
8146
8405
  if (emptyPersona.length > 0) {
8147
8406
  log6("warn", `fallbacks roles.list declares roles with an empty persona: ${capIdList(emptyPersona)} — declare a persona (or remove the role)`);
8148
8407
  }
8149
- return true;
8408
+ return { ran: true, converged: true };
8150
8409
  }
8151
8410
  function reportEffectiveState(mstarIds, readback) {
8152
8411
  const byId = new Map;
@@ -8363,14 +8622,22 @@ var inject = ["loader"];
8363
8622
  function packagedCommandsDir() {
8364
8623
  try {
8365
8624
  const dir = fileURLToPath3(new URL("../harness-commands", import.meta.url));
8366
- return existsSync21(dir) ? dir : undefined;
8625
+ return existsSync22(dir) ? dir : undefined;
8367
8626
  } catch {
8368
8627
  return;
8369
8628
  }
8370
8629
  }
8630
+ var FRONTMATTER_FIELD_RE = /^[ \t]*:[ \t]*(.+)$/;
8371
8631
  function commandFrontmatterField(frontmatter, label) {
8372
- const match = new RegExp(`^${label}[ \\t]*:[ \\t]*(.+)$`, "m").exec(frontmatter);
8373
- return match?.[1]?.trim();
8632
+ for (const line of frontmatter.split(`
8633
+ `)) {
8634
+ if (!line.startsWith(label))
8635
+ continue;
8636
+ const match = FRONTMATTER_FIELD_RE.exec(line.slice(label.length));
8637
+ if (match)
8638
+ return match[1].trim();
8639
+ }
8640
+ return;
8374
8641
  }
8375
8642
  function unquote2(value) {
8376
8643
  return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
@@ -8411,7 +8678,7 @@ function registerMstarCommands(ctx) {
8411
8678
  for (const file of readdirSync14(dir).sort()) {
8412
8679
  if (!file.endsWith(".md"))
8413
8680
  continue;
8414
- const parsed = parseCommandMarkdown(readFileSync18(join23(dir, file), "utf8"));
8681
+ const parsed = parseCommandMarkdown(readFileSync18(join24(dir, file), "utf8"));
8415
8682
  if (parsed === undefined)
8416
8683
  continue;
8417
8684
  commandsCtx.commands.register({
@@ -8477,6 +8744,7 @@ function apply(ctx, config) {
8477
8744
  else
8478
8745
  logger.warn(message);
8479
8746
  });
8747
+ resetAdvisoryAbortWarn();
8480
8748
  let advisoryPassed = false;
8481
8749
  let advisoryPassInFlight = false;
8482
8750
  let advisoryGeneration = 0;
@@ -8485,8 +8753,8 @@ function apply(ctx, config) {
8485
8753
  return;
8486
8754
  advisoryPassInFlight = true;
8487
8755
  const generation = advisoryGeneration;
8488
- runFallbacksAdvisory(ctx, packagedAgentsDir()).then((ran) => {
8489
- if (ran && generation === advisoryGeneration)
8756
+ runFallbacksAdvisory(ctx, packagedAgentsDir()).then((report) => {
8757
+ if (report.ran && report.converged && generation === advisoryGeneration)
8490
8758
  advisoryPassed = true;
8491
8759
  }).finally(() => {
8492
8760
  advisoryPassInFlight = false;
@@ -8500,23 +8768,43 @@ function apply(ctx, config) {
8500
8768
  const service = fallbacksService(serviceCtx);
8501
8769
  if (service === undefined)
8502
8770
  return;
8503
- declareMstarSeeds(service, {
8504
- agentsDir: packagedAgentsDir(),
8505
- log: (level, message) => {
8506
- const logger = ctx.logger(SEEDS_LOGGER);
8507
- if (level === "warn")
8508
- logger.warn(message);
8509
- else if (level === "error")
8510
- logger.error(message);
8511
- else
8512
- logger.debug(message);
8513
- }
8514
- }).catch((error) => {
8515
- ctx.logger(SEEDS_LOGGER).error(`mstar seeds declaration failed (contained — fallbacks taxonomy unchanged): ${error instanceof Error ? error.message : String(error)}`);
8516
- });
8771
+ let disposed = false;
8772
+ const retryTimers = [];
8773
+ const declareAttempt = (attempt) => {
8774
+ declareMstarSeeds(service, {
8775
+ agentsDir: packagedAgentsDir(),
8776
+ log: (level, message) => {
8777
+ const logger = ctx.logger(SEEDS_LOGGER);
8778
+ if (level === "warn")
8779
+ logger.warn(message);
8780
+ else if (level === "error")
8781
+ logger.error(message);
8782
+ else
8783
+ logger.debug(message);
8784
+ }
8785
+ }).catch((error) => {
8786
+ if (disposed)
8787
+ return;
8788
+ const message = error instanceof Error ? error.message : String(error);
8789
+ if (attempt >= 3) {
8790
+ ctx.logger(SEEDS_LOGGER).error(`mstar seeds declaration failed (contained — fallbacks taxonomy unchanged): ${message}`);
8791
+ return;
8792
+ }
8793
+ ctx.logger(SEEDS_LOGGER).debug(`seeds declare attempt ${attempt}/3 failed: ${message}`);
8794
+ retryTimers.push(setTimeout(() => {
8795
+ if (!disposed)
8796
+ declareAttempt(attempt + 1);
8797
+ }, attempt === 1 ? 50 : 250));
8798
+ });
8799
+ };
8800
+ declareAttempt(1);
8517
8801
  return () => {
8802
+ disposed = true;
8803
+ for (const timer of retryTimers)
8804
+ clearTimeout(timer);
8518
8805
  advisoryPassed = false;
8519
8806
  advisoryGeneration += 1;
8807
+ resetAdvisoryAbortWarn();
8520
8808
  };
8521
8809
  });
8522
8810
  registerSettleListener(ctx, config, pairing);
@@ -8575,6 +8863,7 @@ function apply(ctx, config) {
8575
8863
  }
8576
8864
  ctx.on("tools/pre-execute", (exec, next) => preExecuteListener(ctx, resolver, config, adapter, exec, next), { prepend: true });
8577
8865
  registerRolePersonaChannel(ctx, config);
8866
+ probeRolePersonaSeam(ctx);
8578
8867
  ctx.events.on("subagent/start", () => {
8579
8868
  runAdvisoryPass();
8580
8869
  });