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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/index.d.ts +0 -11
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +4 -40
- package/dist/adapters/index.js.map +1 -1
- package/dist/assets/CHANGELOG.md +36 -0
- package/dist/assets/dashboard/client/assets/{index-CZ8WC97G.js → index-C_eAetZJ.js} +66 -66
- package/dist/assets/dashboard/client/assets/index-CmiJKqOU.css +32 -0
- package/dist/assets/dashboard/client/index.html +2 -2
- package/dist/assets/dashboard/server.js +467 -271
- package/dist/assets/templates/agents/AGENTS.md +94 -0
- package/dist/assets/templates/agents/maxsim-debugger.md +2 -2
- package/dist/assets/templates/agents/maxsim-executor.md +5 -5
- package/dist/assets/templates/agents/maxsim-phase-researcher.md +2 -2
- package/dist/assets/templates/agents/maxsim-plan-checker.md +2 -2
- package/dist/assets/templates/agents/maxsim-planner.md +3 -3
- package/dist/assets/templates/commands/maxsim/add-todo.md +15 -5
- package/dist/assets/templates/commands/maxsim/discuss-phase.md +1 -0
- package/dist/assets/templates/commands/maxsim/init-existing.md +4 -0
- package/dist/assets/templates/commands/maxsim/new-project.md +4 -0
- package/dist/assets/templates/commands/maxsim/settings.md +1 -1
- package/dist/assets/templates/references/thinking-partner.md +41 -0
- package/dist/assets/templates/skills/batch-worktree/SKILL.md +137 -0
- package/dist/assets/templates/skills/brainstorming/SKILL.md +159 -0
- package/dist/assets/templates/skills/code-review/SKILL.md +151 -0
- package/dist/assets/templates/skills/memory-management/SKILL.md +174 -0
- package/dist/assets/templates/skills/roadmap-writing/SKILL.md +198 -0
- package/dist/assets/templates/skills/sdd/SKILL.md +175 -0
- package/dist/assets/templates/skills/simplify/SKILL.md +185 -0
- package/dist/assets/templates/skills/using-maxsim/SKILL.md +120 -0
- package/dist/assets/templates/templates/acceptance-criteria.md +10 -0
- package/dist/assets/templates/templates/config.json +1 -1
- package/dist/assets/templates/templates/decisions.md +10 -0
- package/dist/assets/templates/templates/no-gos.md +9 -0
- package/dist/assets/templates/workflows/add-tests.md +3 -3
- package/dist/assets/templates/workflows/add-todo.md +89 -0
- package/dist/assets/templates/workflows/complete-milestone.md +1 -1
- package/dist/assets/templates/workflows/discuss-phase.md +85 -1
- package/dist/assets/templates/workflows/execute-phase.md +26 -16
- package/dist/assets/templates/workflows/execute-plan.md +166 -0
- package/dist/assets/templates/workflows/init-existing.md +123 -3
- package/dist/assets/templates/workflows/new-milestone.md +4 -0
- package/dist/assets/templates/workflows/new-project.md +111 -3
- package/dist/assets/templates/workflows/plan-phase.md +5 -5
- package/dist/assets/templates/workflows/quick.md +2 -2
- package/dist/assets/templates/workflows/settings.md +8 -4
- package/dist/assets/templates/workflows/verify-work.md +1 -1
- package/dist/cli.cjs +1512 -1026
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +170 -278
- package/dist/cli.js.map +1 -1
- package/dist/core/artefakte.d.ts +12 -0
- package/dist/core/artefakte.d.ts.map +1 -0
- package/dist/core/artefakte.js +136 -0
- package/dist/core/artefakte.js.map +1 -0
- package/dist/core/commands.d.ts +13 -13
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +48 -58
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +4 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -18
- package/dist/core/config.js.map +1 -1
- package/dist/core/context-loader.d.ts +20 -0
- package/dist/core/context-loader.d.ts.map +1 -0
- package/dist/core/context-loader.js +154 -0
- package/dist/core/context-loader.js.map +1 -0
- package/dist/core/core.d.ts +26 -2
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.js +90 -24
- package/dist/core/core.js.map +1 -1
- package/dist/core/dashboard-launcher.d.ts +56 -0
- package/dist/core/dashboard-launcher.d.ts.map +1 -0
- package/dist/core/dashboard-launcher.js +246 -0
- package/dist/core/dashboard-launcher.js.map +1 -0
- package/dist/core/frontmatter.d.ts +5 -5
- package/dist/core/frontmatter.d.ts.map +1 -1
- package/dist/core/frontmatter.js +21 -26
- package/dist/core/frontmatter.js.map +1 -1
- package/dist/core/index.d.ts +10 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +40 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +14 -15
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +93 -155
- package/dist/core/init.js.map +1 -1
- package/dist/core/milestone.d.ts +3 -3
- package/dist/core/milestone.d.ts.map +1 -1
- package/dist/core/milestone.js +9 -9
- package/dist/core/milestone.js.map +1 -1
- package/dist/core/phase.d.ts +9 -9
- package/dist/core/phase.d.ts.map +1 -1
- package/dist/core/phase.js +65 -63
- package/dist/core/phase.js.map +1 -1
- package/dist/core/roadmap.d.ts +4 -3
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/roadmap.js +46 -108
- package/dist/core/roadmap.js.map +1 -1
- package/dist/core/skills.d.ts +19 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +145 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/start.d.ts +15 -0
- package/dist/core/start.d.ts.map +1 -0
- package/dist/core/start.js +80 -0
- package/dist/core/start.js.map +1 -0
- package/dist/core/state.d.ts +13 -13
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +125 -130
- package/dist/core/state.js.map +1 -1
- package/dist/core/template.d.ts +3 -3
- package/dist/core/template.d.ts.map +1 -1
- package/dist/core/template.js +12 -14
- package/dist/core/template.js.map +1 -1
- package/dist/core/types.d.ts +15 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -2
- package/dist/core/types.js.map +1 -1
- package/dist/core/verify.d.ts +10 -9
- package/dist/core/verify.d.ts.map +1 -1
- package/dist/core/verify.js +38 -48
- package/dist/core/verify.js.map +1 -1
- package/dist/core-TFSlUjV1.cjs +4312 -0
- package/dist/core-TFSlUjV1.cjs.map +1 -0
- package/dist/install/adapters.d.ts +6 -0
- package/dist/install/adapters.d.ts.map +1 -0
- package/dist/install/adapters.js +65 -0
- package/dist/install/adapters.js.map +1 -0
- package/dist/install/copy.d.ts +6 -0
- package/dist/install/copy.d.ts.map +1 -0
- package/dist/install/copy.js +71 -0
- package/dist/install/copy.js.map +1 -0
- package/dist/install/dashboard.d.ts +16 -0
- package/dist/install/dashboard.d.ts.map +1 -0
- package/dist/install/dashboard.js +273 -0
- package/dist/install/dashboard.js.map +1 -0
- package/dist/install/hooks.d.ts +31 -0
- package/dist/install/hooks.d.ts.map +1 -0
- package/dist/install/hooks.js +260 -0
- package/dist/install/hooks.js.map +1 -0
- package/dist/install/index.d.ts +2 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +535 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/manifest.d.ts +23 -0
- package/dist/install/manifest.d.ts.map +1 -0
- package/dist/install/manifest.js +129 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/install/patches.d.ts +10 -0
- package/dist/install/patches.d.ts.map +1 -0
- package/dist/install/patches.js +124 -0
- package/dist/install/patches.js.map +1 -0
- package/dist/install/shared.d.ts +56 -0
- package/dist/install/shared.d.ts.map +1 -0
- package/dist/install/shared.js +172 -0
- package/dist/install/shared.js.map +1 -0
- package/dist/install/uninstall.d.ts +5 -0
- package/dist/install/uninstall.d.ts.map +1 -0
- package/dist/install/uninstall.js +222 -0
- package/dist/install/uninstall.js.map +1 -0
- package/dist/install.cjs +793 -1648
- package/dist/install.cjs.map +1 -1
- package/dist/mcp-server.cjs +38 -14
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/skills-BOSxYUzf.cjs +6812 -0
- package/dist/skills-BOSxYUzf.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/adapters/codex.d.ts +0 -19
- package/dist/adapters/codex.d.ts.map +0 -1
- package/dist/adapters/codex.js +0 -94
- package/dist/adapters/codex.js.map +0 -1
- package/dist/adapters/gemini.d.ts +0 -19
- package/dist/adapters/gemini.d.ts.map +0 -1
- package/dist/adapters/gemini.js +0 -96
- package/dist/adapters/gemini.js.map +0 -1
- package/dist/adapters/opencode.d.ts +0 -17
- package/dist/adapters/opencode.d.ts.map +0 -1
- package/dist/adapters/opencode.js +0 -111
- package/dist/adapters/opencode.js.map +0 -1
- package/dist/adapters/transforms/content.d.ts +0 -39
- package/dist/adapters/transforms/content.d.ts.map +0 -1
- package/dist/adapters/transforms/content.js +0 -125
- package/dist/adapters/transforms/content.js.map +0 -1
- package/dist/adapters/transforms/frontmatter.d.ts +0 -42
- package/dist/adapters/transforms/frontmatter.d.ts.map +0 -1
- package/dist/adapters/transforms/frontmatter.js +0 -204
- package/dist/adapters/transforms/frontmatter.js.map +0 -1
- package/dist/adapters/transforms/tool-maps.d.ts +0 -20
- package/dist/adapters/transforms/tool-maps.d.ts.map +0 -1
- package/dist/adapters/transforms/tool-maps.js +0 -64
- package/dist/adapters/transforms/tool-maps.js.map +0 -1
- package/dist/assets/dashboard/client/assets/index-DzJChB-D.css +0 -32
- package/dist/install.d.ts +0 -2
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js +0 -1841
- 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
|
-
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
/**
|
|
4762
|
-
function
|
|
4763
|
-
|
|
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:
|
|
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
|
|
11808
|
-
if (!filePath)
|
|
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
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
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
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
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
|
|
11831
|
-
if (!filePath || !field || value === void 0)
|
|
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
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
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
|
-
|
|
11920
|
+
return cmdOk({
|
|
11852
11921
|
updated: true,
|
|
11853
11922
|
field,
|
|
11854
11923
|
value: parsedValue
|
|
11855
|
-
},
|
|
11924
|
+
}, "true");
|
|
11856
11925
|
}
|
|
11857
|
-
function cmdFrontmatterMerge(cwd, filePath, data
|
|
11858
|
-
if (!filePath || !data)
|
|
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
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11944
|
+
return cmdOk({
|
|
11880
11945
|
merged: true,
|
|
11881
11946
|
fields: Object.keys(mergeData)
|
|
11882
|
-
},
|
|
11947
|
+
}, "true");
|
|
11883
11948
|
}
|
|
11884
|
-
function cmdFrontmatterValidate(cwd, filePath, schemaName
|
|
11885
|
-
if (!filePath || !schemaName)
|
|
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)
|
|
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
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
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
|
-
|
|
11961
|
+
return cmdOk({
|
|
11900
11962
|
valid: missing.length === 0,
|
|
11901
11963
|
missing,
|
|
11902
11964
|
present,
|
|
11903
11965
|
schema: schemaName
|
|
11904
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
12010
|
+
return cmdOk({
|
|
11952
12011
|
created: true,
|
|
11953
12012
|
path: ".planning/config.json"
|
|
11954
|
-
}, raw
|
|
12013
|
+
}, raw ? "created" : void 0);
|
|
11955
12014
|
} catch (err) {
|
|
11956
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
12041
|
+
return cmdOk({
|
|
11983
12042
|
updated: true,
|
|
11984
12043
|
key: keyPath,
|
|
11985
12044
|
value: parsedValue
|
|
11986
|
-
}, raw
|
|
12045
|
+
}, raw ? `${keyPath}=${parsedValue}` : void 0);
|
|
11987
12046
|
} catch (err) {
|
|
11988
|
-
|
|
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)
|
|
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
|
|
12056
|
+
else return cmdErr("No config.json found at " + configPath);
|
|
11998
12057
|
} catch (err) {
|
|
11999
|
-
|
|
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")
|
|
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)
|
|
12009
|
-
|
|
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
|
|
12028
|
-
const
|
|
12029
|
-
|
|
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
|
|
12034
|
-
|
|
12035
|
-
return
|
|
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
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12107
|
-
|
|
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
|
|
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
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
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
|
|
12137
|
-
|
|
12138
|
-
|
|
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
|
-
|
|
12145
|
-
} catch {
|
|
12146
|
-
|
|
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)
|
|
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
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12254
|
+
return cmdOk({
|
|
12203
12255
|
advanced: true,
|
|
12204
12256
|
previous_plan: currentPlan,
|
|
12205
12257
|
current_plan: newPlan,
|
|
12206
12258
|
total_plans: totalPlans
|
|
12207
|
-
}, raw
|
|
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
|
-
|
|
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
|
-
|
|
12277
|
+
return cmdOk({
|
|
12232
12278
|
recorded: true,
|
|
12233
12279
|
phase,
|
|
12234
12280
|
plan,
|
|
12235
12281
|
duration
|
|
12236
|
-
}, raw
|
|
12237
|
-
} else
|
|
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
|
|
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
|
|
12265
|
-
if (
|
|
12266
|
-
|
|
12267
|
-
|
|
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
|
|
12275
|
-
} else
|
|
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
|
|
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
|
-
|
|
12332
|
+
return cmdOk({
|
|
12294
12333
|
added: false,
|
|
12295
12334
|
reason: thrown.message
|
|
12296
|
-
}, raw
|
|
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
|
-
|
|
12343
|
+
return cmdOk({
|
|
12309
12344
|
added: true,
|
|
12310
12345
|
decision: entry
|
|
12311
|
-
}, raw
|
|
12312
|
-
} else
|
|
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
|
|
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
|
-
|
|
12360
|
+
return cmdOk({
|
|
12329
12361
|
added: false,
|
|
12330
12362
|
reason: thrown.message
|
|
12331
|
-
}, raw
|
|
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
|
-
|
|
12369
|
+
return cmdOk({
|
|
12342
12370
|
added: true,
|
|
12343
12371
|
blocker: blockerText
|
|
12344
|
-
}, raw
|
|
12345
|
-
} else
|
|
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
|
|
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
|
-
|
|
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 = /(
|
|
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 (
|
|
12387
|
+
if (!/^\s*[-*]\s+/.test(line)) return true;
|
|
12366
12388
|
return !line.toLowerCase().includes(text.toLowerCase());
|
|
12367
12389
|
}).join("\n");
|
|
12368
|
-
if (!newBody.trim() ||
|
|
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
|
-
|
|
12393
|
+
return cmdOk({
|
|
12372
12394
|
resolved: true,
|
|
12373
12395
|
blocker: text
|
|
12374
|
-
}, raw
|
|
12375
|
-
} else
|
|
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
|
|
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
|
-
|
|
12435
|
+
return cmdOk({
|
|
12417
12436
|
recorded: true,
|
|
12418
12437
|
updated
|
|
12419
|
-
}, raw
|
|
12420
|
-
} else
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
12467
|
-
for (const item of items) blockers.push(item.replace(
|
|
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(
|
|
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
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
12518
|
+
function cmdRoadmapGetPhase(cwd, phaseNum) {
|
|
12509
12519
|
const rmPath = roadmapPath(cwd);
|
|
12510
|
-
if (!node_fs.default.existsSync(rmPath)) {
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
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
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
},
|
|
12559
|
+
}, section);
|
|
12557
12560
|
} catch (e) {
|
|
12558
|
-
|
|
12561
|
+
return cmdErr("Failed to read ROADMAP.md: " + e.message);
|
|
12559
12562
|
}
|
|
12560
12563
|
}
|
|
12561
|
-
function cmdRoadmapAnalyze(cwd
|
|
12562
|
-
const
|
|
12563
|
-
if (!
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
12607
|
+
const dirMatch = allDirs.find((d) => d.startsWith(p.normalized + "-") || d === p.normalized);
|
|
12596
12608
|
if (dirMatch) {
|
|
12597
|
-
const phaseFiles = node_fs.default.
|
|
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
|
|
12613
|
-
const checkboxMatch = content.match(checkboxPattern);
|
|
12624
|
+
const checkboxMatch = content.match(p.checkboxPattern);
|
|
12614
12625
|
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === "x" : false;
|
|
12615
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
12668
|
+
});
|
|
12658
12669
|
}
|
|
12659
|
-
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum
|
|
12660
|
-
if (!phaseNum)
|
|
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)
|
|
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
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
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
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
|
12718
|
-
if (!reqIdsRaw || reqIdsRaw.length === 0)
|
|
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)
|
|
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
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
12750
|
+
}, `${updated.length}/${reqIds.length} requirements marked complete`);
|
|
12754
12751
|
}
|
|
12755
|
-
function cmdMilestoneComplete(cwd, version, options
|
|
12756
|
-
if (!version)
|
|
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
|
-
|
|
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
|
-
}
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13313
|
+
return cmdOk({
|
|
13317
13314
|
count,
|
|
13318
13315
|
todos
|
|
13319
|
-
}, raw
|
|
13316
|
+
}, raw ? count.toString() : void 0);
|
|
13320
13317
|
}
|
|
13321
13318
|
function cmdVerifyPathExists(cwd, targetPath, raw) {
|
|
13322
|
-
if (!targetPath)
|
|
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
|
-
|
|
13323
|
+
return cmdOk({
|
|
13327
13324
|
exists: true,
|
|
13328
13325
|
type: stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other"
|
|
13329
|
-
}, raw
|
|
13330
|
-
} catch {
|
|
13331
|
-
|
|
13326
|
+
}, raw ? "true" : void 0);
|
|
13327
|
+
} catch (e) {
|
|
13328
|
+
rethrowCliSignals(e);
|
|
13329
|
+
return cmdOk({
|
|
13332
13330
|
exists: false,
|
|
13333
13331
|
type: null
|
|
13334
|
-
}, raw
|
|
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
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
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
|
-
|
|
13404
|
+
return cmdOk(outputDigest);
|
|
13410
13405
|
} catch (e) {
|
|
13411
|
-
|
|
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)
|
|
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
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
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
|
-
|
|
13421
|
+
return cmdOk({
|
|
13429
13422
|
model,
|
|
13430
13423
|
profile
|
|
13431
|
-
}, raw
|
|
13424
|
+
}, raw ? model : void 0);
|
|
13432
13425
|
}
|
|
13433
13426
|
async function cmdCommit(cwd, message, files, raw, amend) {
|
|
13434
|
-
if (!message && !amend)
|
|
13435
|
-
if (!loadConfig(cwd).commit_docs) {
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
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
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
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
|
|
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
|
-
|
|
13468
|
+
return cmdOk({
|
|
13486
13469
|
committed: true,
|
|
13487
13470
|
hash,
|
|
13488
13471
|
reason: "committed"
|
|
13489
|
-
}, raw
|
|
13472
|
+
}, raw ? hash || "committed" : void 0);
|
|
13490
13473
|
}
|
|
13491
13474
|
function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
13492
|
-
if (!summaryPath)
|
|
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
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
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
|
-
|
|
13530
|
-
return;
|
|
13509
|
+
return cmdOk(filtered);
|
|
13531
13510
|
}
|
|
13532
|
-
|
|
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
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
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
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
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
|
-
|
|
13546
|
+
return cmdOk({
|
|
13577
13547
|
available: true,
|
|
13578
13548
|
query,
|
|
13579
13549
|
count: results.length,
|
|
13580
13550
|
results
|
|
13581
|
-
}, raw
|
|
13551
|
+
}, raw ? results.map((r) => `${r.title}\n${r.url}\n${r.description}`).join("\n\n") : void 0);
|
|
13582
13552
|
} catch (err) {
|
|
13583
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13610
|
+
return cmdOk({
|
|
13640
13611
|
bar: text,
|
|
13641
13612
|
percent,
|
|
13642
13613
|
completed: totalSummaries,
|
|
13643
13614
|
total: totalPlans
|
|
13644
|
-
}, raw
|
|
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
|
-
|
|
13635
|
+
return cmdOk({
|
|
13665
13636
|
rendered,
|
|
13666
13637
|
done: doneCount,
|
|
13667
13638
|
in_progress: inProgressCount,
|
|
13668
13639
|
total: totalCount,
|
|
13669
13640
|
percent
|
|
13670
|
-
}, raw
|
|
13671
|
-
} else
|
|
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
|
-
}
|
|
13649
|
+
});
|
|
13679
13650
|
}
|
|
13680
13651
|
function cmdTodoComplete(cwd, filename, raw) {
|
|
13681
|
-
if (!filename)
|
|
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))
|
|
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
|
-
|
|
13663
|
+
return cmdOk({
|
|
13693
13664
|
completed: true,
|
|
13694
13665
|
file: filename,
|
|
13695
13666
|
date: today
|
|
13696
|
-
}, raw
|
|
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")
|
|
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)
|
|
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
|
-
|
|
13698
|
+
return cmdOk({
|
|
13728
13699
|
created: true,
|
|
13729
13700
|
directory: `.planning/phases/${dirName}`,
|
|
13730
13701
|
path: dirPath
|
|
13731
|
-
}, raw
|
|
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
|
-
|
|
13713
|
+
return cmdOk({
|
|
13749
13714
|
created: true,
|
|
13750
13715
|
path: relPath
|
|
13751
|
-
}, raw
|
|
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
|
|
13762
|
-
if (!summaryPath)
|
|
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
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
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
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
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
|
-
|
|
13793
|
+
return cmdOk({
|
|
13832
13794
|
passed,
|
|
13833
13795
|
checks,
|
|
13834
13796
|
errors
|
|
13835
|
-
},
|
|
13797
|
+
}, passed ? "passed" : "failed");
|
|
13836
13798
|
}
|
|
13837
|
-
function cmdVerifyPlanStructure(cwd, filePath
|
|
13838
|
-
if (!filePath)
|
|
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
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
13853
|
+
}, errors.length === 0 ? "valid" : "invalid");
|
|
13895
13854
|
}
|
|
13896
|
-
function cmdVerifyPhaseCompleteness(cwd, phase
|
|
13897
|
-
if (!phase)
|
|
13855
|
+
function cmdVerifyPhaseCompleteness(cwd, phase) {
|
|
13856
|
+
if (!phase) return cmdErr("phase required");
|
|
13898
13857
|
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
13899
|
-
if (!phaseInfo) {
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
13888
|
+
}, errors.length === 0 ? "complete" : "incomplete");
|
|
13934
13889
|
}
|
|
13935
|
-
function cmdVerifyReferences(cwd, filePath
|
|
13936
|
-
if (!filePath)
|
|
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
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
13920
|
+
}, missing.length === 0 ? "valid" : "invalid");
|
|
13969
13921
|
}
|
|
13970
|
-
async function cmdVerifyCommits(cwd, hashes
|
|
13971
|
-
if (!hashes || hashes.length === 0)
|
|
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
|
-
|
|
13935
|
+
return cmdOk({
|
|
13984
13936
|
all_valid: invalid.length === 0,
|
|
13985
13937
|
valid,
|
|
13986
13938
|
invalid,
|
|
13987
13939
|
total: hashes.length
|
|
13988
|
-
},
|
|
13940
|
+
}, invalid.length === 0 ? "valid" : "invalid");
|
|
13989
13941
|
}
|
|
13990
|
-
function cmdVerifyArtifacts(cwd, planFilePath
|
|
13991
|
-
if (!planFilePath)
|
|
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
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
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
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
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
|
-
|
|
13982
|
+
return cmdOk({
|
|
14037
13983
|
all_passed: passed === results.length,
|
|
14038
13984
|
passed,
|
|
14039
13985
|
total: results.length,
|
|
14040
13986
|
artifacts: results
|
|
14041
|
-
},
|
|
13987
|
+
}, passed === results.length ? "valid" : "invalid");
|
|
14042
13988
|
}
|
|
14043
|
-
function cmdVerifyKeyLinks(cwd, planFilePath
|
|
14044
|
-
if (!planFilePath)
|
|
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
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
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
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
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
|
-
|
|
14036
|
+
return cmdOk({
|
|
14097
14037
|
all_verified: verified === results.length,
|
|
14098
14038
|
verified,
|
|
14099
14039
|
total: results.length,
|
|
14100
14040
|
links: results
|
|
14101
|
-
},
|
|
14041
|
+
}, verified === results.length ? "valid" : "invalid");
|
|
14102
14042
|
}
|
|
14103
|
-
function cmdValidateConsistency(cwd
|
|
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
|
-
|
|
14050
|
+
return cmdOk({
|
|
14111
14051
|
passed: false,
|
|
14112
14052
|
errors,
|
|
14113
14053
|
warnings
|
|
14114
|
-
},
|
|
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
|
-
|
|
14106
|
+
return cmdOk({
|
|
14168
14107
|
passed,
|
|
14169
14108
|
errors,
|
|
14170
14109
|
warnings,
|
|
14171
14110
|
warning_count: warnings.length
|
|
14172
|
-
},
|
|
14111
|
+
}, passed ? "passed" : "failed");
|
|
14173
14112
|
}
|
|
14174
|
-
function cmdValidateHealth(cwd, options
|
|
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
|
-
|
|
14137
|
+
return cmdOk({
|
|
14199
14138
|
status: "broken",
|
|
14200
14139
|
errors,
|
|
14201
14140
|
warnings,
|
|
14202
14141
|
info,
|
|
14203
14142
|
repairable_count: 0
|
|
14204
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
-
|
|
14547
|
-
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
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 =
|
|
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
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
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.
|
|
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
|
-
|
|
14587
|
-
}
|
|
14588
|
-
|
|
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
|
-
},
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
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
|
-
|
|
14544
|
+
return cmdErr("Failed to list phases: " + e.message);
|
|
14601
14545
|
}
|
|
14602
14546
|
}
|
|
14603
|
-
function cmdPhaseNextDecimal(cwd, basePhase
|
|
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
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
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
|
-
|
|
14574
|
+
return cmdOk({
|
|
14634
14575
|
found: baseExists,
|
|
14635
14576
|
base_phase: normalized,
|
|
14636
14577
|
next: nextDecimal,
|
|
14637
14578
|
existing: existingDecimals
|
|
14638
|
-
},
|
|
14579
|
+
}, nextDecimal);
|
|
14639
14580
|
} catch (e) {
|
|
14640
|
-
|
|
14581
|
+
return cmdErr("Failed to calculate next decimal phase: " + e.message);
|
|
14641
14582
|
}
|
|
14642
14583
|
}
|
|
14643
|
-
function cmdFindPhase(cwd, phase
|
|
14644
|
-
if (!phase)
|
|
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
|
-
|
|
14677
|
-
} catch {
|
|
14678
|
-
|
|
14614
|
+
return cmdOk(result, result.directory);
|
|
14615
|
+
} catch (e) {
|
|
14616
|
+
return cmdOk(notFound, "");
|
|
14679
14617
|
}
|
|
14680
14618
|
}
|
|
14681
|
-
function cmdPhasePlanIndex(cwd, phase
|
|
14682
|
-
if (!phase)
|
|
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
|
-
|
|
14674
|
+
return cmdOk({
|
|
14740
14675
|
phase: normalized,
|
|
14741
14676
|
plans,
|
|
14742
14677
|
waves,
|
|
14743
14678
|
incomplete,
|
|
14744
14679
|
has_checkpoints: hasCheckpoints
|
|
14745
|
-
}
|
|
14680
|
+
});
|
|
14746
14681
|
}
|
|
14747
|
-
function cmdPhaseAdd(cwd, description
|
|
14748
|
-
if (!description)
|
|
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
|
-
|
|
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
|
-
},
|
|
14692
|
+
}, result.padded);
|
|
14758
14693
|
} catch (e) {
|
|
14759
|
-
|
|
14694
|
+
return cmdErr(e.message);
|
|
14760
14695
|
}
|
|
14761
14696
|
}
|
|
14762
|
-
function cmdPhaseInsert(cwd, afterPhase, description
|
|
14763
|
-
if (!afterPhase || !description)
|
|
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
|
-
|
|
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
|
-
},
|
|
14707
|
+
}, result.phase_number);
|
|
14773
14708
|
} catch (e) {
|
|
14774
|
-
|
|
14709
|
+
return cmdErr(e.message);
|
|
14775
14710
|
}
|
|
14776
14711
|
}
|
|
14777
|
-
function cmdPhaseRemove(cwd, targetPhase, options
|
|
14778
|
-
if (!targetPhase)
|
|
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))
|
|
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)
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
}
|
|
14880
|
+
});
|
|
14940
14881
|
}
|
|
14941
|
-
function cmdPhaseComplete(cwd, phaseNum
|
|
14942
|
-
if (!phaseNum)
|
|
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
|
-
|
|
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
|
-
}
|
|
14896
|
+
});
|
|
14956
14897
|
} catch (e) {
|
|
14957
|
-
|
|
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
|
|
14969
|
-
if (!planPath)
|
|
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
|
-
|
|
14930
|
+
return cmdOk({
|
|
14990
14931
|
template,
|
|
14991
14932
|
type,
|
|
14992
14933
|
taskCount,
|
|
14993
14934
|
fileCount,
|
|
14994
14935
|
hasDecisions
|
|
14995
|
-
},
|
|
14936
|
+
}, template);
|
|
14996
14937
|
} catch (thrown) {
|
|
14997
|
-
|
|
14938
|
+
return cmdOk({
|
|
14998
14939
|
template: "templates/summary-standard.md",
|
|
14999
14940
|
type: "standard",
|
|
15000
14941
|
error: thrown.message
|
|
15001
|
-
},
|
|
14942
|
+
}, "templates/summary-standard.md");
|
|
15002
14943
|
}
|
|
15003
14944
|
}
|
|
15004
|
-
function cmdTemplateFill(cwd, templateType, options
|
|
15005
|
-
if (!templateType)
|
|
15006
|
-
if (!options.phase)
|
|
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
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
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
|
-
|
|
15168
|
-
|
|
15169
|
-
|
|
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
|
-
|
|
15108
|
+
return cmdOk({
|
|
15176
15109
|
created: true,
|
|
15177
15110
|
path: relPath,
|
|
15178
15111
|
template: templateType
|
|
15179
|
-
},
|
|
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
|
-
|
|
15213
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
15827
|
+
});
|
|
15249
15828
|
}
|
|
15250
|
-
function cmdInitPlanPhase(cwd, phase
|
|
15251
|
-
if (!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
|
-
|
|
15865
|
+
return cmdOk(result);
|
|
15288
15866
|
}
|
|
15289
|
-
function cmdInitNewProject(cwd
|
|
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
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
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
|
-
}
|
|
15889
|
+
});
|
|
15326
15890
|
}
|
|
15327
|
-
function cmdInitNewMilestone(cwd
|
|
15891
|
+
function cmdInitNewMilestone(cwd) {
|
|
15328
15892
|
const config = loadConfig(cwd);
|
|
15329
15893
|
const milestone = getMilestoneInfo(cwd);
|
|
15330
|
-
|
|
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
|
-
}
|
|
15908
|
+
});
|
|
15345
15909
|
}
|
|
15346
|
-
function cmdInitQuick(cwd, description
|
|
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
|
-
|
|
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
|
-
}
|
|
15937
|
+
});
|
|
15374
15938
|
}
|
|
15375
|
-
function cmdInitResume(cwd
|
|
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
|
-
|
|
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
|
-
}
|
|
15958
|
+
});
|
|
15395
15959
|
}
|
|
15396
|
-
function cmdInitVerifyWork(cwd, phase
|
|
15397
|
-
if (!phase)
|
|
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
|
-
|
|
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
|
-
}
|
|
15973
|
+
});
|
|
15410
15974
|
}
|
|
15411
|
-
function cmdInitPhaseOp(cwd, phase
|
|
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
|
-
|
|
16024
|
+
return cmdOk(result);
|
|
15461
16025
|
}
|
|
15462
|
-
function cmdInitTodos(cwd, area
|
|
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
|
-
|
|
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
|
-
}
|
|
16067
|
+
});
|
|
15504
16068
|
}
|
|
15505
|
-
function cmdInitMilestoneOp(cwd
|
|
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
|
-
|
|
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
|
-
}
|
|
16108
|
+
});
|
|
15545
16109
|
}
|
|
15546
|
-
function cmdInitMapCodebase(cwd
|
|
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
|
-
|
|
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
|
-
}
|
|
16129
|
+
});
|
|
15566
16130
|
}
|
|
15567
|
-
function cmdInitExisting(cwd
|
|
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
|
-
|
|
15573
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
16165
|
+
});
|
|
15616
16166
|
}
|
|
15617
|
-
function cmdInitProgress(cwd
|
|
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
|
|
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 >=
|
|
15636
|
-
const
|
|
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:
|
|
16191
|
+
plan_count: plansList.length,
|
|
15642
16192
|
summary_count: summaries.length,
|
|
15643
16193
|
has_research: hasResearch
|
|
15644
16194
|
};
|
|
15645
|
-
phases.push(
|
|
15646
|
-
if (!currentPhase && (status === "in_progress" || status === "researched")) currentPhase =
|
|
15647
|
-
if (!nextPhase && status === "pending") nextPhase =
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
15817
|
-
"analyze": () => cmdRoadmapAnalyze(cwd
|
|
15818
|
-
"update-plan-progress": () => cmdRoadmapUpdatePlanProgress(cwd, args[2]
|
|
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]
|
|
15827
|
-
"add": () => cmdPhaseAdd(cwd, args.slice(2).join(" ")
|
|
15828
|
-
"insert": () => cmdPhaseInsert(cwd, args[2], args.slice(3).join(" ")
|
|
15829
|
-
"remove": () => cmdPhaseRemove(cwd, args[2], { force: hasFlag(args, "force") }
|
|
15830
|
-
"complete": () => cmdPhaseComplete(cwd, args[2]
|
|
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]
|
|
15866
|
-
"plan-phase": () => cmdInitPlanPhase(cwd, args[2]
|
|
15867
|
-
"new-project": () => cmdInitNewProject(cwd
|
|
15868
|
-
"new-milestone": () => cmdInitNewMilestone(cwd
|
|
15869
|
-
"quick": () => cmdInitQuick(cwd, args.slice(2).join(" ")
|
|
15870
|
-
"resume": () => cmdInitResume(cwd
|
|
15871
|
-
"verify-work": () => cmdInitVerifyWork(cwd, args[2]
|
|
15872
|
-
"phase-op": () => cmdInitPhaseOp(cwd, args[2]
|
|
15873
|
-
"todos": () => cmdInitTodos(cwd, args[2]
|
|
15874
|
-
"milestone-op": () => cmdInitMilestoneOp(cwd
|
|
15875
|
-
"map-codebase": () => cmdInitMapCodebase(cwd
|
|
15876
|
-
"init-existing": () => cmdInitExisting(cwd
|
|
15877
|
-
"progress": () => cmdInitProgress(cwd
|
|
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
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
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
|
|
15988
|
-
console.log(`Dashboard found on port ${port} —
|
|
15989
|
-
|
|
15990
|
-
console.log(
|
|
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
|
-
|
|
16015
|
-
|
|
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
|
-
|
|
16033
|
-
|
|
16034
|
-
if (
|
|
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
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
|
|
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
|
-
|
|
16067
|
-
|
|
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 ${
|
|
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
|