maxsimcli 3.11.0 → 4.0.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 (197) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapters/index.d.ts +0 -11
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +4 -40
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/assets/CHANGELOG.md +36 -0
  7. package/dist/assets/dashboard/client/assets/{index-CZ8WC97G.js → index-C_eAetZJ.js} +66 -66
  8. package/dist/assets/dashboard/client/assets/index-CmiJKqOU.css +32 -0
  9. package/dist/assets/dashboard/client/index.html +2 -2
  10. package/dist/assets/dashboard/server.js +467 -271
  11. package/dist/assets/templates/agents/AGENTS.md +94 -0
  12. package/dist/assets/templates/agents/maxsim-debugger.md +2 -2
  13. package/dist/assets/templates/agents/maxsim-executor.md +5 -5
  14. package/dist/assets/templates/agents/maxsim-phase-researcher.md +2 -2
  15. package/dist/assets/templates/agents/maxsim-plan-checker.md +2 -2
  16. package/dist/assets/templates/agents/maxsim-planner.md +3 -3
  17. package/dist/assets/templates/commands/maxsim/add-todo.md +15 -5
  18. package/dist/assets/templates/commands/maxsim/discuss-phase.md +1 -0
  19. package/dist/assets/templates/commands/maxsim/init-existing.md +4 -0
  20. package/dist/assets/templates/commands/maxsim/new-project.md +4 -0
  21. package/dist/assets/templates/commands/maxsim/settings.md +1 -1
  22. package/dist/assets/templates/references/thinking-partner.md +41 -0
  23. package/dist/assets/templates/skills/batch-worktree/SKILL.md +137 -0
  24. package/dist/assets/templates/skills/brainstorming/SKILL.md +159 -0
  25. package/dist/assets/templates/skills/code-review/SKILL.md +151 -0
  26. package/dist/assets/templates/skills/memory-management/SKILL.md +174 -0
  27. package/dist/assets/templates/skills/roadmap-writing/SKILL.md +198 -0
  28. package/dist/assets/templates/skills/sdd/SKILL.md +175 -0
  29. package/dist/assets/templates/skills/simplify/SKILL.md +185 -0
  30. package/dist/assets/templates/skills/using-maxsim/SKILL.md +120 -0
  31. package/dist/assets/templates/templates/acceptance-criteria.md +10 -0
  32. package/dist/assets/templates/templates/config.json +1 -1
  33. package/dist/assets/templates/templates/decisions.md +10 -0
  34. package/dist/assets/templates/templates/no-gos.md +9 -0
  35. package/dist/assets/templates/workflows/add-tests.md +3 -3
  36. package/dist/assets/templates/workflows/add-todo.md +89 -0
  37. package/dist/assets/templates/workflows/complete-milestone.md +1 -1
  38. package/dist/assets/templates/workflows/discuss-phase.md +85 -1
  39. package/dist/assets/templates/workflows/execute-phase.md +26 -16
  40. package/dist/assets/templates/workflows/execute-plan.md +166 -0
  41. package/dist/assets/templates/workflows/init-existing.md +123 -3
  42. package/dist/assets/templates/workflows/new-milestone.md +4 -0
  43. package/dist/assets/templates/workflows/new-project.md +111 -3
  44. package/dist/assets/templates/workflows/plan-phase.md +5 -5
  45. package/dist/assets/templates/workflows/quick.md +2 -2
  46. package/dist/assets/templates/workflows/settings.md +8 -4
  47. package/dist/assets/templates/workflows/verify-work.md +1 -1
  48. package/dist/cli.cjs +1512 -1026
  49. package/dist/cli.cjs.map +1 -1
  50. package/dist/cli.js +170 -278
  51. package/dist/cli.js.map +1 -1
  52. package/dist/core/artefakte.d.ts +12 -0
  53. package/dist/core/artefakte.d.ts.map +1 -0
  54. package/dist/core/artefakte.js +136 -0
  55. package/dist/core/artefakte.js.map +1 -0
  56. package/dist/core/commands.d.ts +13 -13
  57. package/dist/core/commands.d.ts.map +1 -1
  58. package/dist/core/commands.js +48 -58
  59. package/dist/core/commands.js.map +1 -1
  60. package/dist/core/config.d.ts +4 -3
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +14 -18
  63. package/dist/core/config.js.map +1 -1
  64. package/dist/core/context-loader.d.ts +20 -0
  65. package/dist/core/context-loader.d.ts.map +1 -0
  66. package/dist/core/context-loader.js +154 -0
  67. package/dist/core/context-loader.js.map +1 -0
  68. package/dist/core/core.d.ts +26 -2
  69. package/dist/core/core.d.ts.map +1 -1
  70. package/dist/core/core.js +90 -24
  71. package/dist/core/core.js.map +1 -1
  72. package/dist/core/dashboard-launcher.d.ts +56 -0
  73. package/dist/core/dashboard-launcher.d.ts.map +1 -0
  74. package/dist/core/dashboard-launcher.js +246 -0
  75. package/dist/core/dashboard-launcher.js.map +1 -0
  76. package/dist/core/frontmatter.d.ts +5 -5
  77. package/dist/core/frontmatter.d.ts.map +1 -1
  78. package/dist/core/frontmatter.js +21 -26
  79. package/dist/core/frontmatter.js.map +1 -1
  80. package/dist/core/index.d.ts +10 -3
  81. package/dist/core/index.d.ts.map +1 -1
  82. package/dist/core/index.js +40 -2
  83. package/dist/core/index.js.map +1 -1
  84. package/dist/core/init.d.ts +14 -15
  85. package/dist/core/init.d.ts.map +1 -1
  86. package/dist/core/init.js +93 -155
  87. package/dist/core/init.js.map +1 -1
  88. package/dist/core/milestone.d.ts +3 -3
  89. package/dist/core/milestone.d.ts.map +1 -1
  90. package/dist/core/milestone.js +9 -9
  91. package/dist/core/milestone.js.map +1 -1
  92. package/dist/core/phase.d.ts +9 -9
  93. package/dist/core/phase.d.ts.map +1 -1
  94. package/dist/core/phase.js +65 -63
  95. package/dist/core/phase.js.map +1 -1
  96. package/dist/core/roadmap.d.ts +4 -3
  97. package/dist/core/roadmap.d.ts.map +1 -1
  98. package/dist/core/roadmap.js +46 -108
  99. package/dist/core/roadmap.js.map +1 -1
  100. package/dist/core/skills.d.ts +19 -0
  101. package/dist/core/skills.d.ts.map +1 -0
  102. package/dist/core/skills.js +145 -0
  103. package/dist/core/skills.js.map +1 -0
  104. package/dist/core/start.d.ts +15 -0
  105. package/dist/core/start.d.ts.map +1 -0
  106. package/dist/core/start.js +80 -0
  107. package/dist/core/start.js.map +1 -0
  108. package/dist/core/state.d.ts +13 -13
  109. package/dist/core/state.d.ts.map +1 -1
  110. package/dist/core/state.js +125 -130
  111. package/dist/core/state.js.map +1 -1
  112. package/dist/core/template.d.ts +3 -3
  113. package/dist/core/template.d.ts.map +1 -1
  114. package/dist/core/template.js +12 -14
  115. package/dist/core/template.js.map +1 -1
  116. package/dist/core/types.d.ts +15 -4
  117. package/dist/core/types.d.ts.map +1 -1
  118. package/dist/core/types.js +9 -2
  119. package/dist/core/types.js.map +1 -1
  120. package/dist/core/verify.d.ts +10 -9
  121. package/dist/core/verify.d.ts.map +1 -1
  122. package/dist/core/verify.js +38 -48
  123. package/dist/core/verify.js.map +1 -1
  124. package/dist/core-TFSlUjV1.cjs +4312 -0
  125. package/dist/core-TFSlUjV1.cjs.map +1 -0
  126. package/dist/install/adapters.d.ts +6 -0
  127. package/dist/install/adapters.d.ts.map +1 -0
  128. package/dist/install/adapters.js +65 -0
  129. package/dist/install/adapters.js.map +1 -0
  130. package/dist/install/copy.d.ts +6 -0
  131. package/dist/install/copy.d.ts.map +1 -0
  132. package/dist/install/copy.js +71 -0
  133. package/dist/install/copy.js.map +1 -0
  134. package/dist/install/dashboard.d.ts +16 -0
  135. package/dist/install/dashboard.d.ts.map +1 -0
  136. package/dist/install/dashboard.js +273 -0
  137. package/dist/install/dashboard.js.map +1 -0
  138. package/dist/install/hooks.d.ts +31 -0
  139. package/dist/install/hooks.d.ts.map +1 -0
  140. package/dist/install/hooks.js +260 -0
  141. package/dist/install/hooks.js.map +1 -0
  142. package/dist/install/index.d.ts +2 -0
  143. package/dist/install/index.d.ts.map +1 -0
  144. package/dist/install/index.js +535 -0
  145. package/dist/install/index.js.map +1 -0
  146. package/dist/install/manifest.d.ts +23 -0
  147. package/dist/install/manifest.d.ts.map +1 -0
  148. package/dist/install/manifest.js +129 -0
  149. package/dist/install/manifest.js.map +1 -0
  150. package/dist/install/patches.d.ts +10 -0
  151. package/dist/install/patches.d.ts.map +1 -0
  152. package/dist/install/patches.js +124 -0
  153. package/dist/install/patches.js.map +1 -0
  154. package/dist/install/shared.d.ts +56 -0
  155. package/dist/install/shared.d.ts.map +1 -0
  156. package/dist/install/shared.js +172 -0
  157. package/dist/install/shared.js.map +1 -0
  158. package/dist/install/uninstall.d.ts +5 -0
  159. package/dist/install/uninstall.d.ts.map +1 -0
  160. package/dist/install/uninstall.js +222 -0
  161. package/dist/install/uninstall.js.map +1 -0
  162. package/dist/install.cjs +793 -1648
  163. package/dist/install.cjs.map +1 -1
  164. package/dist/mcp-server.cjs +38 -14
  165. package/dist/mcp-server.cjs.map +1 -1
  166. package/dist/skills-BOSxYUzf.cjs +6812 -0
  167. package/dist/skills-BOSxYUzf.cjs.map +1 -0
  168. package/package.json +1 -1
  169. package/dist/adapters/codex.d.ts +0 -19
  170. package/dist/adapters/codex.d.ts.map +0 -1
  171. package/dist/adapters/codex.js +0 -94
  172. package/dist/adapters/codex.js.map +0 -1
  173. package/dist/adapters/gemini.d.ts +0 -19
  174. package/dist/adapters/gemini.d.ts.map +0 -1
  175. package/dist/adapters/gemini.js +0 -96
  176. package/dist/adapters/gemini.js.map +0 -1
  177. package/dist/adapters/opencode.d.ts +0 -17
  178. package/dist/adapters/opencode.d.ts.map +0 -1
  179. package/dist/adapters/opencode.js +0 -111
  180. package/dist/adapters/opencode.js.map +0 -1
  181. package/dist/adapters/transforms/content.d.ts +0 -39
  182. package/dist/adapters/transforms/content.d.ts.map +0 -1
  183. package/dist/adapters/transforms/content.js +0 -125
  184. package/dist/adapters/transforms/content.js.map +0 -1
  185. package/dist/adapters/transforms/frontmatter.d.ts +0 -42
  186. package/dist/adapters/transforms/frontmatter.d.ts.map +0 -1
  187. package/dist/adapters/transforms/frontmatter.js +0 -204
  188. package/dist/adapters/transforms/frontmatter.js.map +0 -1
  189. package/dist/adapters/transforms/tool-maps.d.ts +0 -20
  190. package/dist/adapters/transforms/tool-maps.d.ts.map +0 -1
  191. package/dist/adapters/transforms/tool-maps.js +0 -64
  192. package/dist/adapters/transforms/tool-maps.js.map +0 -1
  193. package/dist/assets/dashboard/client/assets/index-DzJChB-D.css +0 -32
  194. package/dist/install.d.ts +0 -2
  195. package/dist/install.d.ts.map +0 -1
  196. package/dist/install.js +0 -1841
  197. package/dist/install.js.map +0 -1
package/dist/cli.cjs CHANGED
@@ -31,10 +31,9 @@ let node_fs = require("node:fs");
31
31
  node_fs = __toESM(node_fs);
32
32
  let node_path = require("node:path");
33
33
  node_path = __toESM(node_path);
34
+ let node_child_process = require("node:child_process");
34
35
  let node_os = require("node:os");
35
36
  node_os = __toESM(node_os);
36
- let node_child_process = require("node:child_process");
37
- let node_module = require("node:module");
38
37
  let node_buffer = require("node:buffer");
39
38
  let child_process = require("child_process");
40
39
  let node_events = require("node:events");
@@ -42,8 +41,22 @@ let node_process = require("node:process");
42
41
  node_process = __toESM(node_process);
43
42
  let node_tty = require("node:tty");
44
43
  node_tty = __toESM(node_tty);
44
+ let node_module = require("node:module");
45
45
 
46
46
  //#region src/core/types.ts
47
+ function cmdOk(result, rawValue) {
48
+ return {
49
+ ok: true,
50
+ result,
51
+ rawValue
52
+ };
53
+ }
54
+ function cmdErr(error) {
55
+ return {
56
+ ok: false,
57
+ error
58
+ };
59
+ }
47
60
  const PLANNING_CONFIG_DEFAULTS = {
48
61
  model_profile: "balanced",
49
62
  commit_docs: true,
@@ -53,9 +66,8 @@ const PLANNING_CONFIG_DEFAULTS = {
53
66
  milestone_branch_template: "maxsim/{milestone}-{slug}",
54
67
  workflow: {
55
68
  research: true,
56
- plan_check: true,
57
- verifier: true,
58
- nyquist_validation: false
69
+ plan_checker: true,
70
+ verifier: true
59
71
  },
60
72
  parallelization: true,
61
73
  brave_search: false
@@ -594,7 +606,7 @@ var require_has_flag = /* @__PURE__ */ __commonJSMin(((exports, module) => {
594
606
  //#endregion
595
607
  //#region ../../node_modules/supports-color/index.js
596
608
  var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) => {
597
- const os$4 = require("os");
609
+ const os$5 = require("os");
598
610
  const tty$2 = require("tty");
599
611
  const hasFlag = require_has_flag();
600
612
  const { env } = process;
@@ -621,7 +633,7 @@ var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) =>
621
633
  const min = forceColor || 0;
622
634
  if (env.TERM === "dumb") return min;
623
635
  if (process.platform === "win32") {
624
- const osRelease = os$4.release().split(".");
636
+ const osRelease = os$5.release().split(".");
625
637
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
626
638
  return 1;
627
639
  }
@@ -4711,21 +4723,47 @@ const MODEL_PROFILES = {
4711
4723
  tokenburner: "opus"
4712
4724
  }
4713
4725
  };
4726
+ /** Thrown by output() to signal successful command completion. */
4727
+ var CliOutput = class {
4728
+ result;
4729
+ raw;
4730
+ rawValue;
4731
+ constructor(result, raw, rawValue) {
4732
+ this.result = result;
4733
+ this.raw = raw ?? false;
4734
+ this.rawValue = rawValue;
4735
+ }
4736
+ };
4737
+ /** Thrown by error() to signal a command error. */
4738
+ var CliError = class {
4739
+ message;
4740
+ constructor(message) {
4741
+ this.message = message;
4742
+ }
4743
+ };
4714
4744
  function output(result, raw, rawValue) {
4715
- if (raw && rawValue !== void 0) process.stdout.write(String(rawValue));
4745
+ throw new CliOutput(result, raw, rawValue);
4746
+ }
4747
+ function error(message) {
4748
+ throw new CliError(message);
4749
+ }
4750
+ /** Re-throw CliOutput/CliError signals so catch blocks don't intercept them */
4751
+ function rethrowCliSignals(e) {
4752
+ if (e instanceof CliOutput || e instanceof CliError) throw e;
4753
+ }
4754
+ /**
4755
+ * Handle a CliOutput by writing to stdout. Extracted so cli.ts can use it.
4756
+ */
4757
+ function writeOutput(out) {
4758
+ if (out.raw && out.rawValue !== void 0) process.stdout.write(String(out.rawValue));
4716
4759
  else {
4717
- const json = JSON.stringify(result, null, 2);
4760
+ const json = JSON.stringify(out.result, null, 2);
4718
4761
  if (json.length > 5e4) {
4719
4762
  const tmpPath = node_path.default.join(node_os.default.tmpdir(), `maxsim-${Date.now()}.json`);
4720
4763
  node_fs.default.writeFileSync(tmpPath, json, "utf-8");
4721
4764
  process.stdout.write("@file:" + tmpPath);
4722
4765
  } else process.stdout.write(json);
4723
4766
  }
4724
- process.exit(0);
4725
- }
4726
- function error(message) {
4727
- process.stderr.write("Error: " + message + "\n");
4728
- process.exit(1);
4729
4767
  }
4730
4768
  /** Today's date as YYYY-MM-DD. */
4731
4769
  function todayISO() {
@@ -4758,9 +4796,28 @@ function listSubDirs(dir, sortByPhase = false) {
4758
4796
  const dirs = node_fs.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4759
4797
  return sortByPhase ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
4760
4798
  }
4761
- /** Log only when MAXSIM_DEBUG is set. */
4762
- function debugLog(e) {
4763
- if (process.env.MAXSIM_DEBUG) console.error(e);
4799
+ /** Async version of listSubDirs using fs.promises. */
4800
+ async function listSubDirsAsync(dir, sortByPhase = false) {
4801
+ const dirs = (await node_fs.promises.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
4802
+ return sortByPhase ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
4803
+ }
4804
+ /** Async version of safeReadFile using fs.promises. */
4805
+ async function safeReadFileAsync(filePath) {
4806
+ try {
4807
+ return await node_fs.promises.readFile(filePath, "utf-8");
4808
+ } catch {
4809
+ return null;
4810
+ }
4811
+ }
4812
+ /** Extract a human-readable message from an unknown thrown value. */
4813
+ function errorMsg(e) {
4814
+ return e instanceof Error ? e.message : String(e);
4815
+ }
4816
+ /** Log only when MAXSIM_DEBUG is set. Accepts an optional context label. */
4817
+ function debugLog(contextOrError, error) {
4818
+ if (!process.env.MAXSIM_DEBUG) return;
4819
+ if (error !== void 0) console.error(`[maxsim:${contextOrError}]`, error);
4820
+ else console.error(contextOrError);
4764
4821
  }
4765
4822
  /** Escape a phase number for use in regex. */
4766
4823
  function escapePhaseNum(phase) {
@@ -4833,6 +4890,9 @@ function loadConfig(cwd) {
4833
4890
  field: "research"
4834
4891
  }) ?? defaults.research,
4835
4892
  plan_checker: get("plan_checker", {
4893
+ section: "workflow",
4894
+ field: "plan_checker"
4895
+ }) ?? get("plan_checker", {
4836
4896
  section: "workflow",
4837
4897
  field: "plan_check"
4838
4898
  }) ?? defaults.plan_checker,
@@ -4849,7 +4909,11 @@ function loadConfig(cwd) {
4849
4909
  config: result
4850
4910
  };
4851
4911
  return result;
4852
- } catch {
4912
+ } catch (e) {
4913
+ if (node_fs.default.existsSync(cfgPath)) {
4914
+ console.warn(`[maxsim] Warning: config.json exists but could not be parsed — using defaults.`);
4915
+ debugLog("config-load-failed", e);
4916
+ }
4853
4917
  _configCache = {
4854
4918
  cwd,
4855
4919
  config: defaults
@@ -4872,10 +4936,15 @@ async function execGit(cwd, args) {
4872
4936
  stderr: ""
4873
4937
  };
4874
4938
  } catch (thrown) {
4939
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
4940
+ debugLog("exec-git-failed", {
4941
+ args,
4942
+ error: message
4943
+ });
4875
4944
  return {
4876
4945
  exitCode: 1,
4877
4946
  stdout: "",
4878
- stderr: thrown.message ?? ""
4947
+ stderr: message
4879
4948
  };
4880
4949
  }
4881
4950
  }
@@ -4950,7 +5019,12 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
4950
5019
  has_context: hasContext,
4951
5020
  has_verification: hasVerification
4952
5021
  };
4953
- } catch {
5022
+ } catch (e) {
5023
+ debugLog("search-phase-in-dir-failed", {
5024
+ dir: baseDir,
5025
+ phase: normalized,
5026
+ error: errorMsg(e)
5027
+ });
4954
5028
  return null;
4955
5029
  }
4956
5030
  }
@@ -4979,7 +5053,7 @@ function findPhaseInternal(cwd, phase) {
4979
5053
  }
4980
5054
  }
4981
5055
  } catch (e) {
4982
- debugLog(e);
5056
+ debugLog("find-phase-milestone-search-failed", e);
4983
5057
  }
4984
5058
  return null;
4985
5059
  }
@@ -5002,7 +5076,7 @@ function getArchivedPhaseDirs(cwd) {
5002
5076
  });
5003
5077
  }
5004
5078
  } catch (e) {
5005
- debugLog(e);
5079
+ debugLog("get-archived-phase-dirs-failed", e);
5006
5080
  }
5007
5081
  return results;
5008
5082
  }
@@ -5028,7 +5102,11 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
5028
5102
  goal,
5029
5103
  section
5030
5104
  };
5031
- } catch {
5105
+ } catch (e) {
5106
+ debugLog("get-roadmap-phase-failed", {
5107
+ phase: phaseNum,
5108
+ error: errorMsg(e)
5109
+ });
5032
5110
  return null;
5033
5111
  }
5034
5112
  }
@@ -11804,39 +11882,30 @@ const FRONTMATTER_SCHEMAS = {
11804
11882
  "score"
11805
11883
  ] }
11806
11884
  };
11807
- function cmdFrontmatterGet(cwd, filePath, field, raw) {
11808
- if (!filePath) error("file path required");
11885
+ function cmdFrontmatterGet(cwd, filePath, field) {
11886
+ if (!filePath) return cmdErr("file path required");
11809
11887
  const content = safeReadFile(node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath));
11810
- if (!content) {
11811
- output({
11812
- error: "File not found",
11813
- path: filePath
11814
- }, raw);
11815
- return;
11816
- }
11888
+ if (!content) return cmdOk({
11889
+ error: "File not found",
11890
+ path: filePath
11891
+ });
11817
11892
  const fm = extractFrontmatter(content);
11818
11893
  if (field) {
11819
11894
  const value = fm[field];
11820
- if (value === void 0) {
11821
- output({
11822
- error: "Field not found",
11823
- field
11824
- }, raw);
11825
- return;
11826
- }
11827
- output({ [field]: value }, raw, JSON.stringify(value));
11828
- } else output(fm, raw);
11895
+ if (value === void 0) return cmdOk({
11896
+ error: "Field not found",
11897
+ field
11898
+ });
11899
+ return cmdOk({ [field]: value }, JSON.stringify(value));
11900
+ } else return cmdOk(fm);
11829
11901
  }
11830
- function cmdFrontmatterSet(cwd, filePath, field, value, raw) {
11831
- if (!filePath || !field || value === void 0) error("file, field, and value required");
11902
+ function cmdFrontmatterSet(cwd, filePath, field, value) {
11903
+ if (!filePath || !field || value === void 0) return cmdErr("file, field, and value required");
11832
11904
  const fullPath = node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath);
11833
- if (!node_fs.default.existsSync(fullPath)) {
11834
- output({
11835
- error: "File not found",
11836
- path: filePath
11837
- }, raw);
11838
- return;
11839
- }
11905
+ if (!node_fs.default.existsSync(fullPath)) return cmdOk({
11906
+ error: "File not found",
11907
+ path: filePath
11908
+ });
11840
11909
  const content = node_fs.default.readFileSync(fullPath, "utf-8");
11841
11910
  const fm = extractFrontmatter(content);
11842
11911
  let parsedValue;
@@ -11848,60 +11917,53 @@ function cmdFrontmatterSet(cwd, filePath, field, value, raw) {
11848
11917
  fm[field] = parsedValue;
11849
11918
  const newContent = spliceFrontmatter(content, fm);
11850
11919
  node_fs.default.writeFileSync(fullPath, newContent, "utf-8");
11851
- output({
11920
+ return cmdOk({
11852
11921
  updated: true,
11853
11922
  field,
11854
11923
  value: parsedValue
11855
- }, raw, "true");
11924
+ }, "true");
11856
11925
  }
11857
- function cmdFrontmatterMerge(cwd, filePath, data, raw) {
11858
- if (!filePath || !data) error("file and data required");
11926
+ function cmdFrontmatterMerge(cwd, filePath, data) {
11927
+ if (!filePath || !data) return cmdErr("file and data required");
11859
11928
  const fullPath = node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath);
11860
- if (!node_fs.default.existsSync(fullPath)) {
11861
- output({
11862
- error: "File not found",
11863
- path: filePath
11864
- }, raw);
11865
- return;
11866
- }
11929
+ if (!node_fs.default.existsSync(fullPath)) return cmdOk({
11930
+ error: "File not found",
11931
+ path: filePath
11932
+ });
11867
11933
  const content = node_fs.default.readFileSync(fullPath, "utf-8");
11868
11934
  const fm = extractFrontmatter(content);
11869
11935
  let mergeData;
11870
11936
  try {
11871
11937
  mergeData = JSON.parse(data);
11872
11938
  } catch {
11873
- error("Invalid JSON for --data");
11874
- return;
11939
+ return cmdErr("Invalid JSON for --data");
11875
11940
  }
11876
11941
  Object.assign(fm, mergeData);
11877
11942
  const newContent = spliceFrontmatter(content, fm);
11878
11943
  node_fs.default.writeFileSync(fullPath, newContent, "utf-8");
11879
- output({
11944
+ return cmdOk({
11880
11945
  merged: true,
11881
11946
  fields: Object.keys(mergeData)
11882
- }, raw, "true");
11947
+ }, "true");
11883
11948
  }
11884
- function cmdFrontmatterValidate(cwd, filePath, schemaName, raw) {
11885
- if (!filePath || !schemaName) error("file and schema required");
11949
+ function cmdFrontmatterValidate(cwd, filePath, schemaName) {
11950
+ if (!filePath || !schemaName) return cmdErr("file and schema required");
11886
11951
  const schema = FRONTMATTER_SCHEMAS[schemaName];
11887
- if (!schema) error(`Unknown schema: ${schemaName}. Available: ${Object.keys(FRONTMATTER_SCHEMAS).join(", ")}`);
11952
+ if (!schema) return cmdErr(`Unknown schema: ${schemaName}. Available: ${Object.keys(FRONTMATTER_SCHEMAS).join(", ")}`);
11888
11953
  const content = safeReadFile(node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath));
11889
- if (!content) {
11890
- output({
11891
- error: "File not found",
11892
- path: filePath
11893
- }, raw);
11894
- return;
11895
- }
11954
+ if (!content) return cmdOk({
11955
+ error: "File not found",
11956
+ path: filePath
11957
+ });
11896
11958
  const fm = extractFrontmatter(content);
11897
11959
  const missing = schema.required.filter((f) => fm[f] === void 0);
11898
11960
  const present = schema.required.filter((f) => fm[f] !== void 0);
11899
- output({
11961
+ return cmdOk({
11900
11962
  valid: missing.length === 0,
11901
11963
  missing,
11902
11964
  present,
11903
11965
  schema: schemaName
11904
- }, raw, missing.length === 0 ? "valid" : "invalid");
11966
+ }, missing.length === 0 ? "valid" : "invalid");
11905
11967
  }
11906
11968
 
11907
11969
  //#endregion
@@ -11917,15 +11979,12 @@ function cmdConfigEnsureSection(cwd, raw) {
11917
11979
  try {
11918
11980
  if (!node_fs.default.existsSync(planningDir)) node_fs.default.mkdirSync(planningDir, { recursive: true });
11919
11981
  } catch (err) {
11920
- error("Failed to create .planning directory: " + err.message);
11921
- }
11922
- if (node_fs.default.existsSync(configPath)) {
11923
- output({
11924
- created: false,
11925
- reason: "already_exists"
11926
- }, raw, "exists");
11927
- return;
11982
+ return cmdErr("Failed to create .planning directory: " + err.message);
11928
11983
  }
11984
+ if (node_fs.default.existsSync(configPath)) return cmdOk({
11985
+ created: false,
11986
+ reason: "already_exists"
11987
+ }, raw ? "exists" : void 0);
11929
11988
  const homedir = node_os.default.homedir();
11930
11989
  const braveKeyFile = node_path.default.join(homedir, ".maxsim", "brave_api_key");
11931
11990
  const hasBraveSearch = !!(process.env.BRAVE_API_KEY || node_fs.default.existsSync(braveKeyFile));
@@ -11948,17 +12007,17 @@ function cmdConfigEnsureSection(cwd, raw) {
11948
12007
  };
11949
12008
  try {
11950
12009
  node_fs.default.writeFileSync(configPath, JSON.stringify(defaults, null, 2), "utf-8");
11951
- output({
12010
+ return cmdOk({
11952
12011
  created: true,
11953
12012
  path: ".planning/config.json"
11954
- }, raw, "created");
12013
+ }, raw ? "created" : void 0);
11955
12014
  } catch (err) {
11956
- error("Failed to create config.json: " + err.message);
12015
+ return cmdErr("Failed to create config.json: " + err.message);
11957
12016
  }
11958
12017
  }
11959
12018
  function cmdConfigSet(cwd, keyPath, value, raw) {
11960
12019
  const configPath = node_path.default.join(cwd, ".planning", "config.json");
11961
- if (!keyPath) error("Usage: config-set <key.path> <value>");
12020
+ if (!keyPath) return cmdErr("Usage: config-set <key.path> <value>");
11962
12021
  let parsedValue = value;
11963
12022
  if (value === "true") parsedValue = true;
11964
12023
  else if (value === "false") parsedValue = false;
@@ -11967,7 +12026,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
11967
12026
  try {
11968
12027
  if (node_fs.default.existsSync(configPath)) config = JSON.parse(node_fs.default.readFileSync(configPath, "utf-8"));
11969
12028
  } catch (err) {
11970
- error("Failed to read config.json: " + err.message);
12029
+ return cmdErr("Failed to read config.json: " + err.message);
11971
12030
  }
11972
12031
  const keys = keyPath.split(".");
11973
12032
  let current = config;
@@ -11979,34 +12038,33 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
11979
12038
  current[keys[keys.length - 1]] = parsedValue;
11980
12039
  try {
11981
12040
  node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
11982
- output({
12041
+ return cmdOk({
11983
12042
  updated: true,
11984
12043
  key: keyPath,
11985
12044
  value: parsedValue
11986
- }, raw, `${keyPath}=${parsedValue}`);
12045
+ }, raw ? `${keyPath}=${parsedValue}` : void 0);
11987
12046
  } catch (err) {
11988
- error("Failed to write config.json: " + err.message);
12047
+ return cmdErr("Failed to write config.json: " + err.message);
11989
12048
  }
11990
12049
  }
11991
12050
  function cmdConfigGet(cwd, keyPath, raw) {
11992
12051
  const configPath = node_path.default.join(cwd, ".planning", "config.json");
11993
- if (!keyPath) error("Usage: config-get <key.path>");
12052
+ if (!keyPath) return cmdErr("Usage: config-get <key.path>");
11994
12053
  let config = {};
11995
12054
  try {
11996
12055
  if (node_fs.default.existsSync(configPath)) config = JSON.parse(node_fs.default.readFileSync(configPath, "utf-8"));
11997
- else error("No config.json found at " + configPath);
12056
+ else return cmdErr("No config.json found at " + configPath);
11998
12057
  } catch (err) {
11999
- if (err.message.startsWith("No config.json")) throw err;
12000
- error("Failed to read config.json: " + err.message);
12058
+ return cmdErr("Failed to read config.json: " + err.message);
12001
12059
  }
12002
12060
  const keys = keyPath.split(".");
12003
12061
  let current = config;
12004
12062
  for (const key of keys) {
12005
- if (current === void 0 || current === null || typeof current !== "object") error(`Key not found: ${keyPath}`);
12063
+ if (current === void 0 || current === null || typeof current !== "object") return cmdErr(`Key not found: ${keyPath}`);
12006
12064
  current = current[key];
12007
12065
  }
12008
- if (current === void 0) error(`Key not found: ${keyPath}`);
12009
- output(current, raw, String(current));
12066
+ if (current === void 0) return cmdErr(`Key not found: ${keyPath}`);
12067
+ return cmdOk(current, raw ? String(current) : void 0);
12010
12068
  }
12011
12069
 
12012
12070
  //#endregion
@@ -12023,16 +12081,31 @@ function escapeStringRegexp(string) {
12023
12081
  *
12024
12082
  * Ported from maxsim/bin/lib/state.cjs
12025
12083
  */
12084
+ /**
12085
+ * Parse a markdown table row into cells, handling escaped pipes (`\|`) within cell content.
12086
+ * Strips leading/trailing pipe characters and trims each cell.
12087
+ */
12088
+ function parseTableRow(row) {
12089
+ const placeholder = "\0PIPE\0";
12090
+ return row.replace(/\\\|/g, placeholder).split("|").map((c) => c.replaceAll(placeholder, "|").trim()).filter(Boolean);
12091
+ }
12026
12092
  function stateExtractField(content, fieldName) {
12027
- const pattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, "i");
12028
- const match = content.match(pattern);
12029
- return match ? match[1].trim() : null;
12093
+ const escaped = escapeStringRegexp(fieldName);
12094
+ const boldPattern = new RegExp(`\\*\\*\\s*${escaped}\\s*:\\s*\\*\\*\\s*(.+)`, "i");
12095
+ const boldMatch = content.match(boldPattern);
12096
+ if (boldMatch) return boldMatch[1].trim();
12097
+ const plainPattern = new RegExp(`^\\s*${escaped}\\s*:\\s*(.+)`, "im");
12098
+ const plainMatch = content.match(plainPattern);
12099
+ return plainMatch ? plainMatch[1].trim() : null;
12030
12100
  }
12031
12101
  function stateReplaceField(content, fieldName, newValue) {
12032
12102
  const escaped = escapeStringRegexp(fieldName);
12033
- const pattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, "i");
12034
- if (pattern.test(content)) return content.replace(pattern, (_match, prefix) => `${prefix}${newValue}`);
12035
- return null;
12103
+ const boldPattern = new RegExp(`(\\*\\*\\s*${escaped}\\s*:\\s*\\*\\*\\s*)(.*)`, "i");
12104
+ let replaced = content.replace(boldPattern, (_match, prefix) => `${prefix}${newValue}`);
12105
+ if (replaced !== content) return replaced;
12106
+ const plainPattern = new RegExp(`(^[ \\t]*${escaped}\\s*:\\s*)(.*)`, "im");
12107
+ replaced = content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
12108
+ return replaced !== content ? replaced : null;
12036
12109
  }
12037
12110
  function readTextArgOrFile(cwd, value, filePath, label) {
12038
12111
  if (!filePath) return value;
@@ -12059,16 +12132,14 @@ function appendToStateSection(content, sectionPattern, entry, placeholderPattern
12059
12132
  sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
12060
12133
  return content.replace(sectionPattern, (_m, header) => `${header}${sectionBody}`);
12061
12134
  }
12062
- function cmdStateLoad(cwd, raw) {
12135
+ async function cmdStateLoad(cwd, raw) {
12063
12136
  const config = loadConfig(cwd);
12064
- let stateRaw = "";
12065
- try {
12066
- stateRaw = node_fs.default.readFileSync(statePath(cwd), "utf-8");
12067
- } catch (e) {
12068
- debugLog(e);
12069
- }
12070
- const configExists = node_fs.default.existsSync(configPath(cwd));
12071
- const roadmapExists = node_fs.default.existsSync(roadmapPath(cwd));
12137
+ const [stateContent, configExists, roadmapExists] = await Promise.all([
12138
+ safeReadFileAsync(statePath(cwd)),
12139
+ node_fs.default.promises.access(configPath(cwd)).then(() => true, () => false),
12140
+ node_fs.default.promises.access(roadmapPath(cwd)).then(() => true, () => false)
12141
+ ]);
12142
+ const stateRaw = stateContent ?? "";
12072
12143
  const stateExists = stateRaw.length > 0;
12073
12144
  const result = {
12074
12145
  config,
@@ -12079,7 +12150,7 @@ function cmdStateLoad(cwd, raw) {
12079
12150
  };
12080
12151
  if (raw) {
12081
12152
  const c = config;
12082
- const lines = [
12153
+ return cmdOk(result, [
12083
12154
  `model_profile=${c.model_profile}`,
12084
12155
  `commit_docs=${c.commit_docs}`,
12085
12156
  `branching_strategy=${c.branching_strategy}`,
@@ -12092,36 +12163,25 @@ function cmdStateLoad(cwd, raw) {
12092
12163
  `config_exists=${configExists}`,
12093
12164
  `roadmap_exists=${roadmapExists}`,
12094
12165
  `state_exists=${stateExists}`
12095
- ];
12096
- process.stdout.write(lines.join("\n"));
12097
- process.exit(0);
12166
+ ].join("\n"));
12098
12167
  }
12099
- output(result);
12168
+ return cmdOk(result);
12100
12169
  }
12101
12170
  function cmdStateGet(cwd, section, raw) {
12102
12171
  const statePath$2 = statePath(cwd);
12103
12172
  try {
12104
12173
  const content = node_fs.default.readFileSync(statePath$2, "utf-8");
12105
- if (!section) {
12106
- output({ content }, raw, content);
12107
- return;
12108
- }
12174
+ if (!section) return cmdOk({ content }, raw ? content : void 0);
12175
+ const fieldValue = stateExtractField(content, section);
12176
+ if (fieldValue !== null) return cmdOk({ [section]: fieldValue }, raw ? fieldValue : void 0);
12109
12177
  const fieldEscaped = escapeStringRegexp(section);
12110
- const fieldPattern = new RegExp(`\\*\\*${fieldEscaped}:\\*\\*\\s*(.*)`, "i");
12111
- const fieldMatch = content.match(fieldPattern);
12112
- if (fieldMatch) {
12113
- output({ [section]: fieldMatch[1].trim() }, raw, fieldMatch[1].trim());
12114
- return;
12115
- }
12116
- const sectionPattern = new RegExp(`##\\s*${fieldEscaped}\\s*\n([\\s\\S]*?)(?=\\n##|$)`, "i");
12178
+ const sectionPattern = new RegExp(`#{2,3}\\s*${fieldEscaped}\\s*\\n\\s*\\n?([\\s\\S]*?)(?=\\n#{2,3}\\s|$)`, "i");
12117
12179
  const sectionMatch = content.match(sectionPattern);
12118
- if (sectionMatch) {
12119
- output({ [section]: sectionMatch[1].trim() }, raw, sectionMatch[1].trim());
12120
- return;
12121
- }
12122
- output({ error: `Section or field "${section}" not found` }, raw, "");
12123
- } catch {
12124
- error("STATE.md not found");
12180
+ if (sectionMatch) return cmdOk({ [section]: sectionMatch[1].trim() }, raw ? sectionMatch[1].trim() : void 0);
12181
+ return cmdOk({ error: `Section or field "${section}" not found` }, raw ? "" : void 0);
12182
+ } catch (e) {
12183
+ rethrowCliSignals(e);
12184
+ return cmdErr("STATE.md not found");
12125
12185
  }
12126
12186
  }
12127
12187
  function cmdStatePatch(cwd, patches, raw) {
@@ -12133,36 +12193,34 @@ function cmdStatePatch(cwd, patches, raw) {
12133
12193
  failed: []
12134
12194
  };
12135
12195
  for (const [field, value] of Object.entries(patches)) {
12136
- const fieldEscaped = escapeStringRegexp(field);
12137
- const pattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, "i");
12138
- if (pattern.test(content)) {
12139
- content = content.replace(pattern, (_match, prefix) => `${prefix}${value}`);
12196
+ const result = stateReplaceField(content, field, value);
12197
+ if (result) {
12198
+ content = result;
12140
12199
  results.updated.push(field);
12141
12200
  } else results.failed.push(field);
12142
12201
  }
12143
12202
  if (results.updated.length > 0) node_fs.default.writeFileSync(statePath$3, content, "utf-8");
12144
- output(results, raw, results.updated.length > 0 ? "true" : "false");
12145
- } catch {
12146
- error("STATE.md not found");
12203
+ return cmdOk(results, raw ? results.updated.length > 0 ? "true" : "false" : void 0);
12204
+ } catch (e) {
12205
+ rethrowCliSignals(e);
12206
+ return cmdErr("STATE.md not found");
12147
12207
  }
12148
12208
  }
12149
12209
  function cmdStateUpdate(cwd, field, value) {
12150
- if (!field || value === void 0) error("field and value required for state update");
12210
+ if (!field || value === void 0) return cmdErr("field and value required for state update");
12151
12211
  const statePath$4 = statePath(cwd);
12152
12212
  try {
12153
- let content = node_fs.default.readFileSync(statePath$4, "utf-8");
12154
- const fieldEscaped = escapeStringRegexp(field);
12155
- const pattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, "i");
12156
- if (pattern.test(content)) {
12157
- content = content.replace(pattern, (_match, prefix) => `${prefix}${value}`);
12158
- node_fs.default.writeFileSync(statePath$4, content, "utf-8");
12159
- output({ updated: true });
12160
- } else output({
12213
+ const result = stateReplaceField(node_fs.default.readFileSync(statePath$4, "utf-8"), field, value);
12214
+ if (result) {
12215
+ node_fs.default.writeFileSync(statePath$4, result, "utf-8");
12216
+ return cmdOk({ updated: true });
12217
+ } else return cmdOk({
12161
12218
  updated: false,
12162
12219
  reason: `Field "${field}" not found in STATE.md`
12163
12220
  });
12164
- } catch {
12165
- output({
12221
+ } catch (e) {
12222
+ rethrowCliSignals(e);
12223
+ return cmdOk({
12166
12224
  updated: false,
12167
12225
  reason: "STATE.md not found"
12168
12226
  });
@@ -12170,56 +12228,44 @@ function cmdStateUpdate(cwd, field, value) {
12170
12228
  }
12171
12229
  function cmdStateAdvancePlan(cwd, raw) {
12172
12230
  const statePath$5 = statePath(cwd);
12173
- if (!node_fs.default.existsSync(statePath$5)) {
12174
- output({ error: "STATE.md not found" }, raw);
12175
- return;
12176
- }
12231
+ if (!node_fs.default.existsSync(statePath$5)) return cmdOk({ error: "STATE.md not found" });
12177
12232
  let content = node_fs.default.readFileSync(statePath$5, "utf-8");
12178
12233
  const currentPlan = parseInt(stateExtractField(content, "Current Plan") ?? "", 10);
12179
12234
  const totalPlans = parseInt(stateExtractField(content, "Total Plans in Phase") ?? "", 10);
12180
12235
  const today = todayISO();
12181
- if (isNaN(currentPlan) || isNaN(totalPlans)) {
12182
- output({ error: "Cannot parse Current Plan or Total Plans in Phase from STATE.md" }, raw);
12183
- return;
12184
- }
12236
+ if (isNaN(currentPlan) || isNaN(totalPlans)) return cmdOk({ error: "Cannot parse Current Plan or Total Plans in Phase from STATE.md" });
12185
12237
  if (currentPlan >= totalPlans) {
12186
12238
  content = stateReplaceField(content, "Status", "Phase complete — ready for verification") || content;
12187
12239
  content = stateReplaceField(content, "Last Activity", today) || content;
12188
12240
  node_fs.default.writeFileSync(statePath$5, content, "utf-8");
12189
- output({
12241
+ return cmdOk({
12190
12242
  advanced: false,
12191
12243
  reason: "last_plan",
12192
12244
  current_plan: currentPlan,
12193
12245
  total_plans: totalPlans,
12194
12246
  status: "ready_for_verification"
12195
- }, raw, "false");
12247
+ }, raw ? "false" : void 0);
12196
12248
  } else {
12197
12249
  const newPlan = currentPlan + 1;
12198
12250
  content = stateReplaceField(content, "Current Plan", String(newPlan)) || content;
12199
12251
  content = stateReplaceField(content, "Status", "Ready to execute") || content;
12200
12252
  content = stateReplaceField(content, "Last Activity", today) || content;
12201
12253
  node_fs.default.writeFileSync(statePath$5, content, "utf-8");
12202
- output({
12254
+ return cmdOk({
12203
12255
  advanced: true,
12204
12256
  previous_plan: currentPlan,
12205
12257
  current_plan: newPlan,
12206
12258
  total_plans: totalPlans
12207
- }, raw, "true");
12259
+ }, raw ? "true" : void 0);
12208
12260
  }
12209
12261
  }
12210
12262
  function cmdStateRecordMetric(cwd, options, raw) {
12211
12263
  const statePath$6 = statePath(cwd);
12212
- if (!node_fs.default.existsSync(statePath$6)) {
12213
- output({ error: "STATE.md not found" }, raw);
12214
- return;
12215
- }
12264
+ if (!node_fs.default.existsSync(statePath$6)) return cmdOk({ error: "STATE.md not found" });
12216
12265
  let content = node_fs.default.readFileSync(statePath$6, "utf-8");
12217
12266
  const { phase, plan, duration, tasks, files } = options;
12218
- if (!phase || !plan || !duration) {
12219
- output({ error: "phase, plan, and duration required" }, raw);
12220
- return;
12221
- }
12222
- const metricsPattern = /(##\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n)([\s\S]*?)(?=\n##|\n$|$)/i;
12267
+ if (!phase || !plan || !duration) return cmdOk({ error: "phase, plan, and duration required" });
12268
+ const metricsPattern = /(#{2,3}\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[\s:|\-]+\n)([\s\S]*?)(?=\n#{2,3}\s|\n$|$)/i;
12223
12269
  const metricsMatch = content.match(metricsPattern);
12224
12270
  if (metricsMatch) {
12225
12271
  let tableBody = metricsMatch[2].trimEnd();
@@ -12228,23 +12274,20 @@ function cmdStateRecordMetric(cwd, options, raw) {
12228
12274
  else tableBody = tableBody + "\n" + newRow;
12229
12275
  content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
12230
12276
  node_fs.default.writeFileSync(statePath$6, content, "utf-8");
12231
- output({
12277
+ return cmdOk({
12232
12278
  recorded: true,
12233
12279
  phase,
12234
12280
  plan,
12235
12281
  duration
12236
- }, raw, "true");
12237
- } else output({
12282
+ }, raw ? "true" : void 0);
12283
+ } else return cmdOk({
12238
12284
  recorded: false,
12239
12285
  reason: "Performance Metrics section not found in STATE.md"
12240
- }, raw, "false");
12286
+ }, raw ? "false" : void 0);
12241
12287
  }
12242
12288
  function cmdStateUpdateProgress(cwd, raw) {
12243
12289
  const statePath$7 = statePath(cwd);
12244
- if (!node_fs.default.existsSync(statePath$7)) {
12245
- output({ error: "STATE.md not found" }, raw);
12246
- return;
12247
- }
12290
+ if (!node_fs.default.existsSync(statePath$7)) return cmdOk({ error: "STATE.md not found" });
12248
12291
  let content = node_fs.default.readFileSync(statePath$7, "utf-8");
12249
12292
  const phasesDir = phasesPath(cwd);
12250
12293
  let totalPlans = 0;
@@ -12261,28 +12304,24 @@ function cmdStateUpdateProgress(cwd, raw) {
12261
12304
  const barWidth = 10;
12262
12305
  const filled = Math.round(percent / 100 * barWidth);
12263
12306
  const progressStr = `[${"█".repeat(filled) + "░".repeat(barWidth - filled)}] ${percent}%`;
12264
- const progressPattern = /(\*\*Progress:\*\*\s*).*/i;
12265
- if (progressPattern.test(content)) {
12266
- content = content.replace(progressPattern, (_match, prefix) => `${prefix}${progressStr}`);
12267
- node_fs.default.writeFileSync(statePath$7, content, "utf-8");
12268
- output({
12307
+ const result = stateReplaceField(content, "Progress", progressStr);
12308
+ if (result) {
12309
+ node_fs.default.writeFileSync(statePath$7, result, "utf-8");
12310
+ return cmdOk({
12269
12311
  updated: true,
12270
12312
  percent,
12271
12313
  completed: totalSummaries,
12272
12314
  total: totalPlans,
12273
12315
  bar: progressStr
12274
- }, raw, progressStr);
12275
- } else output({
12316
+ }, raw ? progressStr : void 0);
12317
+ } else return cmdOk({
12276
12318
  updated: false,
12277
12319
  reason: "Progress field not found in STATE.md"
12278
- }, raw, "false");
12320
+ }, raw ? "false" : void 0);
12279
12321
  }
12280
12322
  function cmdStateAddDecision(cwd, options, raw) {
12281
12323
  const statePath$8 = statePath(cwd);
12282
- if (!node_fs.default.existsSync(statePath$8)) {
12283
- output({ error: "STATE.md not found" }, raw);
12284
- return;
12285
- }
12324
+ if (!node_fs.default.existsSync(statePath$8)) return cmdOk({ error: "STATE.md not found" });
12286
12325
  const { phase, summary, summary_file, rationale, rationale_file } = options;
12287
12326
  let summaryText;
12288
12327
  let rationaleText = "";
@@ -12290,99 +12329,79 @@ function cmdStateAddDecision(cwd, options, raw) {
12290
12329
  summaryText = readTextArgOrFile(cwd, summary, summary_file, "summary");
12291
12330
  rationaleText = readTextArgOrFile(cwd, rationale || "", rationale_file, "rationale") || "";
12292
12331
  } catch (thrown) {
12293
- output({
12332
+ return cmdOk({
12294
12333
  added: false,
12295
12334
  reason: thrown.message
12296
- }, raw, "false");
12297
- return;
12298
- }
12299
- if (!summaryText) {
12300
- output({ error: "summary required" }, raw);
12301
- return;
12335
+ }, raw ? "false" : void 0);
12302
12336
  }
12337
+ if (!summaryText) return cmdOk({ error: "summary required" });
12303
12338
  const content = node_fs.default.readFileSync(statePath$8, "utf-8");
12304
12339
  const entry = `- [Phase ${phase || "?"}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ""}`;
12305
12340
  const updated = appendToStateSection(content, /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, entry, [/None yet\.?\s*\n?/gi, /No decisions yet\.?\s*\n?/gi]);
12306
12341
  if (updated) {
12307
12342
  node_fs.default.writeFileSync(statePath$8, updated, "utf-8");
12308
- output({
12343
+ return cmdOk({
12309
12344
  added: true,
12310
12345
  decision: entry
12311
- }, raw, "true");
12312
- } else output({
12346
+ }, raw ? "true" : void 0);
12347
+ } else return cmdOk({
12313
12348
  added: false,
12314
12349
  reason: "Decisions section not found in STATE.md"
12315
- }, raw, "false");
12350
+ }, raw ? "false" : void 0);
12316
12351
  }
12317
12352
  function cmdStateAddBlocker(cwd, text, raw) {
12318
12353
  const statePath$9 = statePath(cwd);
12319
- if (!node_fs.default.existsSync(statePath$9)) {
12320
- output({ error: "STATE.md not found" }, raw);
12321
- return;
12322
- }
12354
+ if (!node_fs.default.existsSync(statePath$9)) return cmdOk({ error: "STATE.md not found" });
12323
12355
  const blockerOptions = typeof text === "object" && text !== null ? text : { text };
12324
12356
  let blockerText;
12325
12357
  try {
12326
12358
  blockerText = readTextArgOrFile(cwd, blockerOptions.text, blockerOptions.text_file, "blocker");
12327
12359
  } catch (thrown) {
12328
- output({
12360
+ return cmdOk({
12329
12361
  added: false,
12330
12362
  reason: thrown.message
12331
- }, raw, "false");
12332
- return;
12333
- }
12334
- if (!blockerText) {
12335
- output({ error: "text required" }, raw);
12336
- return;
12363
+ }, raw ? "false" : void 0);
12337
12364
  }
12365
+ if (!blockerText) return cmdOk({ error: "text required" });
12338
12366
  const updated = appendToStateSection(node_fs.default.readFileSync(statePath$9, "utf-8"), /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, `- ${blockerText}`, [/None\.?\s*\n?/gi, /None yet\.?\s*\n?/gi]);
12339
12367
  if (updated) {
12340
12368
  node_fs.default.writeFileSync(statePath$9, updated, "utf-8");
12341
- output({
12369
+ return cmdOk({
12342
12370
  added: true,
12343
12371
  blocker: blockerText
12344
- }, raw, "true");
12345
- } else output({
12372
+ }, raw ? "true" : void 0);
12373
+ } else return cmdOk({
12346
12374
  added: false,
12347
12375
  reason: "Blockers section not found in STATE.md"
12348
- }, raw, "false");
12376
+ }, raw ? "false" : void 0);
12349
12377
  }
12350
12378
  function cmdStateResolveBlocker(cwd, text, raw) {
12351
12379
  const statePath$10 = statePath(cwd);
12352
- if (!node_fs.default.existsSync(statePath$10)) {
12353
- output({ error: "STATE.md not found" }, raw);
12354
- return;
12355
- }
12356
- if (!text) {
12357
- output({ error: "text required" }, raw);
12358
- return;
12359
- }
12380
+ if (!node_fs.default.existsSync(statePath$10)) return cmdOk({ error: "STATE.md not found" });
12381
+ if (!text) return cmdOk({ error: "text required" });
12360
12382
  let content = node_fs.default.readFileSync(statePath$10, "utf-8");
12361
- const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
12383
+ const sectionPattern = /(#{2,3}\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n\s*\n?)([\s\S]*?)(?=\n#{2,3}\s|$)/i;
12362
12384
  const match = content.match(sectionPattern);
12363
12385
  if (match) {
12364
12386
  let newBody = match[2].split("\n").filter((line) => {
12365
- if (!line.startsWith("- ")) return true;
12387
+ if (!/^\s*[-*]\s+/.test(line)) return true;
12366
12388
  return !line.toLowerCase().includes(text.toLowerCase());
12367
12389
  }).join("\n");
12368
- if (!newBody.trim() || !newBody.includes("- ")) newBody = "None\n";
12390
+ if (!newBody.trim() || !/^\s*[-*]\s+/m.test(newBody)) newBody = "None\n";
12369
12391
  content = content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
12370
12392
  node_fs.default.writeFileSync(statePath$10, content, "utf-8");
12371
- output({
12393
+ return cmdOk({
12372
12394
  resolved: true,
12373
12395
  blocker: text
12374
- }, raw, "true");
12375
- } else output({
12396
+ }, raw ? "true" : void 0);
12397
+ } else return cmdOk({
12376
12398
  resolved: false,
12377
12399
  reason: "Blockers section not found in STATE.md"
12378
- }, raw, "false");
12400
+ }, raw ? "false" : void 0);
12379
12401
  }
12380
12402
  function cmdStateRecordSession(cwd, options, raw) {
12381
12403
  const statePath$11 = statePath(cwd);
12382
- if (!node_fs.default.existsSync(statePath$11)) {
12383
- output({ error: "STATE.md not found" }, raw);
12384
- return;
12385
- }
12404
+ if (!node_fs.default.existsSync(statePath$11)) return cmdOk({ error: "STATE.md not found" });
12386
12405
  let content = node_fs.default.readFileSync(statePath$11, "utf-8");
12387
12406
  const now = (/* @__PURE__ */ new Date()).toISOString();
12388
12407
  const updated = [];
@@ -12413,27 +12432,20 @@ function cmdStateRecordSession(cwd, options, raw) {
12413
12432
  }
12414
12433
  if (updated.length > 0) {
12415
12434
  node_fs.default.writeFileSync(statePath$11, content, "utf-8");
12416
- output({
12435
+ return cmdOk({
12417
12436
  recorded: true,
12418
12437
  updated
12419
- }, raw, "true");
12420
- } else output({
12438
+ }, raw ? "true" : void 0);
12439
+ } else return cmdOk({
12421
12440
  recorded: false,
12422
12441
  reason: "No session fields found in STATE.md"
12423
- }, raw, "false");
12442
+ }, raw ? "false" : void 0);
12424
12443
  }
12425
12444
  function cmdStateSnapshot(cwd, raw) {
12426
12445
  const statePath$12 = statePath(cwd);
12427
- if (!node_fs.default.existsSync(statePath$12)) {
12428
- output({ error: "STATE.md not found" }, raw);
12429
- return;
12430
- }
12446
+ if (!node_fs.default.existsSync(statePath$12)) return cmdOk({ error: "STATE.md not found" });
12431
12447
  const content = node_fs.default.readFileSync(statePath$12, "utf-8");
12432
- const extractField = (fieldName) => {
12433
- const pattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, "i");
12434
- const match = content.match(pattern);
12435
- return match ? match[1].trim() : null;
12436
- };
12448
+ const extractField = (fieldName) => stateExtractField(content, fieldName);
12437
12449
  const currentPhase = extractField("Current Phase");
12438
12450
  const currentPhaseName = extractField("Current Phase Name");
12439
12451
  const totalPhasesRaw = extractField("Total Phases");
@@ -12448,11 +12460,12 @@ function cmdStateSnapshot(cwd, raw) {
12448
12460
  const totalPlansInPhase = totalPlansRaw ? parseInt(totalPlansRaw, 10) : null;
12449
12461
  const progressPercent = progressRaw ? parseInt(progressRaw.replace("%", ""), 10) : null;
12450
12462
  const decisions = [];
12451
- const decisionsMatch = content.match(/##\s*Decisions Made[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n([\s\S]*?)(?=\n##|\n$|$)/i);
12463
+ const decisionsMatch = content.match(/#{2,3}\s*Decisions Made[\s\S]*?\n\|[^\n]+\n\|[\s:|\-]+\n([\s\S]*?)(?=\n#{2,3}\s|\n$|$)/i);
12452
12464
  if (decisionsMatch) {
12453
- const rows = decisionsMatch[1].trim().split("\n").filter((r) => r.includes("|"));
12465
+ const rows = decisionsMatch[1].trim().split("\n").filter((r) => r.includes("|") && !r.match(/^\s*$/));
12454
12466
  for (const row of rows) {
12455
- const cells = row.split("|").map((c) => c.trim()).filter(Boolean);
12467
+ if (/^\s*\|[\s:\-|]+\|\s*$/.test(row)) continue;
12468
+ const cells = parseTableRow(row);
12456
12469
  if (cells.length >= 3) decisions.push({
12457
12470
  phase: cells[0],
12458
12471
  summary: cells[1],
@@ -12461,27 +12474,24 @@ function cmdStateSnapshot(cwd, raw) {
12461
12474
  }
12462
12475
  }
12463
12476
  const blockers = [];
12464
- const blockersMatch = content.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i);
12477
+ const blockersMatch = content.match(/#{2,3}\s*Blockers\s*\n([\s\S]*?)(?=\n#{2,3}\s|$)/i);
12465
12478
  if (blockersMatch) {
12466
- const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
12467
- for (const item of items) blockers.push(item.replace(/^-\s+/, "").trim());
12479
+ const items = blockersMatch[1].match(/^\s*[-*]\s+(.+)$/gm) || [];
12480
+ for (const item of items) blockers.push(item.replace(/^\s*[-*]\s+/, "").trim());
12468
12481
  }
12469
12482
  const session = {
12470
12483
  last_date: null,
12471
12484
  stopped_at: null,
12472
12485
  resume_file: null
12473
12486
  };
12474
- const sessionMatch = content.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
12487
+ const sessionMatch = content.match(/#{2,3}\s*Session\s*\n\s*\n?([\s\S]*?)(?=\n#{2,3}\s|$)/i);
12475
12488
  if (sessionMatch) {
12476
12489
  const sessionSection = sessionMatch[1];
12477
- const lastDateMatch = sessionSection.match(/\*\*Last Date:\*\*\s*(.+)/i);
12478
- const stoppedAtMatch = sessionSection.match(/\*\*Stopped At:\*\*\s*(.+)/i);
12479
- const resumeFileMatch = sessionSection.match(/\*\*Resume File:\*\*\s*(.+)/i);
12480
- if (lastDateMatch) session.last_date = lastDateMatch[1].trim();
12481
- if (stoppedAtMatch) session.stopped_at = stoppedAtMatch[1].trim();
12482
- if (resumeFileMatch) session.resume_file = resumeFileMatch[1].trim();
12490
+ session.last_date = stateExtractField(sessionSection, "Last Date");
12491
+ session.stopped_at = stateExtractField(sessionSection, "Stopped At") || stateExtractField(sessionSection, "Stopped at");
12492
+ session.resume_file = stateExtractField(sessionSection, "Resume File") || stateExtractField(sessionSection, "Resume file");
12483
12493
  }
12484
- output({
12494
+ return cmdOk({
12485
12495
  current_phase: currentPhase,
12486
12496
  current_phase_name: currentPhaseName,
12487
12497
  total_phases: totalPhases,
@@ -12495,7 +12505,7 @@ function cmdStateSnapshot(cwd, raw) {
12495
12505
  blockers,
12496
12506
  paused_at: pausedAt,
12497
12507
  session
12498
- }, raw);
12508
+ });
12499
12509
  }
12500
12510
 
12501
12511
  //#endregion
@@ -12505,15 +12515,12 @@ function cmdStateSnapshot(cwd, raw) {
12505
12515
  *
12506
12516
  * Ported from maxsim/bin/lib/roadmap.cjs
12507
12517
  */
12508
- function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
12518
+ function cmdRoadmapGetPhase(cwd, phaseNum) {
12509
12519
  const rmPath = roadmapPath(cwd);
12510
- if (!node_fs.default.existsSync(rmPath)) {
12511
- output({
12512
- found: false,
12513
- error: "ROADMAP.md not found"
12514
- }, raw, "");
12515
- return;
12516
- }
12520
+ if (!node_fs.default.existsSync(rmPath)) return cmdOk({
12521
+ found: false,
12522
+ error: "ROADMAP.md not found"
12523
+ }, "");
12517
12524
  try {
12518
12525
  const content = node_fs.default.readFileSync(rmPath, "utf-8");
12519
12526
  const escapedPhase = phaseNum.replace(/\./g, "\\.");
@@ -12522,21 +12529,17 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
12522
12529
  if (!headerMatch) {
12523
12530
  const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`, "i");
12524
12531
  const checklistMatch = content.match(checklistPattern);
12525
- if (checklistMatch) {
12526
- output({
12527
- found: false,
12528
- phase_number: phaseNum,
12529
- phase_name: checklistMatch[1].trim(),
12530
- error: "malformed_roadmap",
12531
- message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
12532
- }, raw, "");
12533
- return;
12534
- }
12535
- output({
12532
+ if (checklistMatch) return cmdOk({
12533
+ found: false,
12534
+ phase_number: phaseNum,
12535
+ phase_name: checklistMatch[1].trim(),
12536
+ error: "malformed_roadmap",
12537
+ message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
12538
+ }, "");
12539
+ return cmdOk({
12536
12540
  found: false,
12537
12541
  phase_number: phaseNum
12538
- }, raw, "");
12539
- return;
12542
+ }, "");
12540
12543
  }
12541
12544
  const phaseName = headerMatch[1].trim();
12542
12545
  const headerIndex = headerMatch.index;
@@ -12546,33 +12549,29 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
12546
12549
  const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
12547
12550
  const goal = goalMatch ? goalMatch[1].trim() : null;
12548
12551
  const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
12549
- output({
12552
+ return cmdOk({
12550
12553
  found: true,
12551
12554
  phase_number: phaseNum,
12552
12555
  phase_name: phaseName,
12553
12556
  goal,
12554
12557
  success_criteria: criteriaMatch ? criteriaMatch[1].trim().split("\n").map((line) => line.replace(/^\s*\d+\.\s*/, "").trim()).filter(Boolean) : [],
12555
12558
  section
12556
- }, raw, section);
12559
+ }, section);
12557
12560
  } catch (e) {
12558
- error("Failed to read ROADMAP.md: " + e.message);
12561
+ return cmdErr("Failed to read ROADMAP.md: " + e.message);
12559
12562
  }
12560
12563
  }
12561
- function cmdRoadmapAnalyze(cwd, raw) {
12562
- const rmPath = roadmapPath(cwd);
12563
- if (!node_fs.default.existsSync(rmPath)) {
12564
- output({
12565
- error: "ROADMAP.md not found",
12566
- milestones: [],
12567
- phases: [],
12568
- current_phase: null
12569
- }, raw);
12570
- return;
12571
- }
12572
- const content = node_fs.default.readFileSync(rmPath, "utf-8");
12564
+ async function cmdRoadmapAnalyze(cwd) {
12565
+ const content = await safeReadFileAsync(roadmapPath(cwd));
12566
+ if (!content) return cmdOk({
12567
+ error: "ROADMAP.md not found",
12568
+ milestones: [],
12569
+ phases: [],
12570
+ current_phase: null
12571
+ });
12573
12572
  const phasesDir = phasesPath(cwd);
12574
12573
  const phasePattern = getPhasePattern();
12575
- const phases = [];
12574
+ const parsedPhases = [];
12576
12575
  let match;
12577
12576
  while ((match = phasePattern.exec(content)) !== null) {
12578
12577
  const phaseNum = match[1];
@@ -12585,16 +12584,29 @@ function cmdRoadmapAnalyze(cwd, raw) {
12585
12584
  const goal = goalMatch ? goalMatch[1].trim() : null;
12586
12585
  const dependsMatch = section.match(/\*\*Depends on:\*\*\s*([^\n]+)/i);
12587
12586
  const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
12588
- const normalized = normalizePhaseName(phaseNum);
12587
+ parsedPhases.push({
12588
+ phaseNum,
12589
+ phaseName,
12590
+ goal,
12591
+ depends_on,
12592
+ normalized: normalizePhaseName(phaseNum),
12593
+ checkboxPattern: new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i")
12594
+ });
12595
+ }
12596
+ let allDirs = [];
12597
+ try {
12598
+ allDirs = await listSubDirsAsync(phasesDir);
12599
+ } catch {}
12600
+ const phases = await Promise.all(parsedPhases.map(async (p) => {
12589
12601
  let diskStatus = "no_directory";
12590
12602
  let planCount = 0;
12591
12603
  let summaryCount = 0;
12592
12604
  let hasContext = false;
12593
12605
  let hasResearch = false;
12594
12606
  try {
12595
- const dirMatch = listSubDirs(phasesDir).find((d) => d.startsWith(normalized + "-") || d === normalized);
12607
+ const dirMatch = allDirs.find((d) => d.startsWith(p.normalized + "-") || d === p.normalized);
12596
12608
  if (dirMatch) {
12597
- const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dirMatch));
12609
+ const phaseFiles = await node_fs.default.promises.readdir(node_path.default.join(phasesDir, dirMatch));
12598
12610
  planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
12599
12611
  summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
12600
12612
  hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
@@ -12609,22 +12621,21 @@ function cmdRoadmapAnalyze(cwd, raw) {
12609
12621
  } catch (e) {
12610
12622
  debugLog(e);
12611
12623
  }
12612
- const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i");
12613
- const checkboxMatch = content.match(checkboxPattern);
12624
+ const checkboxMatch = content.match(p.checkboxPattern);
12614
12625
  const roadmapComplete = checkboxMatch ? checkboxMatch[1] === "x" : false;
12615
- phases.push({
12616
- number: phaseNum,
12617
- name: phaseName,
12618
- goal,
12619
- depends_on,
12626
+ return {
12627
+ number: p.phaseNum,
12628
+ name: p.phaseName,
12629
+ goal: p.goal,
12630
+ depends_on: p.depends_on,
12620
12631
  plan_count: planCount,
12621
12632
  summary_count: summaryCount,
12622
12633
  has_context: hasContext,
12623
12634
  has_research: hasResearch,
12624
12635
  disk_status: diskStatus,
12625
12636
  roadmap_complete: roadmapComplete
12626
- });
12627
- }
12637
+ };
12638
+ }));
12628
12639
  const milestones = [];
12629
12640
  const milestonePattern = /##\s*(.*v(\d+\.\d+)[^(\n]*)/gi;
12630
12641
  let mMatch;
@@ -12643,7 +12654,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
12643
12654
  while ((checklistMatch = checklistPattern.exec(content)) !== null) checklistPhases.add(checklistMatch[1]);
12644
12655
  const detailPhases = new Set(phases.map((p) => p.number));
12645
12656
  const missingDetails = [...checklistPhases].filter((p) => !detailPhases.has(p));
12646
- output({
12657
+ return cmdOk({
12647
12658
  milestones,
12648
12659
  phases,
12649
12660
  phase_count: phases.length,
@@ -12654,57 +12665,46 @@ function cmdRoadmapAnalyze(cwd, raw) {
12654
12665
  current_phase: currentPhase ? currentPhase.number : null,
12655
12666
  next_phase: nextPhase ? nextPhase.number : null,
12656
12667
  missing_phase_details: missingDetails.length > 0 ? missingDetails : null
12657
- }, raw);
12668
+ });
12658
12669
  }
12659
- function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
12660
- if (!phaseNum) error("phase number required for roadmap update-plan-progress");
12670
+ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum) {
12671
+ if (!phaseNum) return cmdErr("phase number required for roadmap update-plan-progress");
12661
12672
  const rmPath = roadmapPath(cwd);
12662
12673
  const phaseInfo = findPhaseInternal(cwd, phaseNum);
12663
- if (!phaseInfo) error(`Phase ${phaseNum} not found`);
12674
+ if (!phaseInfo) return cmdErr(`Phase ${phaseNum} not found`);
12664
12675
  const planCount = phaseInfo.plans.length;
12665
12676
  const summaryCount = phaseInfo.summaries.length;
12666
- if (planCount === 0) {
12667
- output({
12668
- updated: false,
12669
- reason: "No plans found",
12670
- plan_count: 0,
12671
- summary_count: 0
12672
- }, raw, "no plans");
12673
- return;
12674
- }
12677
+ if (planCount === 0) return cmdOk({
12678
+ updated: false,
12679
+ reason: "No plans found",
12680
+ plan_count: 0,
12681
+ summary_count: 0
12682
+ }, "no plans");
12675
12683
  const isComplete = summaryCount >= planCount;
12676
12684
  const status = isComplete ? "Complete" : summaryCount > 0 ? "In Progress" : "Planned";
12677
12685
  const today = todayISO();
12678
- if (!node_fs.default.existsSync(rmPath)) {
12679
- output({
12680
- updated: false,
12681
- reason: "ROADMAP.md not found",
12682
- plan_count: planCount,
12683
- summary_count: summaryCount
12684
- }, raw, "no roadmap");
12685
- return;
12686
- }
12686
+ if (!node_fs.default.existsSync(rmPath)) return cmdOk({
12687
+ updated: false,
12688
+ reason: "ROADMAP.md not found",
12689
+ plan_count: planCount,
12690
+ summary_count: summaryCount
12691
+ }, "no roadmap");
12687
12692
  let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
12688
12693
  const phaseEscaped = phaseNum.replace(".", "\\.");
12689
- const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i");
12690
12694
  const dateField = isComplete ? ` ${today} ` : " ";
12691
- roadmapContent = roadmapContent.replace(tablePattern, `$1 ${summaryCount}/${planCount} $2 ${status.padEnd(11)}$3${dateField}$4`);
12692
- const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i");
12695
+ roadmapContent = roadmapContent.replace(new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i"), `$1 ${summaryCount}/${planCount} $2 ${status.padEnd(11)}$3${dateField}$4`);
12693
12696
  const planCountText = isComplete ? `${summaryCount}/${planCount} plans complete` : `${summaryCount}/${planCount} plans executed`;
12694
- roadmapContent = roadmapContent.replace(planCountPattern, `$1${planCountText}`);
12695
- if (isComplete) {
12696
- const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, "i");
12697
- roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
12698
- }
12697
+ roadmapContent = roadmapContent.replace(new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i"), `$1${planCountText}`);
12698
+ if (isComplete) roadmapContent = roadmapContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, "i"), `$1x$2 (completed ${today})`);
12699
12699
  node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
12700
- output({
12700
+ return cmdOk({
12701
12701
  updated: true,
12702
12702
  phase: phaseNum,
12703
12703
  plan_count: planCount,
12704
12704
  summary_count: summaryCount,
12705
12705
  status,
12706
12706
  complete: isComplete
12707
- }, raw, `${summaryCount}/${planCount} ${status}`);
12707
+ }, `${summaryCount}/${planCount} ${status}`);
12708
12708
  }
12709
12709
 
12710
12710
  //#endregion
@@ -12714,19 +12714,16 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
12714
12714
  *
12715
12715
  * Ported from maxsim/bin/lib/milestone.cjs
12716
12716
  */
12717
- function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
12718
- if (!reqIdsRaw || reqIdsRaw.length === 0) error("requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02");
12717
+ function cmdRequirementsMarkComplete(cwd, reqIdsRaw) {
12718
+ if (!reqIdsRaw || reqIdsRaw.length === 0) return cmdErr("requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02");
12719
12719
  const reqIds = reqIdsRaw.join(" ").replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
12720
- if (reqIds.length === 0) error("no valid requirement IDs found");
12720
+ if (reqIds.length === 0) return cmdErr("no valid requirement IDs found");
12721
12721
  const reqPath = planningPath(cwd, "REQUIREMENTS.md");
12722
- if (!node_fs.default.existsSync(reqPath)) {
12723
- output({
12724
- updated: false,
12725
- reason: "REQUIREMENTS.md not found",
12726
- ids: reqIds
12727
- }, raw, "no requirements file");
12728
- return;
12729
- }
12722
+ if (!node_fs.default.existsSync(reqPath)) return cmdOk({
12723
+ updated: false,
12724
+ reason: "REQUIREMENTS.md not found",
12725
+ ids: reqIds
12726
+ }, "no requirements file");
12730
12727
  let reqContent = node_fs.default.readFileSync(reqPath, "utf-8");
12731
12728
  const updated = [];
12732
12729
  const notFound = [];
@@ -12745,15 +12742,15 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
12745
12742
  else notFound.push(reqId);
12746
12743
  }
12747
12744
  if (updated.length > 0) node_fs.default.writeFileSync(reqPath, reqContent, "utf-8");
12748
- output({
12745
+ return cmdOk({
12749
12746
  updated: updated.length > 0,
12750
12747
  marked_complete: updated,
12751
12748
  not_found: notFound,
12752
12749
  total: reqIds.length
12753
- }, raw, `${updated.length}/${reqIds.length} requirements marked complete`);
12750
+ }, `${updated.length}/${reqIds.length} requirements marked complete`);
12754
12751
  }
12755
- function cmdMilestoneComplete(cwd, version, options, raw) {
12756
- if (!version) error("version required for milestone complete (e.g., v1.0)");
12752
+ function cmdMilestoneComplete(cwd, version, options) {
12753
+ if (!version) return cmdErr("version required for milestone complete (e.g., v1.0)");
12757
12754
  const roadmapPath$1 = roadmapPath(cwd);
12758
12755
  const reqPath = planningPath(cwd, "REQUIREMENTS.md");
12759
12756
  const statePath$1 = statePath(cwd);
@@ -12822,7 +12819,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
12822
12819
  } catch (e) {
12823
12820
  debugLog(e);
12824
12821
  }
12825
- output({
12822
+ return cmdOk({
12826
12823
  version,
12827
12824
  name: milestoneName,
12828
12825
  date: today,
@@ -12838,7 +12835,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
12838
12835
  },
12839
12836
  milestones_updated: true,
12840
12837
  state_updated: node_fs.default.existsSync(statePath$1)
12841
- }, raw);
12838
+ });
12842
12839
  }
12843
12840
 
12844
12841
  //#endregion
@@ -13267,12 +13264,12 @@ function parseTodoFrontmatter(content) {
13267
13264
  };
13268
13265
  }
13269
13266
  function cmdGenerateSlug(text, raw) {
13270
- if (!text) error("text required for slug generation");
13267
+ if (!text) return cmdErr("text required for slug generation");
13271
13268
  const slug = (0, import_slugify.default)(text, {
13272
13269
  lower: true,
13273
13270
  strict: true
13274
13271
  });
13275
- output({ slug }, raw, slug);
13272
+ return cmdOk({ slug }, raw ? slug : void 0);
13276
13273
  }
13277
13274
  function cmdCurrentTimestamp(format, raw) {
13278
13275
  const now = /* @__PURE__ */ new Date();
@@ -13288,7 +13285,7 @@ function cmdCurrentTimestamp(format, raw) {
13288
13285
  result = now.toISOString();
13289
13286
  break;
13290
13287
  }
13291
- output({ timestamp: result }, raw, result);
13288
+ return cmdOk({ timestamp: result }, raw ? result : void 0);
13292
13289
  }
13293
13290
  function cmdListTodos(cwd, area, raw) {
13294
13291
  const pendingDir = planningPath(cwd, "todos", "pending");
@@ -13313,25 +13310,26 @@ function cmdListTodos(cwd, area, raw) {
13313
13310
  } catch (e) {
13314
13311
  debugLog(e);
13315
13312
  }
13316
- output({
13313
+ return cmdOk({
13317
13314
  count,
13318
13315
  todos
13319
- }, raw, count.toString());
13316
+ }, raw ? count.toString() : void 0);
13320
13317
  }
13321
13318
  function cmdVerifyPathExists(cwd, targetPath, raw) {
13322
- if (!targetPath) error("path required for verification");
13319
+ if (!targetPath) return cmdErr("path required for verification");
13323
13320
  const fullPath = node_path.default.isAbsolute(targetPath) ? targetPath : node_path.default.join(cwd, targetPath);
13324
13321
  try {
13325
13322
  const stats = node_fs.default.statSync(fullPath);
13326
- output({
13323
+ return cmdOk({
13327
13324
  exists: true,
13328
13325
  type: stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other"
13329
- }, raw, "true");
13330
- } catch {
13331
- output({
13326
+ }, raw ? "true" : void 0);
13327
+ } catch (e) {
13328
+ rethrowCliSignals(e);
13329
+ return cmdOk({
13332
13330
  exists: false,
13333
13331
  type: null
13334
- }, raw, "false");
13332
+ }, raw ? "false" : void 0);
13335
13333
  }
13336
13334
  }
13337
13335
  function cmdHistoryDigest(cwd, raw) {
@@ -13358,14 +13356,11 @@ function cmdHistoryDigest(cwd, raw) {
13358
13356
  } catch (e) {
13359
13357
  debugLog(e);
13360
13358
  }
13361
- if (allPhaseDirs.length === 0) {
13362
- output({
13363
- phases: {},
13364
- decisions: [],
13365
- tech_stack: []
13366
- }, raw);
13367
- return;
13368
- }
13359
+ if (allPhaseDirs.length === 0) return cmdOk({
13360
+ phases: {},
13361
+ decisions: [],
13362
+ tech_stack: []
13363
+ });
13369
13364
  try {
13370
13365
  for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
13371
13366
  const summaries = node_fs.default.readdirSync(dirPath).filter((f) => isSummaryFile(f));
@@ -13406,48 +13401,40 @@ function cmdHistoryDigest(cwd, raw) {
13406
13401
  affects: [...data.affects],
13407
13402
  patterns: [...data.patterns]
13408
13403
  };
13409
- output(outputDigest, raw);
13404
+ return cmdOk(outputDigest);
13410
13405
  } catch (e) {
13411
- error("Failed to generate history digest: " + e.message);
13406
+ rethrowCliSignals(e);
13407
+ return cmdErr("Failed to generate history digest: " + e.message);
13412
13408
  }
13413
13409
  }
13414
13410
  function cmdResolveModel(cwd, agentType, raw) {
13415
- if (!agentType) error("agent-type required");
13411
+ if (!agentType) return cmdErr("agent-type required");
13416
13412
  const profile = loadConfig(cwd).model_profile || "balanced";
13417
13413
  const agentModels = MODEL_PROFILES[agentType];
13418
- if (!agentModels) {
13419
- output({
13420
- model: "sonnet",
13421
- profile,
13422
- unknown_agent: true
13423
- }, raw, "sonnet");
13424
- return;
13425
- }
13414
+ if (!agentModels) return cmdOk({
13415
+ model: "sonnet",
13416
+ profile,
13417
+ unknown_agent: true
13418
+ }, raw ? "sonnet" : void 0);
13426
13419
  const resolved = agentModels[profile] || agentModels["balanced"] || "sonnet";
13427
13420
  const model = resolved === "opus" ? "inherit" : resolved;
13428
- output({
13421
+ return cmdOk({
13429
13422
  model,
13430
13423
  profile
13431
- }, raw, model);
13424
+ }, raw ? model : void 0);
13432
13425
  }
13433
13426
  async function cmdCommit(cwd, message, files, raw, amend) {
13434
- if (!message && !amend) error("commit message required");
13435
- if (!loadConfig(cwd).commit_docs) {
13436
- output({
13437
- committed: false,
13438
- hash: null,
13439
- reason: "skipped_commit_docs_false"
13440
- }, raw, "skipped");
13441
- return;
13442
- }
13443
- if (await isGitIgnored(cwd, ".planning")) {
13444
- output({
13445
- committed: false,
13446
- hash: null,
13447
- reason: "skipped_gitignored"
13448
- }, raw, "skipped");
13449
- return;
13450
- }
13427
+ if (!message && !amend) return cmdErr("commit message required");
13428
+ if (!loadConfig(cwd).commit_docs) return cmdOk({
13429
+ committed: false,
13430
+ hash: null,
13431
+ reason: "skipped_commit_docs_false"
13432
+ }, raw ? "skipped" : void 0);
13433
+ if (await isGitIgnored(cwd, ".planning")) return cmdOk({
13434
+ committed: false,
13435
+ hash: null,
13436
+ reason: "skipped_gitignored"
13437
+ }, raw ? "skipped" : void 0);
13451
13438
  const filesToStage = files && files.length > 0 ? files : [".planning/"];
13452
13439
  for (const file of filesToStage) await execGit(cwd, ["add", file]);
13453
13440
  const commitResult = await execGit(cwd, amend ? [
@@ -13460,21 +13447,17 @@ async function cmdCommit(cwd, message, files, raw, amend) {
13460
13447
  message
13461
13448
  ]);
13462
13449
  if (commitResult.exitCode !== 0) {
13463
- if (commitResult.stdout.includes("nothing to commit") || commitResult.stderr.includes("nothing to commit")) {
13464
- output({
13465
- committed: false,
13466
- hash: null,
13467
- reason: "nothing_to_commit"
13468
- }, raw, "nothing");
13469
- return;
13470
- }
13471
- output({
13450
+ if (commitResult.stdout.includes("nothing to commit") || commitResult.stderr.includes("nothing to commit")) return cmdOk({
13451
+ committed: false,
13452
+ hash: null,
13453
+ reason: "nothing_to_commit"
13454
+ }, raw ? "nothing" : void 0);
13455
+ return cmdOk({
13472
13456
  committed: false,
13473
13457
  hash: null,
13474
13458
  reason: "nothing_to_commit",
13475
13459
  error: commitResult.stderr
13476
- }, raw, "nothing");
13477
- return;
13460
+ }, raw ? "nothing" : void 0);
13478
13461
  }
13479
13462
  const hashResult = await execGit(cwd, [
13480
13463
  "rev-parse",
@@ -13482,22 +13465,19 @@ async function cmdCommit(cwd, message, files, raw, amend) {
13482
13465
  "HEAD"
13483
13466
  ]);
13484
13467
  const hash = hashResult.exitCode === 0 ? hashResult.stdout : null;
13485
- output({
13468
+ return cmdOk({
13486
13469
  committed: true,
13487
13470
  hash,
13488
13471
  reason: "committed"
13489
- }, raw, hash || "committed");
13472
+ }, raw ? hash || "committed" : void 0);
13490
13473
  }
13491
13474
  function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
13492
- if (!summaryPath) error("summary-path required for summary-extract");
13475
+ if (!summaryPath) return cmdErr("summary-path required for summary-extract");
13493
13476
  const fullPath = node_path.default.join(cwd, summaryPath);
13494
- if (!node_fs.default.existsSync(fullPath)) {
13495
- output({
13496
- error: "File not found",
13497
- path: summaryPath
13498
- }, raw);
13499
- return;
13500
- }
13477
+ if (!node_fs.default.existsSync(fullPath)) return cmdOk({
13478
+ error: "File not found",
13479
+ path: summaryPath
13480
+ });
13501
13481
  const fm = extractFrontmatter(node_fs.default.readFileSync(fullPath, "utf-8"));
13502
13482
  const parseDecisions = (decisionsList) => {
13503
13483
  if (!decisionsList || !Array.isArray(decisionsList)) return [];
@@ -13526,27 +13506,20 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
13526
13506
  if (fields && fields.length > 0) {
13527
13507
  const filtered = { path: summaryPath };
13528
13508
  for (const field of fields) if (fullResult[field] !== void 0) filtered[field] = fullResult[field];
13529
- output(filtered, raw);
13530
- return;
13509
+ return cmdOk(filtered);
13531
13510
  }
13532
- output(fullResult, raw);
13511
+ return cmdOk(fullResult);
13533
13512
  }
13534
13513
  async function cmdWebsearch(query, options, raw) {
13535
13514
  const apiKey = process.env.BRAVE_API_KEY;
13536
- if (!apiKey) {
13537
- output({
13538
- available: false,
13539
- reason: "BRAVE_API_KEY not set"
13540
- }, raw, "");
13541
- return;
13542
- }
13543
- if (!query) {
13544
- output({
13545
- available: false,
13546
- error: "Query required"
13547
- }, raw, "");
13548
- return;
13549
- }
13515
+ if (!apiKey) return cmdOk({
13516
+ available: false,
13517
+ reason: "BRAVE_API_KEY not set"
13518
+ }, raw ? "" : void 0);
13519
+ if (!query) return cmdOk({
13520
+ available: false,
13521
+ error: "Query required"
13522
+ }, raw ? "" : void 0);
13550
13523
  const params = new URLSearchParams({
13551
13524
  q: query,
13552
13525
  count: String(options.limit || 10),
@@ -13560,30 +13533,28 @@ async function cmdWebsearch(query, options, raw) {
13560
13533
  Accept: "application/json",
13561
13534
  "X-Subscription-Token": apiKey
13562
13535
  } });
13563
- if (!response.ok) {
13564
- output({
13565
- available: false,
13566
- error: `API error: ${response.status}`
13567
- }, raw, "");
13568
- return;
13569
- }
13536
+ if (!response.ok) return cmdOk({
13537
+ available: false,
13538
+ error: `API error: ${response.status}`
13539
+ }, raw ? "" : void 0);
13570
13540
  const results = ((await response.json()).web?.results || []).map((r) => ({
13571
13541
  title: r.title,
13572
13542
  url: r.url,
13573
13543
  description: r.description,
13574
13544
  age: r.age || null
13575
13545
  }));
13576
- output({
13546
+ return cmdOk({
13577
13547
  available: true,
13578
13548
  query,
13579
13549
  count: results.length,
13580
13550
  results
13581
- }, raw, results.map((r) => `${r.title}\n${r.url}\n${r.description}`).join("\n\n"));
13551
+ }, raw ? results.map((r) => `${r.title}\n${r.url}\n${r.description}`).join("\n\n") : void 0);
13582
13552
  } catch (err) {
13583
- output({
13553
+ rethrowCliSignals(err);
13554
+ return cmdOk({
13584
13555
  available: false,
13585
13556
  error: err.message
13586
- }, raw, "");
13557
+ }, raw ? "" : void 0);
13587
13558
  }
13588
13559
  }
13589
13560
  function cmdProgressRender(cwd, format, raw) {
@@ -13631,17 +13602,17 @@ function cmdProgressRender(cwd, format, raw) {
13631
13602
  out += `| Phase | Name | Plans | Status |\n`;
13632
13603
  out += `|-------|------|-------|--------|\n`;
13633
13604
  for (const p of phases) out += `| ${p.number} | ${p.name} | ${p.summaries}/${p.plans} | ${p.status} |\n`;
13634
- output({ rendered: out }, raw, out);
13605
+ return cmdOk({ rendered: out }, raw ? out : void 0);
13635
13606
  } else if (format === "bar") {
13636
13607
  const barWidth = 20;
13637
13608
  const filled = Math.round(percent / 100 * barWidth);
13638
13609
  const text = `[${"█".repeat(filled) + "░".repeat(barWidth - filled)}] ${totalSummaries}/${totalPlans} plans (${percent}%)`;
13639
- output({
13610
+ return cmdOk({
13640
13611
  bar: text,
13641
13612
  percent,
13642
13613
  completed: totalSummaries,
13643
13614
  total: totalPlans
13644
- }, raw, text);
13615
+ }, raw ? text : void 0);
13645
13616
  } else if (format === "phase-bars") {
13646
13617
  const doneCount = phases.filter((p) => p.status === "Complete").length;
13647
13618
  const inProgressCount = phases.filter((p) => p.status === "In Progress").length;
@@ -13661,39 +13632,39 @@ function cmdProgressRender(cwd, format, raw) {
13661
13632
  lines.push(line);
13662
13633
  }
13663
13634
  const rendered = lines.join("\n");
13664
- output({
13635
+ return cmdOk({
13665
13636
  rendered,
13666
13637
  done: doneCount,
13667
13638
  in_progress: inProgressCount,
13668
13639
  total: totalCount,
13669
13640
  percent
13670
- }, raw, rendered);
13671
- } else output({
13641
+ }, raw ? rendered : void 0);
13642
+ } else return cmdOk({
13672
13643
  milestone_version: milestone.version,
13673
13644
  milestone_name: milestone.name,
13674
13645
  phases,
13675
13646
  total_plans: totalPlans,
13676
13647
  total_summaries: totalSummaries,
13677
13648
  percent
13678
- }, raw);
13649
+ });
13679
13650
  }
13680
13651
  function cmdTodoComplete(cwd, filename, raw) {
13681
- if (!filename) error("filename required for todo complete");
13652
+ if (!filename) return cmdErr("filename required for todo complete");
13682
13653
  const pendingDir = planningPath(cwd, "todos", "pending");
13683
13654
  const completedDir = planningPath(cwd, "todos", "completed");
13684
13655
  const sourcePath = node_path.default.join(pendingDir, filename);
13685
- if (!node_fs.default.existsSync(sourcePath)) error(`Todo not found: ${filename}`);
13656
+ if (!node_fs.default.existsSync(sourcePath)) return cmdErr(`Todo not found: ${filename}`);
13686
13657
  node_fs.default.mkdirSync(completedDir, { recursive: true });
13687
13658
  let content = node_fs.default.readFileSync(sourcePath, "utf-8");
13688
13659
  const today = todayISO();
13689
13660
  content = `completed: ${today}\n` + content;
13690
13661
  node_fs.default.writeFileSync(node_path.default.join(completedDir, filename), content, "utf-8");
13691
13662
  node_fs.default.unlinkSync(sourcePath);
13692
- output({
13663
+ return cmdOk({
13693
13664
  completed: true,
13694
13665
  file: filename,
13695
13666
  date: today
13696
- }, raw, "completed");
13667
+ }, raw ? "completed" : void 0);
13697
13668
  }
13698
13669
  function cmdScaffold(cwd, type, options, raw) {
13699
13670
  const { phase, name } = options;
@@ -13701,7 +13672,7 @@ function cmdScaffold(cwd, type, options, raw) {
13701
13672
  const today = todayISO();
13702
13673
  const phaseInfo = phase ? findPhaseInternal(cwd, phase) : null;
13703
13674
  const phaseDir = phaseInfo ? node_path.default.join(cwd, phaseInfo.directory) : null;
13704
- if (phase && !phaseDir && type !== "phase-dir") error(`Phase ${phase} directory not found`);
13675
+ if (phase && !phaseDir && type !== "phase-dir") return cmdErr(`Phase ${phase} directory not found`);
13705
13676
  let filePath;
13706
13677
  let content;
13707
13678
  switch (type) {
@@ -13718,37 +13689,31 @@ function cmdScaffold(cwd, type, options, raw) {
13718
13689
  content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || "Unnamed"}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || "Unnamed"} — Verification\n\n## Goal-Backward Verification\n\n**Phase Goal:** [From ROADMAP.md]\n\n## Checks\n\n| # | Requirement | Status | Evidence |\n|---|------------|--------|----------|\n\n## Result\n\n_Pending verification_\n`;
13719
13690
  break;
13720
13691
  case "phase-dir": {
13721
- if (!phase || !name) error("phase and name required for phase-dir scaffold");
13692
+ if (!phase || !name) return cmdErr("phase and name required for phase-dir scaffold");
13722
13693
  const dirName = `${padded}-${generateSlugInternal(name)}`;
13723
13694
  const phasesParent = phasesPath(cwd);
13724
13695
  node_fs.default.mkdirSync(phasesParent, { recursive: true });
13725
13696
  const dirPath = node_path.default.join(phasesParent, dirName);
13726
13697
  node_fs.default.mkdirSync(dirPath, { recursive: true });
13727
- output({
13698
+ return cmdOk({
13728
13699
  created: true,
13729
13700
  directory: `.planning/phases/${dirName}`,
13730
13701
  path: dirPath
13731
- }, raw, dirPath);
13732
- return;
13702
+ }, raw ? dirPath : void 0);
13733
13703
  }
13734
- default:
13735
- error(`Unknown scaffold type: ${type}. Available: context, uat, verification, phase-dir`);
13736
- return;
13737
- }
13738
- if (node_fs.default.existsSync(filePath)) {
13739
- output({
13740
- created: false,
13741
- reason: "already_exists",
13742
- path: filePath
13743
- }, raw, "exists");
13744
- return;
13704
+ default: return cmdErr(`Unknown scaffold type: ${type}. Available: context, uat, verification, phase-dir`);
13745
13705
  }
13706
+ if (node_fs.default.existsSync(filePath)) return cmdOk({
13707
+ created: false,
13708
+ reason: "already_exists",
13709
+ path: filePath
13710
+ }, raw ? "exists" : void 0);
13746
13711
  node_fs.default.writeFileSync(filePath, content, "utf-8");
13747
13712
  const relPath = node_path.default.relative(cwd, filePath);
13748
- output({
13713
+ return cmdOk({
13749
13714
  created: true,
13750
13715
  path: relPath
13751
- }, raw, relPath);
13716
+ }, raw ? relPath : void 0);
13752
13717
  }
13753
13718
 
13754
13719
  //#endregion
@@ -13758,27 +13723,24 @@ function cmdScaffold(cwd, type, options, raw) {
13758
13723
  *
13759
13724
  * Ported from maxsim/bin/lib/verify.cjs
13760
13725
  */
13761
- async function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
13762
- if (!summaryPath) error("summary-path required");
13726
+ async function cmdVerifySummary(cwd, summaryPath, checkFileCount) {
13727
+ if (!summaryPath) return cmdErr("summary-path required");
13763
13728
  const fullPath = node_path.default.join(cwd, summaryPath);
13764
13729
  const checkCount = checkFileCount || 2;
13765
- if (!node_fs.default.existsSync(fullPath)) {
13766
- output({
13767
- passed: false,
13768
- checks: {
13769
- summary_exists: false,
13770
- files_created: {
13771
- checked: 0,
13772
- found: 0,
13773
- missing: []
13774
- },
13775
- commits_exist: false,
13776
- self_check: "not_found"
13730
+ if (!node_fs.default.existsSync(fullPath)) return cmdOk({
13731
+ passed: false,
13732
+ checks: {
13733
+ summary_exists: false,
13734
+ files_created: {
13735
+ checked: 0,
13736
+ found: 0,
13737
+ missing: []
13777
13738
  },
13778
- errors: ["SUMMARY.md not found"]
13779
- }, raw, "failed");
13780
- return;
13781
- }
13739
+ commits_exist: false,
13740
+ self_check: "not_found"
13741
+ },
13742
+ errors: ["SUMMARY.md not found"]
13743
+ }, "failed");
13782
13744
  const content = node_fs.default.readFileSync(fullPath, "utf-8");
13783
13745
  const errors = [];
13784
13746
  const mentionedFiles = /* @__PURE__ */ new Set();
@@ -13828,22 +13790,19 @@ async function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
13828
13790
  self_check: selfCheck
13829
13791
  };
13830
13792
  const passed = missing.length === 0 && selfCheck !== "failed";
13831
- output({
13793
+ return cmdOk({
13832
13794
  passed,
13833
13795
  checks,
13834
13796
  errors
13835
- }, raw, passed ? "passed" : "failed");
13797
+ }, passed ? "passed" : "failed");
13836
13798
  }
13837
- function cmdVerifyPlanStructure(cwd, filePath, raw) {
13838
- if (!filePath) error("file path required");
13799
+ function cmdVerifyPlanStructure(cwd, filePath) {
13800
+ if (!filePath) return cmdErr("file path required");
13839
13801
  const content = safeReadFile(node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath));
13840
- if (!content) {
13841
- output({
13842
- error: "File not found",
13843
- path: filePath
13844
- }, raw);
13845
- return;
13846
- }
13802
+ if (!content) return cmdOk({
13803
+ error: "File not found",
13804
+ path: filePath
13805
+ });
13847
13806
  const fm = extractFrontmatter(content);
13848
13807
  const errors = [];
13849
13808
  const warnings = [];
@@ -13884,25 +13843,22 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
13884
13843
  if (tasks.length === 0) warnings.push("No <task> elements found");
13885
13844
  if (fm.wave && parseInt(String(fm.wave)) > 1 && (!fm.depends_on || Array.isArray(fm.depends_on) && fm.depends_on.length === 0)) warnings.push("Wave > 1 but depends_on is empty");
13886
13845
  if (/<task\s+type=["']?checkpoint/.test(content) && fm.autonomous !== "false" && fm.autonomous !== false) errors.push("Has checkpoint tasks but autonomous is not false");
13887
- output({
13846
+ return cmdOk({
13888
13847
  valid: errors.length === 0,
13889
13848
  errors,
13890
13849
  warnings,
13891
13850
  task_count: tasks.length,
13892
13851
  tasks,
13893
13852
  frontmatter_fields: Object.keys(fm)
13894
- }, raw, errors.length === 0 ? "valid" : "invalid");
13853
+ }, errors.length === 0 ? "valid" : "invalid");
13895
13854
  }
13896
- function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
13897
- if (!phase) error("phase required");
13855
+ function cmdVerifyPhaseCompleteness(cwd, phase) {
13856
+ if (!phase) return cmdErr("phase required");
13898
13857
  const phaseInfo = findPhaseInternal(cwd, phase);
13899
- if (!phaseInfo) {
13900
- output({
13901
- error: "Phase not found",
13902
- phase
13903
- }, raw);
13904
- return;
13905
- }
13858
+ if (!phaseInfo) return cmdOk({
13859
+ error: "Phase not found",
13860
+ phase
13861
+ });
13906
13862
  const errors = [];
13907
13863
  const warnings = [];
13908
13864
  const phaseDir = node_path.default.join(cwd, phaseInfo.directory);
@@ -13910,8 +13866,7 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
13910
13866
  try {
13911
13867
  files = node_fs.default.readdirSync(phaseDir);
13912
13868
  } catch {
13913
- output({ error: "Cannot read phase directory" }, raw);
13914
- return;
13869
+ return cmdOk({ error: "Cannot read phase directory" });
13915
13870
  }
13916
13871
  const plans = files.filter((f) => isPlanFile(f));
13917
13872
  const summaries = files.filter((f) => isSummaryFile(f));
@@ -13921,7 +13876,7 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
13921
13876
  if (incompletePlans.length > 0) errors.push(`Plans without summaries: ${incompletePlans.join(", ")}`);
13922
13877
  const orphanSummaries = [...summaryIds].filter((id) => !planIds.has(id));
13923
13878
  if (orphanSummaries.length > 0) warnings.push(`Summaries without plans: ${orphanSummaries.join(", ")}`);
13924
- output({
13879
+ return cmdOk({
13925
13880
  complete: errors.length === 0,
13926
13881
  phase: phaseInfo.phase_number,
13927
13882
  plan_count: plans.length,
@@ -13930,18 +13885,15 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
13930
13885
  orphan_summaries: orphanSummaries,
13931
13886
  errors,
13932
13887
  warnings
13933
- }, raw, errors.length === 0 ? "complete" : "incomplete");
13888
+ }, errors.length === 0 ? "complete" : "incomplete");
13934
13889
  }
13935
- function cmdVerifyReferences(cwd, filePath, raw) {
13936
- if (!filePath) error("file path required");
13890
+ function cmdVerifyReferences(cwd, filePath) {
13891
+ if (!filePath) return cmdErr("file path required");
13937
13892
  const content = safeReadFile(node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath));
13938
- if (!content) {
13939
- output({
13940
- error: "File not found",
13941
- path: filePath
13942
- }, raw);
13943
- return;
13944
- }
13893
+ if (!content) return cmdOk({
13894
+ error: "File not found",
13895
+ path: filePath
13896
+ });
13945
13897
  const found = [];
13946
13898
  const missing = [];
13947
13899
  const atRefs = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
@@ -13960,15 +13912,15 @@ function cmdVerifyReferences(cwd, filePath, raw) {
13960
13912
  if (node_fs.default.existsSync(resolved)) found.push(cleanRef);
13961
13913
  else missing.push(cleanRef);
13962
13914
  }
13963
- output({
13915
+ return cmdOk({
13964
13916
  valid: missing.length === 0,
13965
13917
  found: found.length,
13966
13918
  missing,
13967
13919
  total: found.length + missing.length
13968
- }, raw, missing.length === 0 ? "valid" : "invalid");
13920
+ }, missing.length === 0 ? "valid" : "invalid");
13969
13921
  }
13970
- async function cmdVerifyCommits(cwd, hashes, raw) {
13971
- if (!hashes || hashes.length === 0) error("At least one commit hash required");
13922
+ async function cmdVerifyCommits(cwd, hashes) {
13923
+ if (!hashes || hashes.length === 0) return cmdErr("At least one commit hash required");
13972
13924
  const valid = [];
13973
13925
  const invalid = [];
13974
13926
  for (const hash of hashes) {
@@ -13980,31 +13932,25 @@ async function cmdVerifyCommits(cwd, hashes, raw) {
13980
13932
  if (result.exitCode === 0 && result.stdout.trim() === "commit") valid.push(hash);
13981
13933
  else invalid.push(hash);
13982
13934
  }
13983
- output({
13935
+ return cmdOk({
13984
13936
  all_valid: invalid.length === 0,
13985
13937
  valid,
13986
13938
  invalid,
13987
13939
  total: hashes.length
13988
- }, raw, invalid.length === 0 ? "valid" : "invalid");
13940
+ }, invalid.length === 0 ? "valid" : "invalid");
13989
13941
  }
13990
- function cmdVerifyArtifacts(cwd, planFilePath, raw) {
13991
- if (!planFilePath) error("plan file path required");
13942
+ function cmdVerifyArtifacts(cwd, planFilePath) {
13943
+ if (!planFilePath) return cmdErr("plan file path required");
13992
13944
  const content = safeReadFile(node_path.default.isAbsolute(planFilePath) ? planFilePath : node_path.default.join(cwd, planFilePath));
13993
- if (!content) {
13994
- output({
13995
- error: "File not found",
13996
- path: planFilePath
13997
- }, raw);
13998
- return;
13999
- }
13945
+ if (!content) return cmdOk({
13946
+ error: "File not found",
13947
+ path: planFilePath
13948
+ });
14000
13949
  const artifacts = parseMustHavesBlock(content, "artifacts");
14001
- if (artifacts.length === 0) {
14002
- output({
14003
- error: "No must_haves.artifacts found in frontmatter",
14004
- path: planFilePath
14005
- }, raw);
14006
- return;
14007
- }
13950
+ if (artifacts.length === 0) return cmdOk({
13951
+ error: "No must_haves.artifacts found in frontmatter",
13952
+ path: planFilePath
13953
+ });
14008
13954
  const results = [];
14009
13955
  for (const artifact of artifacts) {
14010
13956
  if (typeof artifact === "string") continue;
@@ -14033,31 +13979,25 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
14033
13979
  results.push(check);
14034
13980
  }
14035
13981
  const passed = results.filter((r) => r.passed).length;
14036
- output({
13982
+ return cmdOk({
14037
13983
  all_passed: passed === results.length,
14038
13984
  passed,
14039
13985
  total: results.length,
14040
13986
  artifacts: results
14041
- }, raw, passed === results.length ? "valid" : "invalid");
13987
+ }, passed === results.length ? "valid" : "invalid");
14042
13988
  }
14043
- function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
14044
- if (!planFilePath) error("plan file path required");
13989
+ function cmdVerifyKeyLinks(cwd, planFilePath) {
13990
+ if (!planFilePath) return cmdErr("plan file path required");
14045
13991
  const content = safeReadFile(node_path.default.isAbsolute(planFilePath) ? planFilePath : node_path.default.join(cwd, planFilePath));
14046
- if (!content) {
14047
- output({
14048
- error: "File not found",
14049
- path: planFilePath
14050
- }, raw);
14051
- return;
14052
- }
13992
+ if (!content) return cmdOk({
13993
+ error: "File not found",
13994
+ path: planFilePath
13995
+ });
14053
13996
  const keyLinks = parseMustHavesBlock(content, "key_links");
14054
- if (keyLinks.length === 0) {
14055
- output({
14056
- error: "No must_haves.key_links found in frontmatter",
14057
- path: planFilePath
14058
- }, raw);
14059
- return;
14060
- }
13997
+ if (keyLinks.length === 0) return cmdOk({
13998
+ error: "No must_haves.key_links found in frontmatter",
13999
+ path: planFilePath
14000
+ });
14061
14001
  const results = [];
14062
14002
  for (const link of keyLinks) {
14063
14003
  if (typeof link === "string") continue;
@@ -14093,26 +14033,25 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
14093
14033
  results.push(check);
14094
14034
  }
14095
14035
  const verified = results.filter((r) => r.verified).length;
14096
- output({
14036
+ return cmdOk({
14097
14037
  all_verified: verified === results.length,
14098
14038
  verified,
14099
14039
  total: results.length,
14100
14040
  links: results
14101
- }, raw, verified === results.length ? "valid" : "invalid");
14041
+ }, verified === results.length ? "valid" : "invalid");
14102
14042
  }
14103
- function cmdValidateConsistency(cwd, raw) {
14043
+ function cmdValidateConsistency(cwd) {
14104
14044
  const rmPath = roadmapPath(cwd);
14105
14045
  const phasesDir = phasesPath(cwd);
14106
14046
  const errors = [];
14107
14047
  const warnings = [];
14108
14048
  if (!node_fs.default.existsSync(rmPath)) {
14109
14049
  errors.push("ROADMAP.md not found");
14110
- output({
14050
+ return cmdOk({
14111
14051
  passed: false,
14112
14052
  errors,
14113
14053
  warnings
14114
- }, raw, "failed");
14115
- return;
14054
+ }, "failed");
14116
14055
  }
14117
14056
  const roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
14118
14057
  const roadmapPhases = /* @__PURE__ */ new Set();
@@ -14164,14 +14103,14 @@ function cmdValidateConsistency(cwd, raw) {
14164
14103
  debugLog(e);
14165
14104
  }
14166
14105
  const passed = errors.length === 0;
14167
- output({
14106
+ return cmdOk({
14168
14107
  passed,
14169
14108
  errors,
14170
14109
  warnings,
14171
14110
  warning_count: warnings.length
14172
- }, raw, passed ? "passed" : "failed");
14111
+ }, passed ? "passed" : "failed");
14173
14112
  }
14174
- function cmdValidateHealth(cwd, options, raw) {
14113
+ function cmdValidateHealth(cwd, options) {
14175
14114
  const planningDir = planningPath(cwd);
14176
14115
  const projectPath = planningPath(cwd, "PROJECT.md");
14177
14116
  const rmPath = roadmapPath(cwd);
@@ -14195,14 +14134,13 @@ function cmdValidateHealth(cwd, options, raw) {
14195
14134
  };
14196
14135
  if (!node_fs.default.existsSync(planningDir)) {
14197
14136
  addIssue("error", "E001", ".planning/ directory not found", "Run /maxsim:new-project to initialize");
14198
- output({
14137
+ return cmdOk({
14199
14138
  status: "broken",
14200
14139
  errors,
14201
14140
  warnings,
14202
14141
  info,
14203
14142
  repairable_count: 0
14204
- }, raw);
14205
- return;
14143
+ });
14206
14144
  }
14207
14145
  if (!node_fs.default.existsSync(projectPath)) addIssue("error", "E002", "PROJECT.md not found", "Run /maxsim:new-project to create");
14208
14146
  else {
@@ -14362,14 +14300,14 @@ function cmdValidateHealth(cwd, options, raw) {
14362
14300
  else if (warnings.length > 0) status = "degraded";
14363
14301
  else status = "healthy";
14364
14302
  const repairableCount = errors.filter((e) => e.repairable).length + warnings.filter((w) => w.repairable).length;
14365
- output({
14303
+ return cmdOk({
14366
14304
  status,
14367
14305
  errors,
14368
14306
  warnings,
14369
14307
  info,
14370
14308
  repairable_count: repairableCount,
14371
14309
  repairs_performed: repairActions.length > 0 ? repairActions : void 0
14372
- }, raw);
14310
+ });
14373
14311
  }
14374
14312
 
14375
14313
  //#endregion
@@ -14435,7 +14373,7 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
14435
14373
  if (dm) existingDecimals.push(parseInt(dm[1], 10));
14436
14374
  }
14437
14375
  } catch (e) {
14438
- debugLog(e);
14376
+ debugLog("phase-insert-decimal-scan-failed", e);
14439
14377
  }
14440
14378
  const decimalPhase = `${normalizedBase}.${existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1}`;
14441
14379
  const dirName = `${decimalPhase}-${slug}`;
@@ -14481,7 +14419,9 @@ function phaseCompleteCore(cwd, phaseNum) {
14481
14419
  roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
14482
14420
  const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i");
14483
14421
  roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
14422
+ debugLog("phase-complete-write", `writing ROADMAP.md for phase ${phaseNum}`);
14484
14423
  node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
14424
+ debugLog("phase-complete-write", `ROADMAP.md updated for phase ${phaseNum}`);
14485
14425
  const reqPath = planningPath(cwd, "REQUIREMENTS.md");
14486
14426
  if (node_fs.default.existsSync(reqPath)) {
14487
14427
  const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${escapePhaseNum(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, "i"));
@@ -14492,7 +14432,9 @@ function phaseCompleteCore(cwd, phaseNum) {
14492
14432
  reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, "gi"), "$1x$2");
14493
14433
  reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, "gi"), "$1 Complete $2");
14494
14434
  }
14435
+ debugLog("phase-complete-write", `writing REQUIREMENTS.md for phase ${phaseNum}`);
14495
14436
  node_fs.default.writeFileSync(reqPath, reqContent, "utf-8");
14437
+ debugLog("phase-complete-write", `REQUIREMENTS.md updated for phase ${phaseNum}`);
14496
14438
  requirementsUpdated = true;
14497
14439
  }
14498
14440
  }
@@ -14514,7 +14456,7 @@ function phaseCompleteCore(cwd, phaseNum) {
14514
14456
  }
14515
14457
  }
14516
14458
  } catch (e) {
14517
- debugLog(e);
14459
+ debugLog("phase-complete-next-phase-scan-failed", e);
14518
14460
  }
14519
14461
  if (node_fs.default.existsSync(stPath)) {
14520
14462
  let stateContent = node_fs.default.readFileSync(stPath, "utf-8");
@@ -14524,7 +14466,9 @@ function phaseCompleteCore(cwd, phaseNum) {
14524
14466
  stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
14525
14467
  stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
14526
14468
  stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ""}`);
14469
+ debugLog("phase-complete-write", `writing STATE.md for phase ${phaseNum}`);
14527
14470
  node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
14471
+ debugLog("phase-complete-write", `STATE.md updated for phase ${phaseNum}`);
14528
14472
  }
14529
14473
  return {
14530
14474
  completed_phase: phaseNum,
@@ -14539,22 +14483,21 @@ function phaseCompleteCore(cwd, phaseNum) {
14539
14483
  requirements_updated: requirementsUpdated
14540
14484
  };
14541
14485
  }
14542
- function cmdPhasesList(cwd, options, raw) {
14486
+ async function cmdPhasesList(cwd, options) {
14543
14487
  const phasesDirPath = phasesPath(cwd);
14544
- const { type, phase, includeArchived } = options;
14545
- if (!node_fs.default.existsSync(phasesDirPath)) {
14546
- if (type) output({
14547
- files: [],
14548
- count: 0
14549
- }, raw, "");
14550
- else output({
14551
- directories: [],
14552
- count: 0
14553
- }, raw, "");
14554
- return;
14555
- }
14488
+ const { type, phase, includeArchived, offset, limit } = options;
14489
+ if (!node_fs.default.existsSync(phasesDirPath)) if (type) return cmdOk({
14490
+ files: [],
14491
+ count: 0,
14492
+ total: 0
14493
+ }, "");
14494
+ else return cmdOk({
14495
+ directories: [],
14496
+ count: 0,
14497
+ total: 0
14498
+ }, "");
14556
14499
  try {
14557
- let dirs = listSubDirs(phasesDirPath);
14500
+ let dirs = await listSubDirsAsync(phasesDirPath);
14558
14501
  if (includeArchived) {
14559
14502
  const archived = getArchivedPhaseDirs(cwd);
14560
14503
  for (const a of archived) dirs.push(`${a.name} [${a.milestone}]`);
@@ -14563,55 +14506,53 @@ function cmdPhasesList(cwd, options, raw) {
14563
14506
  if (phase) {
14564
14507
  const normalized = normalizePhaseName(phase);
14565
14508
  const match = dirs.find((d) => d.startsWith(normalized));
14566
- if (!match) {
14567
- output({
14568
- files: [],
14569
- count: 0,
14570
- phase_dir: null,
14571
- error: "Phase not found"
14572
- }, raw, "");
14573
- return;
14574
- }
14509
+ if (!match) return cmdOk({
14510
+ files: [],
14511
+ count: 0,
14512
+ total: 0,
14513
+ phase_dir: null,
14514
+ error: "Phase not found"
14515
+ }, "");
14575
14516
  dirs = [match];
14576
14517
  }
14577
14518
  if (type) {
14578
- const files = [];
14579
- for (const dir of dirs) {
14519
+ const files = (await Promise.all(dirs.map(async (dir) => {
14580
14520
  const dirPath = node_path.default.join(phasesDirPath, dir);
14581
- const dirFiles = node_fs.default.readdirSync(dirPath);
14521
+ const dirFiles = await node_fs.default.promises.readdir(dirPath);
14582
14522
  let filtered;
14583
14523
  if (type === "plans") filtered = dirFiles.filter(isPlanFile);
14584
14524
  else if (type === "summaries") filtered = dirFiles.filter(isSummaryFile);
14585
14525
  else filtered = dirFiles;
14586
- files.push(...filtered.sort());
14587
- }
14588
- output({
14526
+ return filtered.sort();
14527
+ }))).flat();
14528
+ return cmdOk({
14589
14529
  files,
14590
14530
  count: files.length,
14531
+ total: files.length,
14591
14532
  phase_dir: phase ? dirs[0].replace(/^\d+(?:\.\d+)?-?/, "") : null
14592
- }, raw, files.join("\n"));
14593
- return;
14594
- }
14595
- output({
14596
- directories: dirs,
14597
- count: dirs.length
14598
- }, raw, dirs.join("\n"));
14533
+ }, files.join("\n"));
14534
+ }
14535
+ const total = dirs.length;
14536
+ const start = offset ?? 0;
14537
+ const paginated = limit !== void 0 ? dirs.slice(start, start + limit) : dirs.slice(start);
14538
+ return cmdOk({
14539
+ directories: paginated,
14540
+ count: paginated.length,
14541
+ total
14542
+ }, paginated.join("\n"));
14599
14543
  } catch (e) {
14600
- error("Failed to list phases: " + e.message);
14544
+ return cmdErr("Failed to list phases: " + e.message);
14601
14545
  }
14602
14546
  }
14603
- function cmdPhaseNextDecimal(cwd, basePhase, raw) {
14547
+ function cmdPhaseNextDecimal(cwd, basePhase) {
14604
14548
  const phasesDirPath = phasesPath(cwd);
14605
14549
  const normalized = normalizePhaseName(basePhase);
14606
- if (!node_fs.default.existsSync(phasesDirPath)) {
14607
- output({
14608
- found: false,
14609
- base_phase: normalized,
14610
- next: `${normalized}.1`,
14611
- existing: []
14612
- }, raw, `${normalized}.1`);
14613
- return;
14614
- }
14550
+ if (!node_fs.default.existsSync(phasesDirPath)) return cmdOk({
14551
+ found: false,
14552
+ base_phase: normalized,
14553
+ next: `${normalized}.1`,
14554
+ existing: []
14555
+ }, `${normalized}.1`);
14615
14556
  try {
14616
14557
  const dirs = listSubDirs(phasesDirPath);
14617
14558
  const baseExists = dirs.some((d) => d.startsWith(normalized + "-") || d === normalized);
@@ -14630,18 +14571,18 @@ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
14630
14571
  const lastDecimal = existingDecimals[existingDecimals.length - 1];
14631
14572
  nextDecimal = `${normalized}.${parseInt(lastDecimal.split(".")[1], 10) + 1}`;
14632
14573
  }
14633
- output({
14574
+ return cmdOk({
14634
14575
  found: baseExists,
14635
14576
  base_phase: normalized,
14636
14577
  next: nextDecimal,
14637
14578
  existing: existingDecimals
14638
- }, raw, nextDecimal);
14579
+ }, nextDecimal);
14639
14580
  } catch (e) {
14640
- error("Failed to calculate next decimal phase: " + e.message);
14581
+ return cmdErr("Failed to calculate next decimal phase: " + e.message);
14641
14582
  }
14642
14583
  }
14643
- function cmdFindPhase(cwd, phase, raw) {
14644
- if (!phase) error("phase identifier required");
14584
+ function cmdFindPhase(cwd, phase) {
14585
+ if (!phase) return cmdErr("phase identifier required");
14645
14586
  const phasesDirPath = phasesPath(cwd);
14646
14587
  const normalized = normalizePhaseName(phase);
14647
14588
  const notFound = {
@@ -14654,10 +14595,7 @@ function cmdFindPhase(cwd, phase, raw) {
14654
14595
  };
14655
14596
  try {
14656
14597
  const match = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized));
14657
- if (!match) {
14658
- output(notFound, raw, "");
14659
- return;
14660
- }
14598
+ if (!match) return cmdOk(notFound, "");
14661
14599
  const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
14662
14600
  const phaseNumber = dirMatch ? dirMatch[1] : normalized;
14663
14601
  const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
@@ -14673,13 +14611,13 @@ function cmdFindPhase(cwd, phase, raw) {
14673
14611
  plans,
14674
14612
  summaries
14675
14613
  };
14676
- output(result, raw, result.directory);
14677
- } catch {
14678
- output(notFound, raw, "");
14614
+ return cmdOk(result, result.directory);
14615
+ } catch (e) {
14616
+ return cmdOk(notFound, "");
14679
14617
  }
14680
14618
  }
14681
- function cmdPhasePlanIndex(cwd, phase, raw) {
14682
- if (!phase) error("phase required for phase-plan-index");
14619
+ function cmdPhasePlanIndex(cwd, phase) {
14620
+ if (!phase) return cmdErr("phase required for phase-plan-index");
14683
14621
  const phasesDirPath = phasesPath(cwd);
14684
14622
  const normalized = normalizePhaseName(phase);
14685
14623
  let phaseDir = null;
@@ -14687,19 +14625,16 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
14687
14625
  const match = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized));
14688
14626
  if (match) phaseDir = node_path.default.join(phasesDirPath, match);
14689
14627
  } catch (e) {
14690
- debugLog(e);
14691
- }
14692
- if (!phaseDir) {
14693
- output({
14694
- phase: normalized,
14695
- error: "Phase not found",
14696
- plans: [],
14697
- waves: {},
14698
- incomplete: [],
14699
- has_checkpoints: false
14700
- }, raw);
14701
- return;
14628
+ debugLog("phase-plan-index-failed", e);
14702
14629
  }
14630
+ if (!phaseDir) return cmdOk({
14631
+ phase: normalized,
14632
+ error: "Phase not found",
14633
+ plans: [],
14634
+ waves: {},
14635
+ incomplete: [],
14636
+ has_checkpoints: false
14637
+ });
14703
14638
  const phaseFiles = node_fs.default.readdirSync(phaseDir);
14704
14639
  const planFiles = phaseFiles.filter(isPlanFile).sort();
14705
14640
  const summaryFiles = phaseFiles.filter(isSummaryFile);
@@ -14736,62 +14671,62 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
14736
14671
  if (!waves[waveKey]) waves[waveKey] = [];
14737
14672
  waves[waveKey].push(id);
14738
14673
  }
14739
- output({
14674
+ return cmdOk({
14740
14675
  phase: normalized,
14741
14676
  plans,
14742
14677
  waves,
14743
14678
  incomplete,
14744
14679
  has_checkpoints: hasCheckpoints
14745
- }, raw);
14680
+ });
14746
14681
  }
14747
- function cmdPhaseAdd(cwd, description, raw) {
14748
- if (!description) error("description required for phase add");
14682
+ function cmdPhaseAdd(cwd, description) {
14683
+ if (!description) return cmdErr("description required for phase add");
14749
14684
  try {
14750
14685
  const result = phaseAddCore(cwd, description, { includeStubs: false });
14751
- output({
14686
+ return cmdOk({
14752
14687
  phase_number: result.phase_number,
14753
14688
  padded: result.padded,
14754
14689
  name: result.description,
14755
14690
  slug: result.slug,
14756
14691
  directory: result.directory
14757
- }, raw, result.padded);
14692
+ }, result.padded);
14758
14693
  } catch (e) {
14759
- error(e.message);
14694
+ return cmdErr(e.message);
14760
14695
  }
14761
14696
  }
14762
- function cmdPhaseInsert(cwd, afterPhase, description, raw) {
14763
- if (!afterPhase || !description) error("after-phase and description required for phase insert");
14697
+ function cmdPhaseInsert(cwd, afterPhase, description) {
14698
+ if (!afterPhase || !description) return cmdErr("after-phase and description required for phase insert");
14764
14699
  try {
14765
14700
  const result = phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
14766
- output({
14701
+ return cmdOk({
14767
14702
  phase_number: result.phase_number,
14768
14703
  after_phase: result.after_phase,
14769
14704
  name: result.description,
14770
14705
  slug: result.slug,
14771
14706
  directory: result.directory
14772
- }, raw, result.phase_number);
14707
+ }, result.phase_number);
14773
14708
  } catch (e) {
14774
- error(e.message);
14709
+ return cmdErr(e.message);
14775
14710
  }
14776
14711
  }
14777
- function cmdPhaseRemove(cwd, targetPhase, options, raw) {
14778
- if (!targetPhase) error("phase number required for phase remove");
14712
+ function cmdPhaseRemove(cwd, targetPhase, options) {
14713
+ if (!targetPhase) return cmdErr("phase number required for phase remove");
14779
14714
  const rmPath = roadmapPath(cwd);
14780
14715
  const phasesDirPath = phasesPath(cwd);
14781
14716
  const force = options.force || false;
14782
- if (!node_fs.default.existsSync(rmPath)) error("ROADMAP.md not found");
14717
+ if (!node_fs.default.existsSync(rmPath)) return cmdErr("ROADMAP.md not found");
14783
14718
  const normalized = normalizePhaseName(targetPhase);
14784
14719
  const isDecimal = targetPhase.includes(".");
14785
14720
  let targetDir = null;
14786
14721
  try {
14787
14722
  targetDir = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized + "-") || d === normalized) || null;
14788
14723
  } catch (e) {
14789
- debugLog(e);
14724
+ debugLog("phase-remove-find-target-failed", e);
14790
14725
  }
14791
14726
  if (targetDir && !force) {
14792
14727
  const targetPath = node_path.default.join(phasesDirPath, targetDir);
14793
14728
  const summaries = node_fs.default.readdirSync(targetPath).filter(isSummaryFile);
14794
- if (summaries.length > 0) error(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
14729
+ if (summaries.length > 0) return cmdErr(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
14795
14730
  }
14796
14731
  if (targetDir) node_fs.default.rmSync(node_path.default.join(phasesDirPath, targetDir), {
14797
14732
  recursive: true,
@@ -14837,7 +14772,10 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
14837
14772
  }
14838
14773
  }
14839
14774
  } catch (e) {
14840
- debugLog(e);
14775
+ debugLog("phase-remove-decimal-rename-failed", {
14776
+ phase: targetPhase,
14777
+ error: errorMsg(e)
14778
+ });
14841
14779
  }
14842
14780
  } else {
14843
14781
  const removedInt = parseInt(normalized, 10);
@@ -14885,7 +14823,10 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
14885
14823
  }
14886
14824
  }
14887
14825
  } catch (e) {
14888
- debugLog(e);
14826
+ debugLog("phase-remove-int-rename-failed", {
14827
+ phase: targetPhase,
14828
+ error: errorMsg(e)
14829
+ });
14889
14830
  }
14890
14831
  }
14891
14832
  let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
@@ -14929,20 +14870,20 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
14929
14870
  }
14930
14871
  node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
14931
14872
  }
14932
- output({
14873
+ return cmdOk({
14933
14874
  removed: targetPhase,
14934
14875
  directory_deleted: targetDir || null,
14935
14876
  renamed_directories: renamedDirs,
14936
14877
  renamed_files: renamedFiles,
14937
14878
  roadmap_updated: true,
14938
14879
  state_updated: node_fs.default.existsSync(stPath)
14939
- }, raw);
14880
+ });
14940
14881
  }
14941
- function cmdPhaseComplete(cwd, phaseNum, raw) {
14942
- if (!phaseNum) error("phase number required for phase complete");
14882
+ function cmdPhaseComplete(cwd, phaseNum) {
14883
+ if (!phaseNum) return cmdErr("phase number required for phase complete");
14943
14884
  try {
14944
14885
  const result = phaseCompleteCore(cwd, phaseNum);
14945
- output({
14886
+ return cmdOk({
14946
14887
  completed_phase: result.completed_phase,
14947
14888
  phase_name: result.phase_name,
14948
14889
  plans_executed: result.plans_executed,
@@ -14952,9 +14893,9 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
14952
14893
  date: result.date,
14953
14894
  roadmap_updated: result.roadmap_updated,
14954
14895
  state_updated: result.state_updated
14955
- }, raw);
14896
+ });
14956
14897
  } catch (e) {
14957
- error(e.message);
14898
+ return cmdErr(e.message);
14958
14899
  }
14959
14900
  }
14960
14901
 
@@ -14965,8 +14906,8 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
14965
14906
  *
14966
14907
  * Ported from maxsim/bin/lib/template.cjs
14967
14908
  */
14968
- function cmdTemplateSelect(cwd, planPath, raw) {
14969
- if (!planPath) error("plan-path required");
14909
+ function cmdTemplateSelect(cwd, planPath) {
14910
+ if (!planPath) return cmdErr("plan-path required");
14970
14911
  try {
14971
14912
  const fullPath = node_path.default.join(cwd, planPath);
14972
14913
  const content = node_fs.default.readFileSync(fullPath, "utf-8");
@@ -14986,32 +14927,29 @@ function cmdTemplateSelect(cwd, planPath, raw) {
14986
14927
  template = "templates/summary-complex.md";
14987
14928
  type = "complex";
14988
14929
  }
14989
- output({
14930
+ return cmdOk({
14990
14931
  template,
14991
14932
  type,
14992
14933
  taskCount,
14993
14934
  fileCount,
14994
14935
  hasDecisions
14995
- }, raw, template);
14936
+ }, template);
14996
14937
  } catch (thrown) {
14997
- output({
14938
+ return cmdOk({
14998
14939
  template: "templates/summary-standard.md",
14999
14940
  type: "standard",
15000
14941
  error: thrown.message
15001
- }, raw, "templates/summary-standard.md");
14942
+ }, "templates/summary-standard.md");
15002
14943
  }
15003
14944
  }
15004
- function cmdTemplateFill(cwd, templateType, options, raw) {
15005
- if (!templateType) error("template type required: summary, plan, or verification");
15006
- if (!options.phase) error("--phase required");
14945
+ function cmdTemplateFill(cwd, templateType, options) {
14946
+ if (!templateType) return cmdErr("template type required: summary, plan, or verification");
14947
+ if (!options.phase) return cmdErr("--phase required");
15007
14948
  const phaseInfo = findPhaseInternal(cwd, options.phase);
15008
- if (!phaseInfo) {
15009
- output({
15010
- error: "Phase not found",
15011
- phase: options.phase
15012
- }, raw);
15013
- return;
15014
- }
14949
+ if (!phaseInfo) return cmdOk({
14950
+ error: "Phase not found",
14951
+ phase: options.phase
14952
+ });
15015
14953
  const padded = normalizePhaseName(options.phase);
15016
14954
  const today = todayISO();
15017
14955
  const phaseName = options.name || phaseInfo.phase_name || "Unnamed";
@@ -15157,26 +15095,633 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
15157
15095
  ].join("\n");
15158
15096
  fileName = `${padded}-VERIFICATION.md`;
15159
15097
  break;
15160
- default:
15161
- error(`Unknown template type: ${templateType}. Available: summary, plan, verification`);
15162
- return;
15098
+ default: return cmdErr(`Unknown template type: ${templateType}. Available: summary, plan, verification`);
15163
15099
  }
15164
15100
  const fullContent = `---\n${reconstructFrontmatter(frontmatter)}\n---\n\n${body}\n`;
15165
15101
  const outPath = node_path.default.join(cwd, phaseInfo.directory, fileName);
15166
- if (node_fs.default.existsSync(outPath)) {
15167
- output({
15168
- error: "File already exists",
15169
- path: node_path.default.relative(cwd, outPath)
15170
- }, raw);
15171
- return;
15172
- }
15102
+ if (node_fs.default.existsSync(outPath)) return cmdOk({
15103
+ error: "File already exists",
15104
+ path: node_path.default.relative(cwd, outPath)
15105
+ });
15173
15106
  node_fs.default.writeFileSync(outPath, fullContent, "utf-8");
15174
15107
  const relPath = node_path.default.relative(cwd, outPath);
15175
- output({
15108
+ return cmdOk({
15176
15109
  created: true,
15177
15110
  path: relPath,
15178
15111
  template: templateType
15179
- }, raw, relPath);
15112
+ }, relPath);
15113
+ }
15114
+
15115
+ //#endregion
15116
+ //#region src/core/artefakte.ts
15117
+ /**
15118
+ * Artefakte — CRUD operations for project-level artefakte files
15119
+ *
15120
+ * Manages DECISIONS.md, ACCEPTANCE-CRITERIA.md, and NO-GOS.md
15121
+ * at both project level (.planning/) and phase level (.planning/phases/<phase>/).
15122
+ */
15123
+ const ARTEFAKT_FILES = {
15124
+ "decisions": "DECISIONS.md",
15125
+ "acceptance-criteria": "ACCEPTANCE-CRITERIA.md",
15126
+ "no-gos": "NO-GOS.md"
15127
+ };
15128
+ function isValidType(type) {
15129
+ return !!type && type in ARTEFAKT_FILES;
15130
+ }
15131
+ function resolveArtefaktPath(cwd, type, phase) {
15132
+ const filename = ARTEFAKT_FILES[type];
15133
+ if (phase) {
15134
+ const phaseInfo = findPhaseInternal(cwd, phase);
15135
+ if (!phaseInfo?.directory) return null;
15136
+ return node_path.default.join(cwd, phaseInfo.directory, filename);
15137
+ }
15138
+ return planningPath(cwd, filename);
15139
+ }
15140
+ function getTemplate(type) {
15141
+ const today = todayISO();
15142
+ switch (type) {
15143
+ case "decisions": return `# Decisions\n\n> Architectural and design decisions for this project.\n\n**Created:** ${today}\n\n## Decision Log\n\n| # | Decision | Rationale | Date | Phase |\n|---|----------|-----------|------|-------|\n`;
15144
+ case "acceptance-criteria": return `# Acceptance Criteria\n\n> Conditions that must be met for deliverables to be accepted.\n\n**Created:** ${today}\n\n## Criteria\n\n| # | Criterion | Status | Verified |\n|---|-----------|--------|----------|\n`;
15145
+ case "no-gos": return `# No-Gos\n\n> Things explicitly out of scope or forbidden.\n\n**Created:** ${today}\n\n## Boundaries\n\n- _No entries yet._\n`;
15146
+ }
15147
+ }
15148
+ function cmdArtefakteRead(cwd, type, phase, raw) {
15149
+ if (!isValidType(type)) return cmdErr(`Invalid artefakt type: ${type}. Available: ${Object.keys(ARTEFAKT_FILES).join(", ")}`);
15150
+ const filePath = resolveArtefaktPath(cwd, type, phase);
15151
+ if (!filePath) return cmdErr(`Phase ${phase} not found`);
15152
+ const content = safeReadFile(filePath);
15153
+ if (content === null) return cmdOk({
15154
+ exists: false,
15155
+ type,
15156
+ phase: phase ?? null,
15157
+ content: null
15158
+ }, raw ? "" : void 0);
15159
+ return cmdOk({
15160
+ exists: true,
15161
+ type,
15162
+ phase: phase ?? null,
15163
+ content
15164
+ }, raw ? content : void 0);
15165
+ }
15166
+ function cmdArtefakteWrite(cwd, type, content, phase, raw) {
15167
+ if (!isValidType(type)) return cmdErr(`Invalid artefakt type: ${type}. Available: ${Object.keys(ARTEFAKT_FILES).join(", ")}`);
15168
+ const fileContent = content ?? getTemplate(type);
15169
+ const filePath = resolveArtefaktPath(cwd, type, phase);
15170
+ if (!filePath) return cmdErr(`Phase ${phase} not found`);
15171
+ node_fs.default.mkdirSync(node_path.default.dirname(filePath), { recursive: true });
15172
+ node_fs.default.writeFileSync(filePath, fileContent, "utf-8");
15173
+ const relPath = node_path.default.relative(cwd, filePath);
15174
+ return cmdOk({
15175
+ written: true,
15176
+ type,
15177
+ phase: phase ?? null,
15178
+ path: relPath
15179
+ }, raw ? relPath : void 0);
15180
+ }
15181
+ function cmdArtefakteAppend(cwd, type, entry, phase, raw) {
15182
+ if (!entry) return cmdErr("entry required for artefakte append");
15183
+ if (!isValidType(type)) return cmdErr(`Invalid artefakt type: ${type}. Available: ${Object.keys(ARTEFAKT_FILES).join(", ")}`);
15184
+ const filePath = resolveArtefaktPath(cwd, type, phase);
15185
+ if (!filePath) return cmdErr(`Phase ${phase} not found`);
15186
+ let fileContent = safeReadFile(filePath);
15187
+ if (fileContent === null) fileContent = getTemplate(type);
15188
+ fileContent = fileContent.replace(/^-\s*_No entries yet\._\s*$/m, "");
15189
+ const today = todayISO();
15190
+ let appendLine;
15191
+ if (type === "decisions") appendLine = `| ${(fileContent.match(/^\|\s*\d+/gm) || []).length + 1} | ${entry} | - | ${today} | - |`;
15192
+ else if (type === "acceptance-criteria") appendLine = `| ${(fileContent.match(/^\|\s*\d+/gm) || []).length + 1} | ${entry} | pending | - |`;
15193
+ else appendLine = `- ${entry}`;
15194
+ fileContent = fileContent.trimEnd() + "\n" + appendLine + "\n";
15195
+ node_fs.default.writeFileSync(filePath, fileContent, "utf-8");
15196
+ const relPath = node_path.default.relative(cwd, filePath);
15197
+ return cmdOk({
15198
+ appended: true,
15199
+ type,
15200
+ phase: phase ?? null,
15201
+ entry: appendLine,
15202
+ path: relPath
15203
+ }, raw ? "true" : void 0);
15204
+ }
15205
+ function cmdArtefakteList(cwd, phase, raw) {
15206
+ const results = [];
15207
+ for (const [type, filename] of Object.entries(ARTEFAKT_FILES)) {
15208
+ let filePath;
15209
+ if (phase) {
15210
+ const phaseInfo = findPhaseInternal(cwd, phase);
15211
+ if (!phaseInfo?.directory) return cmdOk({ error: `Phase ${phase} not found` });
15212
+ filePath = node_path.default.join(cwd, phaseInfo.directory, filename);
15213
+ } else filePath = planningPath(cwd, filename);
15214
+ results.push({
15215
+ type,
15216
+ exists: node_fs.default.existsSync(filePath),
15217
+ path: node_path.default.relative(cwd, filePath)
15218
+ });
15219
+ }
15220
+ return cmdOk({
15221
+ phase: phase ?? null,
15222
+ artefakte: results
15223
+ });
15224
+ }
15225
+
15226
+ //#endregion
15227
+ //#region src/core/context-loader.ts
15228
+ /**
15229
+ * Context Loader — Intelligent file selection for workflow context assembly
15230
+ *
15231
+ * Selects relevant planning files based on the current task/phase domain,
15232
+ * preventing context overload by loading only what matters.
15233
+ */
15234
+ function fileEntry(cwd, relPath, role) {
15235
+ const fullPath = node_path.default.join(cwd, relPath);
15236
+ try {
15237
+ return {
15238
+ path: relPath,
15239
+ role,
15240
+ size: node_fs.default.statSync(fullPath).size
15241
+ };
15242
+ } catch {
15243
+ return null;
15244
+ }
15245
+ }
15246
+ function addIfExists(files, cwd, relPath, role) {
15247
+ const entry = fileEntry(cwd, relPath, role);
15248
+ if (entry) files.push(entry);
15249
+ }
15250
+ function loadProjectContext(cwd) {
15251
+ const files = [];
15252
+ addIfExists(files, cwd, ".planning/PROJECT.md", "project-vision");
15253
+ addIfExists(files, cwd, ".planning/REQUIREMENTS.md", "requirements");
15254
+ addIfExists(files, cwd, ".planning/STATE.md", "state");
15255
+ addIfExists(files, cwd, ".planning/config.json", "config");
15256
+ return files;
15257
+ }
15258
+ function loadRoadmapContext(cwd) {
15259
+ const files = [];
15260
+ addIfExists(files, cwd, ".planning/ROADMAP.md", "roadmap");
15261
+ return files;
15262
+ }
15263
+ function loadPhaseContext(cwd, phase) {
15264
+ const files = [];
15265
+ const phaseInfo = findPhaseInternal(cwd, phase);
15266
+ if (!phaseInfo?.directory) return files;
15267
+ const phaseDir = phaseInfo.directory;
15268
+ try {
15269
+ const phaseFiles = node_fs.default.readdirSync(node_path.default.join(cwd, phaseDir));
15270
+ for (const f of phaseFiles) {
15271
+ const relPath = node_path.default.join(phaseDir, f);
15272
+ if (f.endsWith("-CONTEXT.md") || f === "CONTEXT.md") addIfExists(files, cwd, relPath, "phase-context");
15273
+ else if (f.endsWith("-RESEARCH.md") || f === "RESEARCH.md") addIfExists(files, cwd, relPath, "phase-research");
15274
+ else if (f.endsWith("-PLAN.md")) addIfExists(files, cwd, relPath, "phase-plan");
15275
+ else if (f.endsWith("-SUMMARY.md")) addIfExists(files, cwd, relPath, "phase-summary");
15276
+ else if (f.endsWith("-VERIFICATION.md") || f === "VERIFICATION.md") addIfExists(files, cwd, relPath, "phase-verification");
15277
+ }
15278
+ } catch (e) {
15279
+ debugLog("context-loader-phase-files-failed", e);
15280
+ }
15281
+ return files;
15282
+ }
15283
+ function loadArtefakteContext(cwd, phase) {
15284
+ const files = [];
15285
+ for (const filename of [
15286
+ "DECISIONS.md",
15287
+ "ACCEPTANCE-CRITERIA.md",
15288
+ "NO-GOS.md"
15289
+ ]) {
15290
+ if (phase) {
15291
+ const phaseInfo = findPhaseInternal(cwd, phase);
15292
+ if (phaseInfo?.directory) addIfExists(files, cwd, node_path.default.join(phaseInfo.directory, filename), `artefakt-${filename.toLowerCase()}`);
15293
+ }
15294
+ addIfExists(files, cwd, `.planning/${filename}`, `artefakt-${filename.toLowerCase()}`);
15295
+ }
15296
+ return files;
15297
+ }
15298
+ function loadHistoryContext(cwd, currentPhase) {
15299
+ const files = [];
15300
+ const pd = phasesPath(cwd);
15301
+ try {
15302
+ const dirs = listSubDirs(pd, true);
15303
+ for (const dir of dirs) {
15304
+ if (currentPhase) {
15305
+ if (dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i)?.[1] === currentPhase) continue;
15306
+ }
15307
+ const dirPath = node_path.default.join(pd, dir);
15308
+ const summaries = node_fs.default.readdirSync(dirPath).filter((f) => isSummaryFile(f));
15309
+ for (const s of summaries) addIfExists(files, cwd, node_path.default.join(".planning", "phases", dir, s), "history-summary");
15310
+ }
15311
+ } catch (e) {
15312
+ debugLog("context-loader-history-failed", e);
15313
+ }
15314
+ return files;
15315
+ }
15316
+ function cmdContextLoad(cwd, phase, topic, includeHistory) {
15317
+ const allFiles = [];
15318
+ allFiles.push(...loadProjectContext(cwd));
15319
+ allFiles.push(...loadRoadmapContext(cwd));
15320
+ allFiles.push(...loadArtefakteContext(cwd, phase));
15321
+ if (phase) allFiles.push(...loadPhaseContext(cwd, phase));
15322
+ if (includeHistory) allFiles.push(...loadHistoryContext(cwd, phase));
15323
+ const seen = /* @__PURE__ */ new Set();
15324
+ const deduped = allFiles.filter((f) => {
15325
+ if (seen.has(f.path)) return false;
15326
+ seen.add(f.path);
15327
+ return true;
15328
+ });
15329
+ return cmdOk({
15330
+ files: deduped,
15331
+ total_size: deduped.reduce((sum, f) => sum + f.size, 0),
15332
+ phase: phase ?? null,
15333
+ topic: topic ?? null
15334
+ });
15335
+ }
15336
+
15337
+ //#endregion
15338
+ //#region src/core/skills.ts
15339
+ /**
15340
+ * Skills — List, install, and update skill templates
15341
+ *
15342
+ * Skills are installed to `.claude/skills/<name>/SKILL.md`.
15343
+ * Source templates live in `templates/skills/<name>/SKILL.md`.
15344
+ */
15345
+ /**
15346
+ * Resolve the installed skills directory for the current project.
15347
+ * Skills live at `.claude/skills/` relative to cwd.
15348
+ */
15349
+ function skillsDir(cwd) {
15350
+ return node_path.default.join(cwd, ".claude", "skills");
15351
+ }
15352
+ /**
15353
+ * Resolve the templates source directory for skills.
15354
+ * At runtime (from dist/cli.cjs), templates are bundled at dist/assets/templates/skills/.
15355
+ */
15356
+ function skillsTemplateDir() {
15357
+ return node_path.default.resolve(__dirname, "assets", "templates", "skills");
15358
+ }
15359
+ /**
15360
+ * Read a single skill's metadata from its SKILL.md frontmatter.
15361
+ */
15362
+ function readSkillInfo(skillDir, dirName) {
15363
+ const content = safeReadFile(node_path.default.join(skillDir, "SKILL.md"));
15364
+ if (!content) return null;
15365
+ const fm = extractFrontmatter(content);
15366
+ return {
15367
+ name: fm.name ?? dirName,
15368
+ description: fm.description ?? "",
15369
+ directory: dirName
15370
+ };
15371
+ }
15372
+ /**
15373
+ * List all installed skills from `.claude/skills/`.
15374
+ */
15375
+ function cmdSkillList(cwd, raw) {
15376
+ const dir = skillsDir(cwd);
15377
+ if (!node_fs.default.existsSync(dir)) output({
15378
+ skills: [],
15379
+ count: 0
15380
+ }, raw, "No skills installed.");
15381
+ const entries = node_fs.default.readdirSync(dir, { withFileTypes: true });
15382
+ const skills = [];
15383
+ for (const entry of entries) {
15384
+ if (!entry.isDirectory()) continue;
15385
+ const info = readSkillInfo(node_path.default.join(dir, entry.name), entry.name);
15386
+ if (info) skills.push(info);
15387
+ }
15388
+ output({
15389
+ skills,
15390
+ count: skills.length
15391
+ }, raw, skills.map((s) => `${s.name}: ${s.description}`).join("\n"));
15392
+ }
15393
+ /**
15394
+ * Install a specific skill from the templates directory.
15395
+ */
15396
+ function cmdSkillInstall(cwd, skillName, raw) {
15397
+ if (!skillName) error("skill name required. Usage: skill-install <name>");
15398
+ const srcFile = node_path.default.join(skillsTemplateDir(), skillName, "SKILL.md");
15399
+ if (!node_fs.default.existsSync(srcFile)) error(`Skill "${skillName}" not found in templates. Available: ${listAvailableTemplates().join(", ")}`);
15400
+ const destDir = node_path.default.join(skillsDir(cwd), skillName);
15401
+ const destFile = node_path.default.join(destDir, "SKILL.md");
15402
+ node_fs.default.mkdirSync(destDir, { recursive: true });
15403
+ node_fs.default.copyFileSync(srcFile, destFile);
15404
+ output({
15405
+ installed: true,
15406
+ skill: skillName,
15407
+ path: node_path.default.relative(cwd, destFile)
15408
+ }, raw, `Installed skill: ${skillName}`);
15409
+ }
15410
+ /**
15411
+ * Update one or all installed skills from the templates source.
15412
+ */
15413
+ function cmdSkillUpdate(cwd, skillName, raw) {
15414
+ const dir = skillsDir(cwd);
15415
+ const templateDir = skillsTemplateDir();
15416
+ if (skillName) {
15417
+ const srcFile = node_path.default.join(templateDir, skillName, "SKILL.md");
15418
+ if (!node_fs.default.existsSync(srcFile)) error(`Skill template "${skillName}" not found.`);
15419
+ const destDir = node_path.default.join(dir, skillName);
15420
+ if (!node_fs.default.existsSync(destDir)) error(`Skill "${skillName}" is not installed. Use skill-install first.`);
15421
+ const destFile = node_path.default.join(destDir, "SKILL.md");
15422
+ node_fs.default.copyFileSync(srcFile, destFile);
15423
+ output({
15424
+ updated: [skillName],
15425
+ skipped: [],
15426
+ not_found: []
15427
+ }, raw, `Updated skill: ${skillName}`);
15428
+ return;
15429
+ }
15430
+ if (!node_fs.default.existsSync(dir)) {
15431
+ output({
15432
+ updated: [],
15433
+ skipped: [],
15434
+ not_found: []
15435
+ }, raw, "No skills installed.");
15436
+ return;
15437
+ }
15438
+ const entries = node_fs.default.readdirSync(dir, { withFileTypes: true });
15439
+ const updated = [];
15440
+ const skipped = [];
15441
+ for (const entry of entries) {
15442
+ if (!entry.isDirectory()) continue;
15443
+ const name = entry.name;
15444
+ const srcFile = node_path.default.join(templateDir, name, "SKILL.md");
15445
+ if (!node_fs.default.existsSync(srcFile)) {
15446
+ skipped.push(name);
15447
+ continue;
15448
+ }
15449
+ const destFile = node_path.default.join(dir, name, "SKILL.md");
15450
+ node_fs.default.copyFileSync(srcFile, destFile);
15451
+ updated.push(name);
15452
+ }
15453
+ const summary = updated.length > 0 ? `Updated ${updated.length} skill(s): ${updated.join(", ")}` : "No skills updated.";
15454
+ output({
15455
+ updated,
15456
+ skipped
15457
+ }, raw, summary);
15458
+ }
15459
+ function listAvailableTemplates() {
15460
+ const dir = skillsTemplateDir();
15461
+ if (!node_fs.default.existsSync(dir)) return [];
15462
+ return node_fs.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
15463
+ }
15464
+
15465
+ //#endregion
15466
+ //#region src/core/dashboard-launcher.ts
15467
+ /**
15468
+ * Dashboard Launcher — Shared dashboard lifecycle utilities
15469
+ *
15470
+ * Used by both cli.ts (tool-router) and install.ts (npx entry point).
15471
+ */
15472
+ const DEFAULT_PORT = 3333;
15473
+ const PORT_RANGE_END = 3343;
15474
+ const HEALTH_TIMEOUT_MS = 1e4;
15475
+ /**
15476
+ * Check if a dashboard health endpoint is responding on the given port.
15477
+ */
15478
+ async function checkHealth(port, timeoutMs = HEALTH_TIMEOUT_MS) {
15479
+ try {
15480
+ const controller = new AbortController();
15481
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
15482
+ const res = await fetch(`http://localhost:${port}/api/health`, { signal: controller.signal });
15483
+ clearTimeout(timer);
15484
+ if (res.ok) return (await res.json()).status === "ok";
15485
+ return false;
15486
+ } catch (e) {
15487
+ debugLog("health-check-failed", {
15488
+ port,
15489
+ error: errorMsg(e)
15490
+ });
15491
+ return false;
15492
+ }
15493
+ }
15494
+ /**
15495
+ * Scan the port range for a running dashboard instance.
15496
+ * Returns the port number if found, null otherwise.
15497
+ */
15498
+ async function findRunningDashboard(timeoutMs = HEALTH_TIMEOUT_MS) {
15499
+ for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port, timeoutMs)) return port;
15500
+ return null;
15501
+ }
15502
+ /**
15503
+ * Kill processes listening on the given port. Cross-platform.
15504
+ */
15505
+ function killProcessOnPort(port) {
15506
+ if (process.platform === "win32") try {
15507
+ const lines = (0, node_child_process.execSync)(`netstat -ano | findstr :${port} | findstr LISTENING`, { encoding: "utf-8" }).trim().split("\n");
15508
+ const pids = /* @__PURE__ */ new Set();
15509
+ for (const line of lines) {
15510
+ const parts = line.trim().split(/\s+/);
15511
+ const pid = parts[parts.length - 1];
15512
+ if (pid && pid !== "0") pids.add(pid);
15513
+ }
15514
+ for (const pid of pids) try {
15515
+ (0, node_child_process.execSync)(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
15516
+ } catch (e) {
15517
+ debugLog("kill-process-on-port-taskkill-failed", {
15518
+ port,
15519
+ pid,
15520
+ error: errorMsg(e)
15521
+ });
15522
+ }
15523
+ } catch (e) {
15524
+ debugLog("kill-process-on-port-netstat-failed", {
15525
+ port,
15526
+ platform: "win32",
15527
+ error: errorMsg(e)
15528
+ });
15529
+ }
15530
+ else try {
15531
+ (0, node_child_process.execSync)(`lsof -i :${port} -t | xargs kill -SIGTERM 2>/dev/null`, { stdio: "ignore" });
15532
+ } catch (e) {
15533
+ debugLog("kill-process-on-port-lsof-failed", {
15534
+ port,
15535
+ platform: process.platform,
15536
+ error: errorMsg(e)
15537
+ });
15538
+ }
15539
+ }
15540
+ /**
15541
+ * Resolve the dashboard server entry point path.
15542
+ * Tries: local project install, global install, @maxsim/dashboard package, monorepo walk.
15543
+ */
15544
+ function resolveDashboardServer() {
15545
+ const localDashboard = node_path.default.join(process.cwd(), ".claude", "dashboard", "server.js");
15546
+ if (node_fs.default.existsSync(localDashboard)) return localDashboard;
15547
+ const globalDashboard = node_path.default.join(node_os.default.homedir(), ".claude", "dashboard", "server.js");
15548
+ if (node_fs.default.existsSync(globalDashboard)) return globalDashboard;
15549
+ try {
15550
+ const pkgPath = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href).resolve("@maxsim/dashboard/package.json");
15551
+ const pkgDir = node_path.default.dirname(pkgPath);
15552
+ const serverJs = node_path.default.join(pkgDir, "server.js");
15553
+ if (node_fs.default.existsSync(serverJs)) return serverJs;
15554
+ const serverTs = node_path.default.join(pkgDir, "server.ts");
15555
+ if (node_fs.default.existsSync(serverTs)) return serverTs;
15556
+ } catch (e) {
15557
+ debugLog("resolve-dashboard-strategy1-failed", {
15558
+ strategy: "@maxsim/dashboard package",
15559
+ error: errorMsg(e)
15560
+ });
15561
+ }
15562
+ try {
15563
+ let dir = node_path.default.dirname(new URL(require("url").pathToFileURL(__filename).href).pathname);
15564
+ if (process.platform === "win32" && dir.startsWith("/")) dir = dir.slice(1);
15565
+ for (let i = 0; i < 5; i++) {
15566
+ const candidate = node_path.default.join(dir, "packages", "dashboard", "server.ts");
15567
+ if (node_fs.default.existsSync(candidate)) return candidate;
15568
+ const candidateJs = node_path.default.join(dir, "packages", "dashboard", "server.js");
15569
+ if (node_fs.default.existsSync(candidateJs)) return candidateJs;
15570
+ dir = node_path.default.dirname(dir);
15571
+ }
15572
+ } catch (e) {
15573
+ debugLog("resolve-dashboard-strategy2-failed", {
15574
+ strategy: "monorepo walk",
15575
+ error: errorMsg(e)
15576
+ });
15577
+ }
15578
+ return null;
15579
+ }
15580
+ /**
15581
+ * Ensure node-pty is installed in the dashboard directory.
15582
+ * Returns true if node-pty is available after this call.
15583
+ */
15584
+ function ensureNodePty(serverDir) {
15585
+ const ptyModulePath = node_path.default.join(serverDir, "node_modules", "node-pty");
15586
+ if (node_fs.default.existsSync(ptyModulePath)) return true;
15587
+ const dashPkgPath = node_path.default.join(serverDir, "package.json");
15588
+ if (!node_fs.default.existsSync(dashPkgPath)) node_fs.default.writeFileSync(dashPkgPath, "{\"private\":true}\n");
15589
+ try {
15590
+ (0, node_child_process.execSync)("npm install node-pty --save-optional --no-audit --no-fund --loglevel=error", {
15591
+ cwd: serverDir,
15592
+ stdio: "inherit",
15593
+ timeout: 12e4
15594
+ });
15595
+ return true;
15596
+ } catch (e) {
15597
+ debugLog("ensure-node-pty-install-failed", {
15598
+ serverDir,
15599
+ error: errorMsg(e)
15600
+ });
15601
+ return false;
15602
+ }
15603
+ }
15604
+ /**
15605
+ * Read dashboard.json config from the parent directory of the dashboard dir.
15606
+ */
15607
+ function readDashboardConfig(serverPath) {
15608
+ const dashboardDir = node_path.default.dirname(serverPath);
15609
+ const dashboardConfigPath = node_path.default.join(node_path.default.dirname(dashboardDir), "dashboard.json");
15610
+ let projectCwd = process.cwd();
15611
+ let networkMode = false;
15612
+ if (node_fs.default.existsSync(dashboardConfigPath)) try {
15613
+ const config = JSON.parse(node_fs.default.readFileSync(dashboardConfigPath, "utf8"));
15614
+ if (config.projectCwd) projectCwd = config.projectCwd;
15615
+ networkMode = config.networkMode ?? false;
15616
+ } catch (e) {
15617
+ debugLog("read-dashboard-config-failed", {
15618
+ path: dashboardConfigPath,
15619
+ error: errorMsg(e)
15620
+ });
15621
+ }
15622
+ return {
15623
+ projectCwd,
15624
+ networkMode
15625
+ };
15626
+ }
15627
+ /**
15628
+ * Spawn the dashboard server as a detached background process.
15629
+ * Returns the child process PID, or null if spawn failed.
15630
+ */
15631
+ function spawnDashboard(options) {
15632
+ const { serverPath, projectCwd, networkMode = false, nodeEnv = "production" } = options;
15633
+ const serverDir = node_path.default.dirname(serverPath);
15634
+ const isTsFile = serverPath.endsWith(".ts");
15635
+ const child = (0, node_child_process.spawn)("node", isTsFile ? [
15636
+ "--import",
15637
+ "tsx",
15638
+ serverPath
15639
+ ] : [serverPath], {
15640
+ cwd: serverDir,
15641
+ detached: true,
15642
+ stdio: "ignore",
15643
+ env: {
15644
+ ...process.env,
15645
+ MAXSIM_PROJECT_CWD: projectCwd,
15646
+ MAXSIM_NETWORK_MODE: networkMode ? "1" : "0",
15647
+ NODE_ENV: isTsFile ? "development" : nodeEnv
15648
+ },
15649
+ ...process.platform === "win32" ? { shell: true } : {}
15650
+ });
15651
+ child.unref();
15652
+ return child.pid ?? null;
15653
+ }
15654
+ /**
15655
+ * Poll the port range until a dashboard health endpoint responds.
15656
+ * Returns the URL if found within the timeout, null otherwise.
15657
+ */
15658
+ async function waitForDashboard(pollIntervalMs = 500, pollTimeoutMs = 2e4, healthTimeoutMs = 1e3) {
15659
+ const deadline = Date.now() + pollTimeoutMs;
15660
+ while (Date.now() < deadline) {
15661
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
15662
+ for (let p = DEFAULT_PORT; p <= PORT_RANGE_END; p++) if (await checkHealth(p, healthTimeoutMs)) return `http://localhost:${p}`;
15663
+ }
15664
+ return null;
15665
+ }
15666
+
15667
+ //#endregion
15668
+ //#region src/core/start.ts
15669
+ /**
15670
+ * Start — Orchestrates Dashboard launch + browser open
15671
+ *
15672
+ * Provides a unified `maxsimcli start` entry point that:
15673
+ * 1. Checks for a running dashboard
15674
+ * 2. Starts the dashboard if needed
15675
+ * 3. Opens the browser
15676
+ * 4. Reports status
15677
+ */
15678
+ function openBrowser(url) {
15679
+ (0, node_child_process.exec)(process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`, (err) => {
15680
+ if (err) debugLog("open-browser-failed", err);
15681
+ });
15682
+ }
15683
+ async function cmdStart(cwd, options) {
15684
+ const existingPort = await findRunningDashboard();
15685
+ if (existingPort) {
15686
+ const url = `http://localhost:${existingPort}`;
15687
+ if (!options.noBrowser) openBrowser(url);
15688
+ return cmdOk({
15689
+ started: true,
15690
+ url,
15691
+ already_running: true,
15692
+ port: existingPort
15693
+ }, url);
15694
+ }
15695
+ const serverPath = resolveDashboardServer();
15696
+ if (!serverPath) return cmdErr("Dashboard server not found. Run `npx maxsimcli` to install first.");
15697
+ const serverDir = node_path.default.dirname(serverPath);
15698
+ const dashConfig = readDashboardConfig(serverPath);
15699
+ ensureNodePty(serverDir);
15700
+ const pid = spawnDashboard({
15701
+ serverPath,
15702
+ projectCwd: dashConfig.projectCwd,
15703
+ networkMode: options.networkMode
15704
+ });
15705
+ if (!pid) return cmdErr("Failed to spawn dashboard process.");
15706
+ const url = await waitForDashboard();
15707
+ if (url) {
15708
+ if (!options.noBrowser) openBrowser(url);
15709
+ return cmdOk({
15710
+ started: true,
15711
+ url,
15712
+ already_running: false,
15713
+ pid
15714
+ }, url);
15715
+ } else {
15716
+ const fallbackUrl = `http://localhost:${DEFAULT_PORT}`;
15717
+ return cmdOk({
15718
+ started: true,
15719
+ url: fallbackUrl,
15720
+ already_running: false,
15721
+ pid,
15722
+ warning: "Dashboard spawned but health check timed out. It may still be starting."
15723
+ }, fallbackUrl);
15724
+ }
15180
15725
  }
15181
15726
 
15182
15727
  //#endregion
@@ -15209,13 +15754,47 @@ function scanPhaseArtifacts(cwd, phaseDirectory) {
15209
15754
  }
15210
15755
  return result;
15211
15756
  }
15212
- function cmdInitExecutePhase(cwd, phase, raw) {
15213
- if (!phase) error("phase required for init execute-phase");
15757
+ const CODE_EXTENSIONS = new Set([
15758
+ ".ts",
15759
+ ".js",
15760
+ ".py",
15761
+ ".go",
15762
+ ".rs",
15763
+ ".swift",
15764
+ ".java"
15765
+ ]);
15766
+ const EXCLUDED_DIRS = new Set(["node_modules", ".git"]);
15767
+ function findCodeFiles(dir, maxDepth = 3, limit = 5) {
15768
+ const results = [];
15769
+ function walk(currentDir, depth) {
15770
+ if (depth > maxDepth || results.length >= limit) return;
15771
+ let entries;
15772
+ try {
15773
+ entries = node_fs.default.readdirSync(currentDir, { withFileTypes: true });
15774
+ } catch {
15775
+ return;
15776
+ }
15777
+ for (const entry of entries) {
15778
+ if (results.length >= limit) return;
15779
+ if (EXCLUDED_DIRS.has(entry.name)) continue;
15780
+ const fullPath = node_path.default.join(currentDir, entry.name);
15781
+ if (entry.isDirectory()) walk(fullPath, depth + 1);
15782
+ else if (entry.isFile()) {
15783
+ const ext = node_path.default.extname(entry.name).toLowerCase();
15784
+ if (CODE_EXTENSIONS.has(ext)) results.push(fullPath);
15785
+ }
15786
+ }
15787
+ }
15788
+ walk(dir, 1);
15789
+ return results;
15790
+ }
15791
+ function cmdInitExecutePhase(cwd, phase) {
15792
+ if (!phase) return cmdErr("phase required for init execute-phase");
15214
15793
  const config = loadConfig(cwd);
15215
15794
  const phaseInfo = findPhaseInternal(cwd, phase);
15216
15795
  const milestone = getMilestoneInfo(cwd);
15217
15796
  const phase_req_ids = extractReqIds(cwd, phase);
15218
- output({
15797
+ return cmdOk({
15219
15798
  executor_model: resolveModelInternal(cwd, "maxsim-executor"),
15220
15799
  verifier_model: resolveModelInternal(cwd, "maxsim-verifier"),
15221
15800
  commit_docs: config.commit_docs,
@@ -15245,10 +15824,10 @@ function cmdInitExecutePhase(cwd, phase, raw) {
15245
15824
  state_path: ".planning/STATE.md",
15246
15825
  roadmap_path: ".planning/ROADMAP.md",
15247
15826
  config_path: ".planning/config.json"
15248
- }, raw);
15827
+ });
15249
15828
  }
15250
- function cmdInitPlanPhase(cwd, phase, raw) {
15251
- if (!phase) error("phase required for init plan-phase");
15829
+ function cmdInitPlanPhase(cwd, phase) {
15830
+ if (!phase) return cmdErr("phase required for init plan-phase");
15252
15831
  const config = loadConfig(cwd);
15253
15832
  const phaseInfo = findPhaseInternal(cwd, phase);
15254
15833
  const phase_req_ids = extractReqIds(cwd, phase);
@@ -15258,7 +15837,6 @@ function cmdInitPlanPhase(cwd, phase, raw) {
15258
15837
  checker_model: resolveModelInternal(cwd, "maxsim-plan-checker"),
15259
15838
  research_enabled: config.research,
15260
15839
  plan_checker_enabled: config.plan_checker,
15261
- nyquist_validation_enabled: false,
15262
15840
  commit_docs: config.commit_docs,
15263
15841
  phase_found: !!phaseInfo,
15264
15842
  phase_dir: phaseInfo?.directory ?? null,
@@ -15284,30 +15862,16 @@ function cmdInitPlanPhase(cwd, phase, raw) {
15284
15862
  if (artifacts.verification_path) result.verification_path = artifacts.verification_path;
15285
15863
  if (artifacts.uat_path) result.uat_path = artifacts.uat_path;
15286
15864
  }
15287
- output(result, raw);
15865
+ return cmdOk(result);
15288
15866
  }
15289
- function cmdInitNewProject(cwd, raw) {
15867
+ function cmdInitNewProject(cwd) {
15290
15868
  const config = loadConfig(cwd);
15291
15869
  const homedir = node_os.default.homedir();
15292
15870
  const braveKeyFile = node_path.default.join(homedir, ".maxsim", "brave_api_key");
15293
15871
  const hasBraveSearch = !!(process.env.BRAVE_API_KEY || node_fs.default.existsSync(braveKeyFile));
15294
- let hasCode = false;
15295
- let hasPackageFile = false;
15296
- try {
15297
- hasCode = (0, node_child_process.execSync)("find . -maxdepth 3 \\( -name \"*.ts\" -o -name \"*.js\" -o -name \"*.py\" -o -name \"*.go\" -o -name \"*.rs\" -o -name \"*.swift\" -o -name \"*.java\" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5", {
15298
- cwd,
15299
- encoding: "utf-8",
15300
- stdio: [
15301
- "pipe",
15302
- "pipe",
15303
- "pipe"
15304
- ]
15305
- }).trim().length > 0;
15306
- } catch (e) {
15307
- debugLog(e);
15308
- }
15309
- hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
15310
- output({
15872
+ const hasCode = findCodeFiles(cwd).length > 0;
15873
+ const hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
15874
+ return cmdOk({
15311
15875
  researcher_model: resolveModelInternal(cwd, "maxsim-project-researcher"),
15312
15876
  synthesizer_model: resolveModelInternal(cwd, "maxsim-research-synthesizer"),
15313
15877
  roadmapper_model: resolveModelInternal(cwd, "maxsim-roadmapper"),
@@ -15322,12 +15886,12 @@ function cmdInitNewProject(cwd, raw) {
15322
15886
  has_git: pathExistsInternal(cwd, ".git"),
15323
15887
  brave_search_available: hasBraveSearch,
15324
15888
  project_path: ".planning/PROJECT.md"
15325
- }, raw);
15889
+ });
15326
15890
  }
15327
- function cmdInitNewMilestone(cwd, raw) {
15891
+ function cmdInitNewMilestone(cwd) {
15328
15892
  const config = loadConfig(cwd);
15329
15893
  const milestone = getMilestoneInfo(cwd);
15330
- output({
15894
+ return cmdOk({
15331
15895
  researcher_model: resolveModelInternal(cwd, "maxsim-project-researcher"),
15332
15896
  synthesizer_model: resolveModelInternal(cwd, "maxsim-research-synthesizer"),
15333
15897
  roadmapper_model: resolveModelInternal(cwd, "maxsim-roadmapper"),
@@ -15341,9 +15905,9 @@ function cmdInitNewMilestone(cwd, raw) {
15341
15905
  project_path: ".planning/PROJECT.md",
15342
15906
  roadmap_path: ".planning/ROADMAP.md",
15343
15907
  state_path: ".planning/STATE.md"
15344
- }, raw);
15908
+ });
15345
15909
  }
15346
- function cmdInitQuick(cwd, description, raw) {
15910
+ function cmdInitQuick(cwd, description) {
15347
15911
  const config = loadConfig(cwd);
15348
15912
  const now = /* @__PURE__ */ new Date();
15349
15913
  const slug = description ? generateSlugInternal(description)?.substring(0, 40) ?? null : null;
@@ -15355,7 +15919,7 @@ function cmdInitQuick(cwd, description, raw) {
15355
15919
  } catch (e) {
15356
15920
  debugLog(e);
15357
15921
  }
15358
- output({
15922
+ return cmdOk({
15359
15923
  planner_model: resolveModelInternal(cwd, "maxsim-planner"),
15360
15924
  executor_model: resolveModelInternal(cwd, "maxsim-executor"),
15361
15925
  checker_model: resolveModelInternal(cwd, "maxsim-plan-checker"),
@@ -15370,9 +15934,9 @@ function cmdInitQuick(cwd, description, raw) {
15370
15934
  task_dir: slug ? `.planning/quick/${nextNum}-${slug}` : null,
15371
15935
  roadmap_exists: pathExistsInternal(cwd, ".planning/ROADMAP.md"),
15372
15936
  planning_exists: pathExistsInternal(cwd, ".planning")
15373
- }, raw);
15937
+ });
15374
15938
  }
15375
- function cmdInitResume(cwd, raw) {
15939
+ function cmdInitResume(cwd) {
15376
15940
  const config = loadConfig(cwd);
15377
15941
  let interruptedAgentId = null;
15378
15942
  try {
@@ -15380,7 +15944,7 @@ function cmdInitResume(cwd, raw) {
15380
15944
  } catch (e) {
15381
15945
  debugLog(e);
15382
15946
  }
15383
- output({
15947
+ return cmdOk({
15384
15948
  state_exists: pathExistsInternal(cwd, ".planning/STATE.md"),
15385
15949
  roadmap_exists: pathExistsInternal(cwd, ".planning/ROADMAP.md"),
15386
15950
  project_exists: pathExistsInternal(cwd, ".planning/PROJECT.md"),
@@ -15391,13 +15955,13 @@ function cmdInitResume(cwd, raw) {
15391
15955
  has_interrupted_agent: !!interruptedAgentId,
15392
15956
  interrupted_agent_id: interruptedAgentId,
15393
15957
  commit_docs: config.commit_docs
15394
- }, raw);
15958
+ });
15395
15959
  }
15396
- function cmdInitVerifyWork(cwd, phase, raw) {
15397
- if (!phase) error("phase required for init verify-work");
15960
+ function cmdInitVerifyWork(cwd, phase) {
15961
+ if (!phase) return cmdErr("phase required for init verify-work");
15398
15962
  const config = loadConfig(cwd);
15399
15963
  const phaseInfo = findPhaseInternal(cwd, phase);
15400
- output({
15964
+ return cmdOk({
15401
15965
  planner_model: resolveModelInternal(cwd, "maxsim-planner"),
15402
15966
  checker_model: resolveModelInternal(cwd, "maxsim-plan-checker"),
15403
15967
  commit_docs: config.commit_docs,
@@ -15406,9 +15970,9 @@ function cmdInitVerifyWork(cwd, phase, raw) {
15406
15970
  phase_number: phaseInfo?.phase_number ?? null,
15407
15971
  phase_name: phaseInfo?.phase_name ?? null,
15408
15972
  has_verification: phaseInfo?.has_verification ?? false
15409
- }, raw);
15973
+ });
15410
15974
  }
15411
- function cmdInitPhaseOp(cwd, phase, raw) {
15975
+ function cmdInitPhaseOp(cwd, phase) {
15412
15976
  const config = loadConfig(cwd);
15413
15977
  let phaseInfo = findPhaseInternal(cwd, phase ?? "");
15414
15978
  if (!phaseInfo) {
@@ -15457,9 +16021,9 @@ function cmdInitPhaseOp(cwd, phase, raw) {
15457
16021
  if (artifacts.verification_path) result.verification_path = artifacts.verification_path;
15458
16022
  if (artifacts.uat_path) result.uat_path = artifacts.uat_path;
15459
16023
  }
15460
- output(result, raw);
16024
+ return cmdOk(result);
15461
16025
  }
15462
- function cmdInitTodos(cwd, area, raw) {
16026
+ function cmdInitTodos(cwd, area) {
15463
16027
  const config = loadConfig(cwd);
15464
16028
  const now = /* @__PURE__ */ new Date();
15465
16029
  const pendingDir = planningPath(cwd, "todos", "pending");
@@ -15488,7 +16052,7 @@ function cmdInitTodos(cwd, area, raw) {
15488
16052
  } catch (e) {
15489
16053
  debugLog(e);
15490
16054
  }
15491
- output({
16055
+ return cmdOk({
15492
16056
  commit_docs: config.commit_docs,
15493
16057
  date: todayISO(),
15494
16058
  timestamp: now.toISOString(),
@@ -15500,9 +16064,9 @@ function cmdInitTodos(cwd, area, raw) {
15500
16064
  planning_exists: pathExistsInternal(cwd, ".planning"),
15501
16065
  todos_dir_exists: pathExistsInternal(cwd, ".planning/todos"),
15502
16066
  pending_dir_exists: pathExistsInternal(cwd, ".planning/todos/pending")
15503
- }, raw);
16067
+ });
15504
16068
  }
15505
- function cmdInitMilestoneOp(cwd, raw) {
16069
+ function cmdInitMilestoneOp(cwd) {
15506
16070
  const config = loadConfig(cwd);
15507
16071
  const milestone = getMilestoneInfo(cwd);
15508
16072
  let phaseCount = 0;
@@ -15526,7 +16090,7 @@ function cmdInitMilestoneOp(cwd, raw) {
15526
16090
  } catch (e) {
15527
16091
  debugLog(e);
15528
16092
  }
15529
- output({
16093
+ return cmdOk({
15530
16094
  commit_docs: config.commit_docs,
15531
16095
  milestone_version: milestone.version,
15532
16096
  milestone_name: milestone.name,
@@ -15541,9 +16105,9 @@ function cmdInitMilestoneOp(cwd, raw) {
15541
16105
  state_exists: pathExistsInternal(cwd, ".planning/STATE.md"),
15542
16106
  archive_exists: pathExistsInternal(cwd, ".planning/archive"),
15543
16107
  phases_dir_exists: pathExistsInternal(cwd, ".planning/phases")
15544
- }, raw);
16108
+ });
15545
16109
  }
15546
- function cmdInitMapCodebase(cwd, raw) {
16110
+ function cmdInitMapCodebase(cwd) {
15547
16111
  const config = loadConfig(cwd);
15548
16112
  const codebaseDir = planningPath(cwd, "codebase");
15549
16113
  let existingMaps = [];
@@ -15552,7 +16116,7 @@ function cmdInitMapCodebase(cwd, raw) {
15552
16116
  } catch (e) {
15553
16117
  debugLog(e);
15554
16118
  }
15555
- output({
16119
+ return cmdOk({
15556
16120
  mapper_model: resolveModelInternal(cwd, "maxsim-codebase-mapper"),
15557
16121
  commit_docs: config.commit_docs,
15558
16122
  search_gitignored: config.search_gitignored,
@@ -15562,29 +16126,15 @@ function cmdInitMapCodebase(cwd, raw) {
15562
16126
  has_maps: existingMaps.length > 0,
15563
16127
  planning_exists: pathExistsInternal(cwd, ".planning"),
15564
16128
  codebase_dir_exists: pathExistsInternal(cwd, ".planning/codebase")
15565
- }, raw);
16129
+ });
15566
16130
  }
15567
- function cmdInitExisting(cwd, raw) {
16131
+ function cmdInitExisting(cwd) {
15568
16132
  const config = loadConfig(cwd);
15569
16133
  const homedir = node_os.default.homedir();
15570
16134
  const braveKeyFile = node_path.default.join(homedir, ".maxsim", "brave_api_key");
15571
16135
  const hasBraveSearch = !!(process.env.BRAVE_API_KEY || node_fs.default.existsSync(braveKeyFile));
15572
- let hasCode = false;
15573
- let hasPackageFile = false;
15574
- try {
15575
- hasCode = (0, node_child_process.execSync)("find . -maxdepth 3 \\( -name \"*.ts\" -o -name \"*.js\" -o -name \"*.py\" -o -name \"*.go\" -o -name \"*.rs\" -o -name \"*.swift\" -o -name \"*.java\" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5", {
15576
- cwd,
15577
- encoding: "utf-8",
15578
- stdio: [
15579
- "pipe",
15580
- "pipe",
15581
- "pipe"
15582
- ]
15583
- }).trim().length > 0;
15584
- } catch (e) {
15585
- debugLog(e);
15586
- }
15587
- hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
16136
+ const hasCode = findCodeFiles(cwd).length > 0;
16137
+ const hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
15588
16138
  let planningFiles = [];
15589
16139
  try {
15590
16140
  const planDir = planningPath(cwd);
@@ -15592,7 +16142,7 @@ function cmdInitExisting(cwd, raw) {
15592
16142
  } catch (e) {
15593
16143
  debugLog(e);
15594
16144
  }
15595
- output({
16145
+ return cmdOk({
15596
16146
  researcher_model: resolveModelInternal(cwd, "maxsim-project-researcher"),
15597
16147
  synthesizer_model: resolveModelInternal(cwd, "maxsim-research-synthesizer"),
15598
16148
  roadmapper_model: resolveModelInternal(cwd, "maxsim-roadmapper"),
@@ -15612,9 +16162,9 @@ function cmdInitExisting(cwd, raw) {
15612
16162
  parallelization: config.parallelization,
15613
16163
  project_path: ".planning/PROJECT.md",
15614
16164
  codebase_dir: ".planning/codebase"
15615
- }, raw);
16165
+ });
15616
16166
  }
15617
- function cmdInitProgress(cwd, raw) {
16167
+ function cmdInitProgress(cwd) {
15618
16168
  const config = loadConfig(cwd);
15619
16169
  const milestone = getMilestoneInfo(cwd);
15620
16170
  const progressPhasesDir = phasesPath(cwd);
@@ -15629,22 +16179,22 @@ function cmdInitProgress(cwd, raw) {
15629
16179
  const phaseName = match && match[2] ? match[2] : null;
15630
16180
  const phaseDirPath = node_path.default.join(progressPhasesDir, dir);
15631
16181
  const phaseFiles = node_fs.default.readdirSync(phaseDirPath);
15632
- const plans = phaseFiles.filter((f) => isPlanFile(f));
16182
+ const plansList = phaseFiles.filter((f) => isPlanFile(f));
15633
16183
  const summaries = phaseFiles.filter((f) => isSummaryFile(f));
15634
16184
  const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
15635
- const status = summaries.length >= plans.length && plans.length > 0 ? "complete" : plans.length > 0 ? "in_progress" : hasResearch ? "researched" : "pending";
15636
- const phaseInfo = {
16185
+ const status = summaries.length >= plansList.length && plansList.length > 0 ? "complete" : plansList.length > 0 ? "in_progress" : hasResearch ? "researched" : "pending";
16186
+ const phaseInfoItem = {
15637
16187
  number: phaseNumber,
15638
16188
  name: phaseName,
15639
16189
  directory: node_path.default.join(".planning", "phases", dir),
15640
16190
  status,
15641
- plan_count: plans.length,
16191
+ plan_count: plansList.length,
15642
16192
  summary_count: summaries.length,
15643
16193
  has_research: hasResearch
15644
16194
  };
15645
- phases.push(phaseInfo);
15646
- if (!currentPhase && (status === "in_progress" || status === "researched")) currentPhase = phaseInfo;
15647
- if (!nextPhase && status === "pending") nextPhase = phaseInfo;
16195
+ phases.push(phaseInfoItem);
16196
+ if (!currentPhase && (status === "in_progress" || status === "researched")) currentPhase = phaseInfoItem;
16197
+ if (!nextPhase && status === "pending") nextPhase = phaseInfoItem;
15648
16198
  }
15649
16199
  } catch (e) {
15650
16200
  debugLog(e);
@@ -15656,7 +16206,7 @@ function cmdInitProgress(cwd, raw) {
15656
16206
  } catch (e) {
15657
16207
  debugLog(e);
15658
16208
  }
15659
- output({
16209
+ return cmdOk({
15660
16210
  executor_model: resolveModelInternal(cwd, "maxsim-executor"),
15661
16211
  planner_model: resolveModelInternal(cwd, "maxsim-planner"),
15662
16212
  commit_docs: config.commit_docs,
@@ -15677,7 +16227,7 @@ function cmdInitProgress(cwd, raw) {
15677
16227
  roadmap_path: ".planning/ROADMAP.md",
15678
16228
  project_path: ".planning/PROJECT.md",
15679
16229
  config_path: ".planning/config.json"
15680
- }, raw);
16230
+ });
15681
16231
  }
15682
16232
 
15683
16233
  //#endregion
@@ -15705,7 +16255,12 @@ function getFlags(args, ...flags) {
15705
16255
  function hasFlag(args, flag) {
15706
16256
  return args.includes(`--${flag}`);
15707
16257
  }
15708
- const handleState = (args, cwd, raw) => {
16258
+ /** Convert a CmdResult into the appropriate output()/error() call. */
16259
+ function handleResult(r, raw) {
16260
+ if (r.ok) output(r.result, raw, r.rawValue);
16261
+ else error(r.error);
16262
+ }
16263
+ const handleState = async (args, cwd, raw) => {
15709
16264
  const sub = args[1];
15710
16265
  const handler = sub ? {
15711
16266
  "update": () => cmdStateUpdate(cwd, args[2], args[3]),
@@ -15717,12 +16272,12 @@ const handleState = (args, cwd, raw) => {
15717
16272
  const value = args[i + 1];
15718
16273
  if (key && value !== void 0) patches[key] = value;
15719
16274
  }
15720
- cmdStatePatch(cwd, patches, raw);
16275
+ return cmdStatePatch(cwd, patches, raw);
15721
16276
  },
15722
16277
  "advance-plan": () => cmdStateAdvancePlan(cwd, raw),
15723
16278
  "record-metric": () => {
15724
16279
  const f = getFlags(args, "phase", "plan", "duration", "tasks", "files");
15725
- cmdStateRecordMetric(cwd, {
16280
+ return cmdStateRecordMetric(cwd, {
15726
16281
  phase: f.phase ?? "",
15727
16282
  plan: f.plan ?? "",
15728
16283
  duration: f.duration ?? "",
@@ -15733,7 +16288,7 @@ const handleState = (args, cwd, raw) => {
15733
16288
  "update-progress": () => cmdStateUpdateProgress(cwd, raw),
15734
16289
  "add-decision": () => {
15735
16290
  const f = getFlags(args, "phase", "summary", "summary-file", "rationale", "rationale-file");
15736
- cmdStateAddDecision(cwd, {
16291
+ return cmdStateAddDecision(cwd, {
15737
16292
  phase: f.phase ?? void 0,
15738
16293
  summary: f.summary ?? void 0,
15739
16294
  summary_file: f["summary-file"] ?? void 0,
@@ -15743,7 +16298,7 @@ const handleState = (args, cwd, raw) => {
15743
16298
  },
15744
16299
  "add-blocker": () => {
15745
16300
  const f = getFlags(args, "text", "text-file");
15746
- cmdStateAddBlocker(cwd, {
16301
+ return cmdStateAddBlocker(cwd, {
15747
16302
  text: f.text ?? void 0,
15748
16303
  text_file: f["text-file"] ?? void 0
15749
16304
  }, raw);
@@ -15751,38 +16306,38 @@ const handleState = (args, cwd, raw) => {
15751
16306
  "resolve-blocker": () => cmdStateResolveBlocker(cwd, getFlag(args, "--text"), raw),
15752
16307
  "record-session": () => {
15753
16308
  const f = getFlags(args, "stopped-at", "resume-file");
15754
- cmdStateRecordSession(cwd, {
16309
+ return cmdStateRecordSession(cwd, {
15755
16310
  stopped_at: f["stopped-at"] ?? void 0,
15756
16311
  resume_file: f["resume-file"] ?? "None"
15757
16312
  }, raw);
15758
16313
  }
15759
16314
  }[sub] : void 0;
15760
- if (handler) return handler();
15761
- cmdStateLoad(cwd, raw);
16315
+ if (handler) return handleResult(await handler(), raw);
16316
+ return handleResult(await cmdStateLoad(cwd, raw), raw);
15762
16317
  };
15763
16318
  const handleTemplate = (args, cwd, raw) => {
15764
16319
  const sub = args[1];
15765
- if (sub === "select") cmdTemplateSelect(cwd, args[2], raw);
16320
+ if (sub === "select") handleResult(cmdTemplateSelect(cwd, args[2]), raw);
15766
16321
  else if (sub === "fill") {
15767
16322
  const f = getFlags(args, "phase", "plan", "name", "type", "wave", "fields");
15768
- cmdTemplateFill(cwd, args[2], {
16323
+ handleResult(cmdTemplateFill(cwd, args[2], {
15769
16324
  phase: f.phase ?? "",
15770
16325
  plan: f.plan ?? void 0,
15771
16326
  name: f.name ?? void 0,
15772
16327
  type: f.type ?? "execute",
15773
16328
  wave: f.wave ?? "1",
15774
16329
  fields: f.fields ? JSON.parse(f.fields) : {}
15775
- }, raw);
16330
+ }), raw);
15776
16331
  } else error("Unknown template subcommand. Available: select, fill");
15777
16332
  };
15778
16333
  const handleFrontmatter = (args, cwd, raw) => {
15779
16334
  const sub = args[1];
15780
16335
  const file = args[2];
15781
16336
  const handler = sub ? {
15782
- "get": () => cmdFrontmatterGet(cwd, file, getFlag(args, "--field"), raw),
15783
- "set": () => cmdFrontmatterSet(cwd, file, getFlag(args, "--field"), getFlag(args, "--value") ?? void 0, raw),
15784
- "merge": () => cmdFrontmatterMerge(cwd, file, getFlag(args, "--data"), raw),
15785
- "validate": () => cmdFrontmatterValidate(cwd, file, getFlag(args, "--schema"), raw)
16337
+ "get": () => handleResult(cmdFrontmatterGet(cwd, file, getFlag(args, "--field")), raw),
16338
+ "set": () => handleResult(cmdFrontmatterSet(cwd, file, getFlag(args, "--field"), getFlag(args, "--value") ?? void 0), raw),
16339
+ "merge": () => handleResult(cmdFrontmatterMerge(cwd, file, getFlag(args, "--data")), raw),
16340
+ "validate": () => handleResult(cmdFrontmatterValidate(cwd, file, getFlag(args, "--schema")), raw)
15786
16341
  }[sub] : void 0;
15787
16342
  if (handler) return handler();
15788
16343
  error("Unknown frontmatter subcommand. Available: get, set, merge, validate");
@@ -15790,46 +16345,48 @@ const handleFrontmatter = (args, cwd, raw) => {
15790
16345
  const handleVerify = async (args, cwd, raw) => {
15791
16346
  const sub = args[1];
15792
16347
  const handler = sub ? {
15793
- "plan-structure": () => cmdVerifyPlanStructure(cwd, args[2], raw),
15794
- "phase-completeness": () => cmdVerifyPhaseCompleteness(cwd, args[2], raw),
15795
- "references": () => cmdVerifyReferences(cwd, args[2], raw),
15796
- "commits": () => cmdVerifyCommits(cwd, args.slice(2), raw),
15797
- "artifacts": () => cmdVerifyArtifacts(cwd, args[2], raw),
15798
- "key-links": () => cmdVerifyKeyLinks(cwd, args[2], raw)
16348
+ "plan-structure": () => handleResult(cmdVerifyPlanStructure(cwd, args[2]), raw),
16349
+ "phase-completeness": () => handleResult(cmdVerifyPhaseCompleteness(cwd, args[2]), raw),
16350
+ "references": () => handleResult(cmdVerifyReferences(cwd, args[2]), raw),
16351
+ "commits": async () => handleResult(await cmdVerifyCommits(cwd, args.slice(2)), raw),
16352
+ "artifacts": () => handleResult(cmdVerifyArtifacts(cwd, args[2]), raw),
16353
+ "key-links": () => handleResult(cmdVerifyKeyLinks(cwd, args[2]), raw)
15799
16354
  }[sub] : void 0;
15800
16355
  if (handler) return handler();
15801
16356
  error("Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links");
15802
16357
  };
15803
- const handlePhases = (args, cwd, raw) => {
16358
+ const handlePhases = async (args, cwd, raw) => {
15804
16359
  if (args[1] === "list") {
15805
- const f = getFlags(args, "type", "phase");
15806
- cmdPhasesList(cwd, {
16360
+ const f = getFlags(args, "type", "phase", "offset", "limit");
16361
+ handleResult(await cmdPhasesList(cwd, {
15807
16362
  type: f.type,
15808
16363
  phase: f.phase,
15809
- includeArchived: hasFlag(args, "include-archived")
15810
- }, raw);
16364
+ includeArchived: hasFlag(args, "include-archived"),
16365
+ offset: f.offset !== null ? parseInt(f.offset, 10) : void 0,
16366
+ limit: f.limit !== null ? parseInt(f.limit, 10) : void 0
16367
+ }), raw);
15811
16368
  } else error("Unknown phases subcommand. Available: list");
15812
16369
  };
15813
- const handleRoadmap = (args, cwd, raw) => {
16370
+ const handleRoadmap = async (args, cwd, raw) => {
15814
16371
  const sub = args[1];
15815
16372
  const handler = sub ? {
15816
- "get-phase": () => cmdRoadmapGetPhase(cwd, args[2], raw),
15817
- "analyze": () => cmdRoadmapAnalyze(cwd, raw),
15818
- "update-plan-progress": () => cmdRoadmapUpdatePlanProgress(cwd, args[2], raw)
16373
+ "get-phase": () => cmdRoadmapGetPhase(cwd, args[2]),
16374
+ "analyze": () => cmdRoadmapAnalyze(cwd),
16375
+ "update-plan-progress": () => cmdRoadmapUpdatePlanProgress(cwd, args[2])
15819
16376
  }[sub] : void 0;
15820
- if (handler) return handler();
16377
+ if (handler) return handleResult(await handler(), raw);
15821
16378
  error("Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress");
15822
16379
  };
15823
16380
  const handlePhase = (args, cwd, raw) => {
15824
16381
  const sub = args[1];
15825
16382
  const handler = sub ? {
15826
- "next-decimal": () => cmdPhaseNextDecimal(cwd, args[2], raw),
15827
- "add": () => cmdPhaseAdd(cwd, args.slice(2).join(" "), raw),
15828
- "insert": () => cmdPhaseInsert(cwd, args[2], args.slice(3).join(" "), raw),
15829
- "remove": () => cmdPhaseRemove(cwd, args[2], { force: hasFlag(args, "force") }, raw),
15830
- "complete": () => cmdPhaseComplete(cwd, args[2], raw)
16383
+ "next-decimal": () => cmdPhaseNextDecimal(cwd, args[2]),
16384
+ "add": () => cmdPhaseAdd(cwd, args.slice(2).join(" ")),
16385
+ "insert": () => cmdPhaseInsert(cwd, args[2], args.slice(3).join(" ")),
16386
+ "remove": () => cmdPhaseRemove(cwd, args[2], { force: hasFlag(args, "force") }),
16387
+ "complete": () => cmdPhaseComplete(cwd, args[2])
15831
16388
  }[sub] : void 0;
15832
- if (handler) return handler();
16389
+ if (handler) return handleResult(handler(), raw);
15833
16390
  error("Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete");
15834
16391
  };
15835
16392
  const handleMilestone = (args, cwd, raw) => {
@@ -15844,17 +16401,17 @@ const handleMilestone = (args, cwd, raw) => {
15844
16401
  }
15845
16402
  milestoneName = nameArgs.join(" ") || null;
15846
16403
  }
15847
- cmdMilestoneComplete(cwd, args[2], {
16404
+ handleResult(cmdMilestoneComplete(cwd, args[2], {
15848
16405
  name: milestoneName ?? void 0,
15849
16406
  archivePhases: hasFlag(args, "archive-phases")
15850
- }, raw);
16407
+ }), raw);
15851
16408
  } else error("Unknown milestone subcommand. Available: complete");
15852
16409
  };
15853
16410
  const handleValidate = (args, cwd, raw) => {
15854
16411
  const sub = args[1];
15855
16412
  const handler = sub ? {
15856
- "consistency": () => cmdValidateConsistency(cwd, raw),
15857
- "health": () => cmdValidateHealth(cwd, { repair: hasFlag(args, "repair") }, raw)
16413
+ "consistency": () => handleResult(cmdValidateConsistency(cwd), raw),
16414
+ "health": () => handleResult(cmdValidateHealth(cwd, { repair: hasFlag(args, "repair") }), raw)
15858
16415
  }[sub] : void 0;
15859
16416
  if (handler) return handler();
15860
16417
  error("Unknown validate subcommand. Available: consistency, health");
@@ -15862,83 +16419,95 @@ const handleValidate = (args, cwd, raw) => {
15862
16419
  const handleInit = (args, cwd, raw) => {
15863
16420
  const workflow = args[1];
15864
16421
  const handler = workflow ? {
15865
- "execute-phase": () => cmdInitExecutePhase(cwd, args[2], raw),
15866
- "plan-phase": () => cmdInitPlanPhase(cwd, args[2], raw),
15867
- "new-project": () => cmdInitNewProject(cwd, raw),
15868
- "new-milestone": () => cmdInitNewMilestone(cwd, raw),
15869
- "quick": () => cmdInitQuick(cwd, args.slice(2).join(" "), raw),
15870
- "resume": () => cmdInitResume(cwd, raw),
15871
- "verify-work": () => cmdInitVerifyWork(cwd, args[2], raw),
15872
- "phase-op": () => cmdInitPhaseOp(cwd, args[2], raw),
15873
- "todos": () => cmdInitTodos(cwd, args[2], raw),
15874
- "milestone-op": () => cmdInitMilestoneOp(cwd, raw),
15875
- "map-codebase": () => cmdInitMapCodebase(cwd, raw),
15876
- "init-existing": () => cmdInitExisting(cwd, raw),
15877
- "progress": () => cmdInitProgress(cwd, raw)
16422
+ "execute-phase": () => cmdInitExecutePhase(cwd, args[2]),
16423
+ "plan-phase": () => cmdInitPlanPhase(cwd, args[2]),
16424
+ "new-project": () => cmdInitNewProject(cwd),
16425
+ "new-milestone": () => cmdInitNewMilestone(cwd),
16426
+ "quick": () => cmdInitQuick(cwd, args.slice(2).join(" ")),
16427
+ "resume": () => cmdInitResume(cwd),
16428
+ "verify-work": () => cmdInitVerifyWork(cwd, args[2]),
16429
+ "phase-op": () => cmdInitPhaseOp(cwd, args[2]),
16430
+ "todos": () => cmdInitTodos(cwd, args[2]),
16431
+ "milestone-op": () => cmdInitMilestoneOp(cwd),
16432
+ "map-codebase": () => cmdInitMapCodebase(cwd),
16433
+ "init-existing": () => cmdInitExisting(cwd),
16434
+ "progress": () => cmdInitProgress(cwd)
15878
16435
  }[workflow] : void 0;
15879
- if (handler) return handler();
16436
+ if (handler) return handleResult(handler(), raw);
15880
16437
  error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, init-existing, progress`);
15881
16438
  };
15882
16439
  const COMMANDS = {
15883
16440
  "state": handleState,
15884
- "resolve-model": (args, cwd, raw) => cmdResolveModel(cwd, args[1], raw),
15885
- "find-phase": (args, cwd, raw) => cmdFindPhase(cwd, args[1], raw),
16441
+ "resolve-model": (args, cwd, raw) => handleResult(cmdResolveModel(cwd, args[1], raw), raw),
16442
+ "find-phase": (args, cwd, raw) => handleResult(cmdFindPhase(cwd, args[1]), raw),
15886
16443
  "commit": async (args, cwd, raw) => {
15887
16444
  const files = args.indexOf("--files") !== -1 ? args.slice(args.indexOf("--files") + 1).filter((a) => !a.startsWith("--")) : [];
15888
- await cmdCommit(cwd, args[1], files, raw, hasFlag(args, "amend"));
16445
+ handleResult(await cmdCommit(cwd, args[1], files, raw, hasFlag(args, "amend")), raw);
15889
16446
  },
15890
16447
  "verify-summary": async (args, cwd, raw) => {
15891
16448
  const countIndex = args.indexOf("--check-count");
15892
16449
  const checkCount = countIndex !== -1 ? parseInt(args[countIndex + 1], 10) : 2;
15893
- await cmdVerifySummary(cwd, args[1], checkCount, raw);
16450
+ handleResult(await cmdVerifySummary(cwd, args[1], checkCount), raw);
15894
16451
  },
15895
16452
  "template": handleTemplate,
15896
16453
  "frontmatter": handleFrontmatter,
15897
16454
  "verify": handleVerify,
15898
- "generate-slug": (args, _cwd, raw) => cmdGenerateSlug(args[1], raw),
15899
- "current-timestamp": (args, _cwd, raw) => cmdCurrentTimestamp(args[1] || "full", raw),
15900
- "list-todos": (args, cwd, raw) => cmdListTodos(cwd, args[1], raw),
15901
- "verify-path-exists": (args, cwd, raw) => cmdVerifyPathExists(cwd, args[1], raw),
15902
- "config-ensure-section": (_args, cwd, raw) => cmdConfigEnsureSection(cwd, raw),
15903
- "config-set": (args, cwd, raw) => cmdConfigSet(cwd, args[1], args[2], raw),
15904
- "config-get": (args, cwd, raw) => cmdConfigGet(cwd, args[1], raw),
15905
- "history-digest": (_args, cwd, raw) => cmdHistoryDigest(cwd, raw),
16455
+ "generate-slug": (args, _cwd, raw) => handleResult(cmdGenerateSlug(args[1], raw), raw),
16456
+ "current-timestamp": (args, _cwd, raw) => handleResult(cmdCurrentTimestamp(args[1] || "full", raw), raw),
16457
+ "list-todos": (args, cwd, raw) => handleResult(cmdListTodos(cwd, args[1], raw), raw),
16458
+ "verify-path-exists": (args, cwd, raw) => handleResult(cmdVerifyPathExists(cwd, args[1], raw), raw),
16459
+ "config-ensure-section": (_args, cwd, raw) => handleResult(cmdConfigEnsureSection(cwd, raw), raw),
16460
+ "config-set": (args, cwd, raw) => handleResult(cmdConfigSet(cwd, args[1], args[2], raw), raw),
16461
+ "config-get": (args, cwd, raw) => handleResult(cmdConfigGet(cwd, args[1], raw), raw),
16462
+ "history-digest": (_args, cwd, raw) => handleResult(cmdHistoryDigest(cwd, raw), raw),
15906
16463
  "phases": handlePhases,
15907
16464
  "roadmap": handleRoadmap,
15908
16465
  "requirements": (args, cwd, raw) => {
15909
- if (args[1] === "mark-complete") cmdRequirementsMarkComplete(cwd, args.slice(2), raw);
16466
+ if (args[1] === "mark-complete") handleResult(cmdRequirementsMarkComplete(cwd, args.slice(2)), raw);
15910
16467
  else error("Unknown requirements subcommand. Available: mark-complete");
15911
16468
  },
15912
16469
  "phase": handlePhase,
15913
16470
  "milestone": handleMilestone,
15914
16471
  "validate": handleValidate,
15915
- "progress": (args, cwd, raw) => cmdProgressRender(cwd, args[1] || "json", raw),
16472
+ "progress": (args, cwd, raw) => handleResult(cmdProgressRender(cwd, args[1] || "json", raw), raw),
15916
16473
  "todo": (args, cwd, raw) => {
15917
- if (args[1] === "complete") cmdTodoComplete(cwd, args[2], raw);
16474
+ if (args[1] === "complete") handleResult(cmdTodoComplete(cwd, args[2], raw), raw);
15918
16475
  else error("Unknown todo subcommand. Available: complete");
15919
16476
  },
15920
16477
  "scaffold": (args, cwd, raw) => {
15921
16478
  const f = getFlags(args, "phase", "name");
15922
- cmdScaffold(cwd, args[1], {
16479
+ handleResult(cmdScaffold(cwd, args[1], {
15923
16480
  phase: f.phase,
15924
16481
  name: f.name ? args.slice(args.indexOf("--name") + 1).join(" ") : null
15925
- }, raw);
16482
+ }, raw), raw);
15926
16483
  },
15927
16484
  "init": handleInit,
15928
- "phase-plan-index": (args, cwd, raw) => cmdPhasePlanIndex(cwd, args[1], raw),
15929
- "state-snapshot": (_args, cwd, raw) => cmdStateSnapshot(cwd, raw),
16485
+ "phase-plan-index": (args, cwd, raw) => handleResult(cmdPhasePlanIndex(cwd, args[1]), raw),
16486
+ "state-snapshot": (_args, cwd, raw) => handleResult(cmdStateSnapshot(cwd, raw), raw),
15930
16487
  "summary-extract": (args, cwd, raw) => {
15931
16488
  const fieldsIndex = args.indexOf("--fields");
15932
16489
  const fields = fieldsIndex !== -1 ? args[fieldsIndex + 1].split(",") : null;
15933
- cmdSummaryExtract(cwd, args[1], fields, raw);
16490
+ handleResult(cmdSummaryExtract(cwd, args[1], fields, raw), raw);
15934
16491
  },
15935
16492
  "websearch": async (args, _cwd, raw) => {
15936
16493
  const f = getFlags(args, "limit", "freshness");
15937
- await cmdWebsearch(args[1], {
16494
+ handleResult(await cmdWebsearch(args[1], {
15938
16495
  limit: f.limit ? parseInt(f.limit, 10) : 10,
15939
16496
  freshness: f.freshness ?? void 0
15940
- }, raw);
16497
+ }, raw), raw);
15941
16498
  },
16499
+ "artefakte-read": (args, cwd, raw) => handleResult(cmdArtefakteRead(cwd, args[1], getFlag(args, "--phase") ?? void 0, raw), raw),
16500
+ "artefakte-write": (args, cwd, raw) => handleResult(cmdArtefakteWrite(cwd, args[1], getFlag(args, "--content") ?? void 0, getFlag(args, "--phase") ?? void 0, raw), raw),
16501
+ "artefakte-append": (args, cwd, raw) => handleResult(cmdArtefakteAppend(cwd, args[1], getFlag(args, "--entry") ?? void 0, getFlag(args, "--phase") ?? void 0, raw), raw),
16502
+ "artefakte-list": (args, cwd, raw) => handleResult(cmdArtefakteList(cwd, getFlag(args, "--phase") ?? void 0, raw), raw),
16503
+ "context-load": (args, cwd, raw) => handleResult(cmdContextLoad(cwd, getFlag(args, "--phase") ?? void 0, getFlag(args, "--topic") ?? void 0, hasFlag(args, "include-history")), raw),
16504
+ "skill-list": (_args, cwd, raw) => cmdSkillList(cwd, raw),
16505
+ "skill-install": (args, cwd, raw) => cmdSkillInstall(cwd, args[1], raw),
16506
+ "skill-update": (args, cwd, raw) => cmdSkillUpdate(cwd, args[1], raw),
16507
+ "start": async (args, cwd, raw) => handleResult(await cmdStart(cwd, {
16508
+ noBrowser: hasFlag(args, "no-browser"),
16509
+ networkMode: hasFlag(args, "network")
16510
+ }), raw),
15942
16511
  "dashboard": (args) => handleDashboard(args.slice(1)),
15943
16512
  "start-server": async () => {
15944
16513
  const serverPath = node_path.join(__dirname, "mcp-server.cjs");
@@ -15946,30 +16515,42 @@ const COMMANDS = {
15946
16515
  }
15947
16516
  };
15948
16517
  async function main() {
15949
- const args = process.argv.slice(2);
15950
- let cwd = process.cwd();
15951
- const cwdEqArg = args.find((arg) => arg.startsWith("--cwd="));
15952
- const cwdIdx = args.indexOf("--cwd");
15953
- if (cwdEqArg) {
15954
- const value = cwdEqArg.slice(6).trim();
15955
- if (!value) error("Missing value for --cwd");
15956
- args.splice(args.indexOf(cwdEqArg), 1);
15957
- cwd = node_path.resolve(value);
15958
- } else if (cwdIdx !== -1) {
15959
- const value = args[cwdIdx + 1];
15960
- if (!value || value.startsWith("--")) error("Missing value for --cwd");
15961
- args.splice(cwdIdx, 2);
15962
- cwd = node_path.resolve(value);
15963
- }
15964
- if (!node_fs.existsSync(cwd) || !node_fs.statSync(cwd).isDirectory()) error(`Invalid --cwd: ${cwd}`);
15965
- const rawIndex = args.indexOf("--raw");
15966
- const raw = rawIndex !== -1;
15967
- if (rawIndex !== -1) args.splice(rawIndex, 1);
15968
- const command = args[0];
15969
- if (!command) error(`Usage: maxsim-tools <command> [args] [--raw] [--cwd <path>]\nCommands: ${Object.keys(COMMANDS).join(", ")}`);
15970
- const handler = COMMANDS[command];
15971
- if (!handler) error(`Unknown command: ${command}`);
15972
- await handler(args, cwd, raw);
16518
+ try {
16519
+ const args = process.argv.slice(2);
16520
+ let cwd = process.cwd();
16521
+ const cwdEqArg = args.find((arg) => arg.startsWith("--cwd="));
16522
+ const cwdIdx = args.indexOf("--cwd");
16523
+ if (cwdEqArg) {
16524
+ const value = cwdEqArg.slice(6).trim();
16525
+ if (!value) error("Missing value for --cwd");
16526
+ args.splice(args.indexOf(cwdEqArg), 1);
16527
+ cwd = node_path.resolve(value);
16528
+ } else if (cwdIdx !== -1) {
16529
+ const value = args[cwdIdx + 1];
16530
+ if (!value || value.startsWith("--")) error("Missing value for --cwd");
16531
+ args.splice(cwdIdx, 2);
16532
+ cwd = node_path.resolve(value);
16533
+ }
16534
+ if (!node_fs.existsSync(cwd) || !node_fs.statSync(cwd).isDirectory()) error(`Invalid --cwd: ${cwd}`);
16535
+ const rawIndex = args.indexOf("--raw");
16536
+ const raw = rawIndex !== -1;
16537
+ if (rawIndex !== -1) args.splice(rawIndex, 1);
16538
+ const command = args[0];
16539
+ if (!command) error(`Usage: maxsim-tools <command> [args] [--raw] [--cwd <path>]\nCommands: ${Object.keys(COMMANDS).join(", ")}`);
16540
+ const handler = COMMANDS[command];
16541
+ if (!handler) error(`Unknown command: ${command}`);
16542
+ await handler(args, cwd, raw);
16543
+ } catch (thrown) {
16544
+ if (thrown instanceof CliOutput) {
16545
+ writeOutput(thrown);
16546
+ process.exit(0);
16547
+ }
16548
+ if (thrown instanceof CliError) {
16549
+ process.stderr.write("Error: " + thrown.message + "\n");
16550
+ process.exit(1);
16551
+ }
16552
+ throw thrown;
16553
+ }
15973
16554
  }
15974
16555
  /**
15975
16556
  * Dashboard launch command.
@@ -15979,40 +16560,20 @@ async function main() {
15979
16560
  * Supports --stop to kill a running instance.
15980
16561
  */
15981
16562
  async function handleDashboard(args) {
15982
- const DEFAULT_PORT = 3333;
15983
- const PORT_RANGE_END = 3343;
15984
- const HEALTH_TIMEOUT_MS = 1500;
15985
16563
  const networkMode = args.includes("--network");
15986
16564
  if (args.includes("--stop")) {
15987
- for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port, HEALTH_TIMEOUT_MS)) {
15988
- console.log(`Dashboard found on port ${port} — sending shutdown...`);
15989
- console.log(`Dashboard at http://localhost:${port} is running.`);
15990
- console.log(`To stop it, close the browser tab or kill the process on port ${port}.`);
15991
- try {
15992
- if (process.platform === "win32") {
15993
- const lines = (0, node_child_process.execSync)(`netstat -ano | findstr :${port} | findstr LISTENING`, { encoding: "utf-8" }).trim().split("\n");
15994
- const pids = /* @__PURE__ */ new Set();
15995
- for (const line of lines) {
15996
- const parts = line.trim().split(/\s+/);
15997
- const pid = parts[parts.length - 1];
15998
- if (pid && pid !== "0") pids.add(pid);
15999
- }
16000
- for (const pid of pids) try {
16001
- (0, node_child_process.execSync)(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
16002
- console.log(`Killed process ${pid}`);
16003
- } catch {}
16004
- } else (0, node_child_process.execSync)(`lsof -i :${port} -t | xargs kill -SIGTERM 2>/dev/null`, { stdio: "ignore" });
16005
- console.log("Dashboard stopped.");
16006
- } catch {
16007
- console.log("Could not automatically stop the dashboard. Kill the process manually.");
16008
- }
16565
+ for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port)) {
16566
+ console.log(`Dashboard found on port ${port} — stopping...`);
16567
+ killProcessOnPort(port);
16568
+ console.log("Dashboard stopped.");
16009
16569
  return;
16010
16570
  }
16011
16571
  console.log("No running dashboard found.");
16012
16572
  return;
16013
16573
  }
16014
- for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port, HEALTH_TIMEOUT_MS)) {
16015
- console.log(`Dashboard already running at http://localhost:${port}`);
16574
+ const runningPort = await findRunningDashboard();
16575
+ if (runningPort) {
16576
+ console.log(`Dashboard already running at http://localhost:${runningPort}`);
16016
16577
  return;
16017
16578
  }
16018
16579
  const serverPath = resolveDashboardServer();
@@ -16021,100 +16582,25 @@ async function handleDashboard(args) {
16021
16582
  console.error("Ensure @maxsim/dashboard is installed and built.");
16022
16583
  process.exit(1);
16023
16584
  }
16024
- const isTsFile = serverPath.endsWith(".ts");
16025
- const runner = "node";
16026
- const runnerArgs = isTsFile ? [
16027
- "--import",
16028
- "tsx",
16029
- serverPath
16030
- ] : [serverPath];
16031
16585
  const serverDir = node_path.dirname(serverPath);
16032
- let projectCwd = process.cwd();
16033
- const dashboardConfigPath = node_path.join(node_path.dirname(serverDir), "dashboard.json");
16034
- if (node_fs.existsSync(dashboardConfigPath)) try {
16035
- const config = JSON.parse(node_fs.readFileSync(dashboardConfigPath, "utf8"));
16036
- if (config.projectCwd) projectCwd = config.projectCwd;
16037
- } catch {}
16038
- const ptyModulePath = node_path.join(serverDir, "node_modules", "node-pty");
16039
- if (!node_fs.existsSync(ptyModulePath)) {
16040
- console.log("Installing node-pty for terminal support...");
16041
- try {
16042
- (0, node_child_process.execSync)("npm install node-pty --save-optional --no-audit --no-fund --loglevel=error", {
16043
- cwd: serverDir,
16044
- stdio: "inherit",
16045
- timeout: 12e4
16046
- });
16047
- } catch {
16048
- console.warn("node-pty installation failed — terminal will be unavailable.");
16049
- }
16050
- }
16586
+ const dashConfig = readDashboardConfig(serverPath);
16587
+ console.log("Installing node-pty for terminal support...");
16588
+ if (!ensureNodePty(serverDir)) console.warn("node-pty installation failed — terminal will be unavailable.");
16051
16589
  console.log("Dashboard starting...");
16052
- const child = (0, node_child_process.spawn)(runner, runnerArgs, {
16053
- cwd: serverDir,
16054
- detached: true,
16055
- stdio: "ignore",
16056
- env: {
16057
- ...process.env,
16058
- MAXSIM_PROJECT_CWD: projectCwd,
16059
- NODE_ENV: isTsFile ? "development" : "production",
16060
- ...networkMode ? { MAXSIM_NETWORK_MODE: "1" } : {}
16061
- },
16062
- ...process.platform === "win32" ? { shell: true } : {}
16590
+ const pid = spawnDashboard({
16591
+ serverPath,
16592
+ projectCwd: dashConfig.projectCwd,
16593
+ networkMode
16063
16594
  });
16064
- child.unref();
16065
16595
  await new Promise((resolve) => setTimeout(resolve, 3e3));
16066
- for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port, HEALTH_TIMEOUT_MS)) {
16067
- console.log(`Dashboard ready at http://localhost:${port}`);
16596
+ const readyPort = await findRunningDashboard();
16597
+ if (readyPort) {
16598
+ console.log(`Dashboard ready at http://localhost:${readyPort}`);
16068
16599
  return;
16069
16600
  }
16070
- console.log(`Dashboard spawned (PID ${child.pid}). It may take a moment to start.`);
16601
+ console.log(`Dashboard spawned (PID ${pid}). It may take a moment to start.`);
16071
16602
  console.log(`Check http://localhost:${DEFAULT_PORT} once ready.`);
16072
16603
  }
16073
- /**
16074
- * Check if a dashboard health endpoint is responding on the given port.
16075
- */
16076
- async function checkHealth(port, timeoutMs) {
16077
- try {
16078
- const controller = new AbortController();
16079
- const timer = setTimeout(() => controller.abort(), timeoutMs);
16080
- const res = await fetch(`http://localhost:${port}/api/health`, { signal: controller.signal });
16081
- clearTimeout(timer);
16082
- if (res.ok) return (await res.json()).status === "ok";
16083
- return false;
16084
- } catch {
16085
- return false;
16086
- }
16087
- }
16088
- /**
16089
- * Resolve the dashboard server entry point path.
16090
- * Tries: built server.js first, then source server.ts for dev mode.
16091
- */
16092
- function resolveDashboardServer() {
16093
- const localDashboard = node_path.join(process.cwd(), ".claude", "dashboard", "server.js");
16094
- if (node_fs.existsSync(localDashboard)) return localDashboard;
16095
- const globalDashboard = node_path.join(node_os.homedir(), ".claude", "dashboard", "server.js");
16096
- if (node_fs.existsSync(globalDashboard)) return globalDashboard;
16097
- try {
16098
- const pkgPath = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href).resolve("@maxsim/dashboard/package.json");
16099
- const pkgDir = node_path.dirname(pkgPath);
16100
- const serverJs = node_path.join(pkgDir, "server.js");
16101
- if (node_fs.existsSync(serverJs)) return serverJs;
16102
- const serverTs = node_path.join(pkgDir, "server.ts");
16103
- if (node_fs.existsSync(serverTs)) return serverTs;
16104
- } catch {}
16105
- try {
16106
- let dir = node_path.dirname(new URL(require("url").pathToFileURL(__filename).href).pathname);
16107
- if (process.platform === "win32" && dir.startsWith("/")) dir = dir.slice(1);
16108
- for (let i = 0; i < 5; i++) {
16109
- const candidate = node_path.join(dir, "packages", "dashboard", "server.ts");
16110
- if (node_fs.existsSync(candidate)) return candidate;
16111
- const candidateJs = node_path.join(dir, "packages", "dashboard", "server.js");
16112
- if (node_fs.existsSync(candidateJs)) return candidateJs;
16113
- dir = node_path.dirname(dir);
16114
- }
16115
- } catch {}
16116
- return null;
16117
- }
16118
16604
  main();
16119
16605
 
16120
16606
  //#endregion