maxsimcli 3.10.3 → 3.12.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/assets/CHANGELOG.md +26 -0
- package/dist/assets/dashboard/client/assets/index-CxFKStBk.css +32 -0
- package/dist/assets/dashboard/client/assets/{index-CZ8WC97G.js → index-wtQDvXzr.js} +64 -64
- package/dist/assets/dashboard/client/index.html +2 -2
- package/dist/assets/dashboard/server.js +5 -1
- package/dist/assets/templates/agents/AGENTS.md +82 -0
- package/dist/assets/templates/commands/maxsim/settings.md +1 -1
- 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/simplify/SKILL.md +137 -0
- package/dist/assets/templates/skills/using-maxsim/SKILL.md +115 -0
- package/dist/assets/templates/templates/config.json +1 -1
- package/dist/assets/templates/workflows/add-tests.md +3 -3
- package/dist/assets/templates/workflows/complete-milestone.md +1 -1
- package/dist/assets/templates/workflows/execute-phase.md +4 -14
- package/dist/assets/templates/workflows/execute-plan.md +10 -0
- package/dist/assets/templates/workflows/init-existing.md +7 -3
- package/dist/assets/templates/workflows/new-milestone.md +4 -0
- package/dist/assets/templates/workflows/new-project.md +6 -2
- package/dist/assets/templates/workflows/plan-phase.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 +818 -599
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +78 -204
- package/dist/cli.js.map +1 -1
- package/dist/core/commands.d.ts +7 -0
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +40 -35
- package/dist/core/commands.js.map +1 -1
- package/dist/core/core.d.ts +39 -1
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.js +122 -47
- 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 +243 -0
- package/dist/core/dashboard-launcher.js.map +1 -0
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +20 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +2 -3
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +33 -52
- package/dist/core/init.js.map +1 -1
- package/dist/core/milestone.d.ts.map +1 -1
- package/dist/core/milestone.js +15 -20
- package/dist/core/milestone.js.map +1 -1
- package/dist/core/phase.d.ts +33 -0
- package/dist/core/phase.d.ts.map +1 -1
- package/dist/core/phase.js +282 -225
- package/dist/core/phase.js.map +1 -1
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/roadmap.js +17 -18
- package/dist/core/roadmap.js.map +1 -1
- package/dist/core/state.d.ts +5 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +51 -42
- package/dist/core/state.js.map +1 -1
- package/dist/core/template.d.ts.map +1 -1
- package/dist/core/template.js +1 -1
- package/dist/core/template.js.map +1 -1
- package/dist/core/types.d.ts +4 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -2
- package/dist/core/types.js.map +1 -1
- package/dist/core/verify.d.ts.map +1 -1
- package/dist/core/verify.js +61 -80
- package/dist/core/verify.js.map +1 -1
- package/dist/install/adapters.d.ts +15 -0
- package/dist/install/adapters.d.ts.map +1 -0
- package/dist/install/adapters.js +203 -0
- package/dist/install/adapters.js.map +1 -0
- package/dist/install/copy.d.ts +15 -0
- package/dist/install/copy.d.ts.map +1 -0
- package/dist/install/copy.js +191 -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 +32 -0
- package/dist/install/hooks.d.ts.map +1 -0
- package/dist/install/hooks.js +285 -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 +598 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/manifest.d.ts +20 -0
- package/dist/install/manifest.d.ts.map +1 -0
- package/dist/install/manifest.js +135 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/install/patches.d.ts +11 -0
- package/dist/install/patches.d.ts.map +1 -0
- package/dist/install/patches.js +136 -0
- package/dist/install/patches.js.map +1 -0
- package/dist/install/shared.d.ts +50 -0
- package/dist/install/shared.d.ts.map +1 -0
- package/dist/install/shared.js +142 -0
- package/dist/install/shared.js.map +1 -0
- package/dist/install/uninstall.d.ts +6 -0
- package/dist/install/uninstall.d.ts.map +1 -0
- package/dist/install/uninstall.js +280 -0
- package/dist/install/uninstall.js.map +1 -0
- package/dist/install.cjs +782 -705
- package/dist/install.cjs.map +1 -1
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +21 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/phase-tools.d.ts +13 -0
- package/dist/mcp/phase-tools.d.ts.map +1 -0
- package/dist/mcp/phase-tools.js +164 -0
- package/dist/mcp/phase-tools.js.map +1 -0
- package/dist/mcp/state-tools.d.ts +13 -0
- package/dist/mcp/state-tools.d.ts.map +1 -0
- package/dist/mcp/state-tools.js +185 -0
- package/dist/mcp/state-tools.js.map +1 -0
- package/dist/mcp/todo-tools.d.ts +13 -0
- package/dist/mcp/todo-tools.d.ts.map +1 -0
- package/dist/mcp/todo-tools.js +143 -0
- package/dist/mcp/todo-tools.js.map +1 -0
- package/dist/mcp/utils.d.ts +27 -0
- package/dist/mcp/utils.d.ts.map +1 -0
- package/dist/mcp/utils.js +82 -0
- package/dist/mcp/utils.js.map +1 -0
- package/dist/mcp-server.cjs +11806 -0
- package/dist/mcp-server.cjs.map +1 -0
- package/dist/mcp-server.d.cts +2 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +31 -0
- package/dist/mcp-server.js.map +1 -0
- package/package.json +5 -3
- 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 -1804
- 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,6 +41,7 @@ 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
47
|
const PLANNING_CONFIG_DEFAULTS = {
|
|
@@ -53,9 +53,8 @@ const PLANNING_CONFIG_DEFAULTS = {
|
|
|
53
53
|
milestone_branch_template: "maxsim/{milestone}-{slug}",
|
|
54
54
|
workflow: {
|
|
55
55
|
research: true,
|
|
56
|
-
|
|
57
|
-
verifier: true
|
|
58
|
-
nyquist_validation: false
|
|
56
|
+
plan_checker: true,
|
|
57
|
+
verifier: true
|
|
59
58
|
},
|
|
60
59
|
parallelization: true,
|
|
61
60
|
brave_search: false
|
|
@@ -594,7 +593,7 @@ var require_has_flag = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
594
593
|
//#endregion
|
|
595
594
|
//#region ../../node_modules/supports-color/index.js
|
|
596
595
|
var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
597
|
-
const os$
|
|
596
|
+
const os$5 = require("os");
|
|
598
597
|
const tty$2 = require("tty");
|
|
599
598
|
const hasFlag = require_has_flag();
|
|
600
599
|
const { env } = process;
|
|
@@ -621,7 +620,7 @@ var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
621
620
|
const min = forceColor || 0;
|
|
622
621
|
if (env.TERM === "dumb") return min;
|
|
623
622
|
if (process.platform === "win32") {
|
|
624
|
-
const osRelease = os$
|
|
623
|
+
const osRelease = os$5.release().split(".");
|
|
625
624
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
626
625
|
return 1;
|
|
627
626
|
}
|
|
@@ -4711,21 +4710,86 @@ const MODEL_PROFILES = {
|
|
|
4711
4710
|
tokenburner: "opus"
|
|
4712
4711
|
}
|
|
4713
4712
|
};
|
|
4713
|
+
/** Thrown by output() to signal successful command completion. */
|
|
4714
|
+
var CliOutput = class {
|
|
4715
|
+
result;
|
|
4716
|
+
raw;
|
|
4717
|
+
rawValue;
|
|
4718
|
+
constructor(result, raw, rawValue) {
|
|
4719
|
+
this.result = result;
|
|
4720
|
+
this.raw = raw ?? false;
|
|
4721
|
+
this.rawValue = rawValue;
|
|
4722
|
+
}
|
|
4723
|
+
};
|
|
4724
|
+
/** Thrown by error() to signal a command error. */
|
|
4725
|
+
var CliError = class {
|
|
4726
|
+
message;
|
|
4727
|
+
constructor(message) {
|
|
4728
|
+
this.message = message;
|
|
4729
|
+
}
|
|
4730
|
+
};
|
|
4714
4731
|
function output(result, raw, rawValue) {
|
|
4715
|
-
|
|
4732
|
+
throw new CliOutput(result, raw, rawValue);
|
|
4733
|
+
}
|
|
4734
|
+
function error(message) {
|
|
4735
|
+
throw new CliError(message);
|
|
4736
|
+
}
|
|
4737
|
+
/** Re-throw CliOutput/CliError signals so catch blocks don't intercept them */
|
|
4738
|
+
function rethrowCliSignals(e) {
|
|
4739
|
+
if (e instanceof CliOutput || e instanceof CliError) throw e;
|
|
4740
|
+
}
|
|
4741
|
+
/**
|
|
4742
|
+
* Handle a CliOutput by writing to stdout. Extracted so cli.ts can use it.
|
|
4743
|
+
*/
|
|
4744
|
+
function writeOutput(out) {
|
|
4745
|
+
if (out.raw && out.rawValue !== void 0) process.stdout.write(String(out.rawValue));
|
|
4716
4746
|
else {
|
|
4717
|
-
const json = JSON.stringify(result, null, 2);
|
|
4747
|
+
const json = JSON.stringify(out.result, null, 2);
|
|
4718
4748
|
if (json.length > 5e4) {
|
|
4719
4749
|
const tmpPath = node_path.default.join(node_os.default.tmpdir(), `maxsim-${Date.now()}.json`);
|
|
4720
4750
|
node_fs.default.writeFileSync(tmpPath, json, "utf-8");
|
|
4721
4751
|
process.stdout.write("@file:" + tmpPath);
|
|
4722
4752
|
} else process.stdout.write(json);
|
|
4723
4753
|
}
|
|
4724
|
-
process.exit(0);
|
|
4725
4754
|
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4755
|
+
/** Today's date as YYYY-MM-DD. */
|
|
4756
|
+
function todayISO() {
|
|
4757
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4758
|
+
}
|
|
4759
|
+
/** Canonical .planning/ sub-paths. */
|
|
4760
|
+
function planningPath(cwd, ...segments) {
|
|
4761
|
+
return node_path.default.join(cwd, ".planning", ...segments);
|
|
4762
|
+
}
|
|
4763
|
+
function statePath(cwd) {
|
|
4764
|
+
return planningPath(cwd, "STATE.md");
|
|
4765
|
+
}
|
|
4766
|
+
function roadmapPath(cwd) {
|
|
4767
|
+
return planningPath(cwd, "ROADMAP.md");
|
|
4768
|
+
}
|
|
4769
|
+
function configPath(cwd) {
|
|
4770
|
+
return planningPath(cwd, "config.json");
|
|
4771
|
+
}
|
|
4772
|
+
function phasesPath(cwd) {
|
|
4773
|
+
return planningPath(cwd, "phases");
|
|
4774
|
+
}
|
|
4775
|
+
/** Phase-file predicates. */
|
|
4776
|
+
const isPlanFile = (f) => f.endsWith("-PLAN.md") || f === "PLAN.md";
|
|
4777
|
+
const isSummaryFile = (f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md";
|
|
4778
|
+
/** Strip suffix to get plan/summary ID. */
|
|
4779
|
+
const planId = (f) => f.replace("-PLAN.md", "").replace("PLAN.md", "");
|
|
4780
|
+
const summaryId = (f) => f.replace("-SUMMARY.md", "").replace("SUMMARY.md", "");
|
|
4781
|
+
/** List subdirectory names, optionally sorted by phase number. */
|
|
4782
|
+
function listSubDirs(dir, sortByPhase = false) {
|
|
4783
|
+
const dirs = node_fs.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
4784
|
+
return sortByPhase ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
|
|
4785
|
+
}
|
|
4786
|
+
/** Log only when MAXSIM_DEBUG is set. */
|
|
4787
|
+
function debugLog(e) {
|
|
4788
|
+
if (process.env.MAXSIM_DEBUG) console.error(e);
|
|
4789
|
+
}
|
|
4790
|
+
/** Escape a phase number for use in regex. */
|
|
4791
|
+
function escapePhaseNum(phase) {
|
|
4792
|
+
return String(phase).replace(/\./g, "\\.");
|
|
4729
4793
|
}
|
|
4730
4794
|
function safeReadFile(filePath) {
|
|
4731
4795
|
try {
|
|
@@ -4734,8 +4798,10 @@ function safeReadFile(filePath) {
|
|
|
4734
4798
|
return null;
|
|
4735
4799
|
}
|
|
4736
4800
|
}
|
|
4801
|
+
let _configCache = null;
|
|
4737
4802
|
function loadConfig(cwd) {
|
|
4738
|
-
|
|
4803
|
+
if (_configCache && _configCache.cwd === cwd) return _configCache.config;
|
|
4804
|
+
const cfgPath = configPath(cwd);
|
|
4739
4805
|
const defaults = {
|
|
4740
4806
|
model_profile: "balanced",
|
|
4741
4807
|
commit_docs: true,
|
|
@@ -4750,7 +4816,7 @@ function loadConfig(cwd) {
|
|
|
4750
4816
|
brave_search: false
|
|
4751
4817
|
};
|
|
4752
4818
|
try {
|
|
4753
|
-
const raw = node_fs.default.readFileSync(
|
|
4819
|
+
const raw = node_fs.default.readFileSync(cfgPath, "utf-8");
|
|
4754
4820
|
const parsed = JSON.parse(raw);
|
|
4755
4821
|
const get = (key, nested) => {
|
|
4756
4822
|
if (parsed[key] !== void 0) return parsed[key];
|
|
@@ -4765,7 +4831,7 @@ function loadConfig(cwd) {
|
|
|
4765
4831
|
if (typeof val === "object" && val !== null && "enabled" in val) return val.enabled;
|
|
4766
4832
|
return defaults.parallelization;
|
|
4767
4833
|
})();
|
|
4768
|
-
|
|
4834
|
+
const result = {
|
|
4769
4835
|
model_profile: get("model_profile") ?? defaults.model_profile,
|
|
4770
4836
|
commit_docs: get("commit_docs", {
|
|
4771
4837
|
section: "planning",
|
|
@@ -4792,6 +4858,9 @@ function loadConfig(cwd) {
|
|
|
4792
4858
|
field: "research"
|
|
4793
4859
|
}) ?? defaults.research,
|
|
4794
4860
|
plan_checker: get("plan_checker", {
|
|
4861
|
+
section: "workflow",
|
|
4862
|
+
field: "plan_checker"
|
|
4863
|
+
}) ?? get("plan_checker", {
|
|
4795
4864
|
section: "workflow",
|
|
4796
4865
|
field: "plan_check"
|
|
4797
4866
|
}) ?? defaults.plan_checker,
|
|
@@ -4803,7 +4872,16 @@ function loadConfig(cwd) {
|
|
|
4803
4872
|
brave_search: get("brave_search") ?? defaults.brave_search,
|
|
4804
4873
|
model_overrides: parsed["model_overrides"]
|
|
4805
4874
|
};
|
|
4875
|
+
_configCache = {
|
|
4876
|
+
cwd,
|
|
4877
|
+
config: result
|
|
4878
|
+
};
|
|
4879
|
+
return result;
|
|
4806
4880
|
} catch {
|
|
4881
|
+
_configCache = {
|
|
4882
|
+
cwd,
|
|
4883
|
+
config: defaults
|
|
4884
|
+
};
|
|
4807
4885
|
return defaults;
|
|
4808
4886
|
}
|
|
4809
4887
|
}
|
|
@@ -4873,23 +4951,20 @@ function getPhasePattern(escapedPhaseNum, flags = "gim") {
|
|
|
4873
4951
|
}
|
|
4874
4952
|
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
4875
4953
|
try {
|
|
4876
|
-
const match =
|
|
4954
|
+
const match = listSubDirs(baseDir, true).find((d) => d.startsWith(normalized));
|
|
4877
4955
|
if (!match) return null;
|
|
4878
4956
|
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
4879
4957
|
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
4880
4958
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
4881
4959
|
const phaseDir = node_path.default.join(baseDir, match);
|
|
4882
4960
|
const phaseFiles = node_fs.default.readdirSync(phaseDir);
|
|
4883
|
-
const plans = phaseFiles.filter(
|
|
4884
|
-
const summaries = phaseFiles.filter(
|
|
4961
|
+
const plans = phaseFiles.filter(isPlanFile).sort();
|
|
4962
|
+
const summaries = phaseFiles.filter(isSummaryFile).sort();
|
|
4885
4963
|
const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
4886
4964
|
const hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
4887
4965
|
const hasVerification = phaseFiles.some((f) => f.endsWith("-VERIFICATION.md") || f === "VERIFICATION.md");
|
|
4888
|
-
const completedPlanIds = new Set(summaries.map(
|
|
4889
|
-
const incompletePlans = plans.filter((p) =>
|
|
4890
|
-
const planId = p.replace("-PLAN.md", "").replace("PLAN.md", "");
|
|
4891
|
-
return !completedPlanIds.has(planId);
|
|
4892
|
-
});
|
|
4966
|
+
const completedPlanIds = new Set(summaries.map(summaryId));
|
|
4967
|
+
const incompletePlans = plans.filter((p) => !completedPlanIds.has(planId(p)));
|
|
4893
4968
|
return {
|
|
4894
4969
|
found: true,
|
|
4895
4970
|
directory: node_path.default.join(relBase, match),
|
|
@@ -4909,12 +4984,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
4909
4984
|
}
|
|
4910
4985
|
function findPhaseInternal(cwd, phase) {
|
|
4911
4986
|
if (!phase) return null;
|
|
4912
|
-
const
|
|
4987
|
+
const pd = phasesPath(cwd);
|
|
4913
4988
|
const normalized = normalizePhaseName(phase);
|
|
4914
|
-
const current = searchPhaseInDir(
|
|
4989
|
+
const current = searchPhaseInDir(pd, node_path.default.join(".planning", "phases"), normalized);
|
|
4915
4990
|
if (current) return current;
|
|
4916
|
-
const milestonesDir =
|
|
4917
|
-
|
|
4991
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
4992
|
+
try {
|
|
4993
|
+
node_fs.default.statSync(milestonesDir);
|
|
4994
|
+
} catch {
|
|
4995
|
+
return null;
|
|
4996
|
+
}
|
|
4918
4997
|
try {
|
|
4919
4998
|
const archiveDirs = node_fs.default.readdirSync(milestonesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name)).map((e) => e.name).sort().reverse();
|
|
4920
4999
|
for (const archiveName of archiveDirs) {
|
|
@@ -4928,14 +5007,13 @@ function findPhaseInternal(cwd, phase) {
|
|
|
4928
5007
|
}
|
|
4929
5008
|
}
|
|
4930
5009
|
} catch (e) {
|
|
4931
|
-
|
|
5010
|
+
debugLog(e);
|
|
4932
5011
|
}
|
|
4933
5012
|
return null;
|
|
4934
5013
|
}
|
|
4935
5014
|
function getArchivedPhaseDirs(cwd) {
|
|
4936
|
-
const milestonesDir =
|
|
5015
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
4937
5016
|
const results = [];
|
|
4938
|
-
if (!node_fs.default.existsSync(milestonesDir)) return results;
|
|
4939
5017
|
try {
|
|
4940
5018
|
const phaseDirs = node_fs.default.readdirSync(milestonesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name)).map((e) => e.name).sort().reverse();
|
|
4941
5019
|
for (const archiveName of phaseDirs) {
|
|
@@ -4943,7 +5021,7 @@ function getArchivedPhaseDirs(cwd) {
|
|
|
4943
5021
|
if (!versionMatch) continue;
|
|
4944
5022
|
const version = versionMatch[1];
|
|
4945
5023
|
const archivePath = node_path.default.join(milestonesDir, archiveName);
|
|
4946
|
-
const dirs =
|
|
5024
|
+
const dirs = listSubDirs(archivePath, true);
|
|
4947
5025
|
for (const dir of dirs) results.push({
|
|
4948
5026
|
name: dir,
|
|
4949
5027
|
milestone: version,
|
|
@@ -4952,17 +5030,16 @@ function getArchivedPhaseDirs(cwd) {
|
|
|
4952
5030
|
});
|
|
4953
5031
|
}
|
|
4954
5032
|
} catch (e) {
|
|
4955
|
-
|
|
5033
|
+
debugLog(e);
|
|
4956
5034
|
}
|
|
4957
5035
|
return results;
|
|
4958
5036
|
}
|
|
4959
5037
|
function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
4960
5038
|
if (!phaseNum) return null;
|
|
4961
|
-
const
|
|
4962
|
-
if (!node_fs.default.existsSync(roadmapPath)) return null;
|
|
5039
|
+
const rp = roadmapPath(cwd);
|
|
4963
5040
|
try {
|
|
4964
|
-
const content = node_fs.default.readFileSync(
|
|
4965
|
-
const phasePattern = getPhasePattern(phaseNum
|
|
5041
|
+
const content = node_fs.default.readFileSync(rp, "utf-8");
|
|
5042
|
+
const phasePattern = getPhasePattern(escapePhaseNum(phaseNum), "i");
|
|
4966
5043
|
const headerMatch = content.match(phasePattern);
|
|
4967
5044
|
if (!headerMatch) return null;
|
|
4968
5045
|
const phaseName = headerMatch[1].trim();
|
|
@@ -4983,8 +5060,8 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
4983
5060
|
return null;
|
|
4984
5061
|
}
|
|
4985
5062
|
}
|
|
4986
|
-
function resolveModelInternal(cwd, agentType) {
|
|
4987
|
-
|
|
5063
|
+
function resolveModelInternal(cwd, agentType, config) {
|
|
5064
|
+
config = config ?? loadConfig(cwd);
|
|
4988
5065
|
const override = config.model_overrides?.[agentType];
|
|
4989
5066
|
if (override) return override === "opus" ? "inherit" : override;
|
|
4990
5067
|
const profile = config.model_profile || "balanced";
|
|
@@ -5011,7 +5088,7 @@ function generateSlugInternal(text) {
|
|
|
5011
5088
|
}
|
|
5012
5089
|
function getMilestoneInfo(cwd) {
|
|
5013
5090
|
try {
|
|
5014
|
-
const roadmap = node_fs.default.readFileSync(
|
|
5091
|
+
const roadmap = node_fs.default.readFileSync(roadmapPath(cwd), "utf-8");
|
|
5015
5092
|
const versionMatch = roadmap.match(/v(\d+\.\d+)/);
|
|
5016
5093
|
const nameMatch = roadmap.match(/## .*v\d+\.\d+[:\s]+([^\n(]+)/);
|
|
5017
5094
|
return {
|
|
@@ -11994,17 +12071,32 @@ function readTextArgOrFile(cwd, value, filePath, label) {
|
|
|
11994
12071
|
throw new Error(`${label} file not found: ${filePath}`);
|
|
11995
12072
|
}
|
|
11996
12073
|
}
|
|
12074
|
+
/**
|
|
12075
|
+
* Append an entry to a section in STATE.md content, removing placeholder text.
|
|
12076
|
+
* Returns updated content or null if section not found.
|
|
12077
|
+
*/
|
|
12078
|
+
function appendToStateSection(content, sectionPattern, entry, placeholderPatterns) {
|
|
12079
|
+
const match = content.match(sectionPattern);
|
|
12080
|
+
if (!match) return null;
|
|
12081
|
+
let sectionBody = match[2];
|
|
12082
|
+
for (const pat of placeholderPatterns || [
|
|
12083
|
+
/None yet\.?\s*\n?/gi,
|
|
12084
|
+
/No decisions yet\.?\s*\n?/gi,
|
|
12085
|
+
/None\.?\s*\n?/gi
|
|
12086
|
+
]) sectionBody = sectionBody.replace(pat, "");
|
|
12087
|
+
sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
|
|
12088
|
+
return content.replace(sectionPattern, (_m, header) => `${header}${sectionBody}`);
|
|
12089
|
+
}
|
|
11997
12090
|
function cmdStateLoad(cwd, raw) {
|
|
11998
12091
|
const config = loadConfig(cwd);
|
|
11999
|
-
const planningDir = node_path.default.join(cwd, ".planning");
|
|
12000
12092
|
let stateRaw = "";
|
|
12001
12093
|
try {
|
|
12002
|
-
stateRaw = node_fs.default.readFileSync(
|
|
12094
|
+
stateRaw = node_fs.default.readFileSync(statePath(cwd), "utf-8");
|
|
12003
12095
|
} catch (e) {
|
|
12004
|
-
|
|
12096
|
+
debugLog(e);
|
|
12005
12097
|
}
|
|
12006
|
-
const configExists = node_fs.default.existsSync(
|
|
12007
|
-
const roadmapExists = node_fs.default.existsSync(
|
|
12098
|
+
const configExists = node_fs.default.existsSync(configPath(cwd));
|
|
12099
|
+
const roadmapExists = node_fs.default.existsSync(roadmapPath(cwd));
|
|
12008
12100
|
const stateExists = stateRaw.length > 0;
|
|
12009
12101
|
const result = {
|
|
12010
12102
|
config,
|
|
@@ -12015,7 +12107,7 @@ function cmdStateLoad(cwd, raw) {
|
|
|
12015
12107
|
};
|
|
12016
12108
|
if (raw) {
|
|
12017
12109
|
const c = config;
|
|
12018
|
-
|
|
12110
|
+
output(result, true, [
|
|
12019
12111
|
`model_profile=${c.model_profile}`,
|
|
12020
12112
|
`commit_docs=${c.commit_docs}`,
|
|
12021
12113
|
`branching_strategy=${c.branching_strategy}`,
|
|
@@ -12028,16 +12120,14 @@ function cmdStateLoad(cwd, raw) {
|
|
|
12028
12120
|
`config_exists=${configExists}`,
|
|
12029
12121
|
`roadmap_exists=${roadmapExists}`,
|
|
12030
12122
|
`state_exists=${stateExists}`
|
|
12031
|
-
];
|
|
12032
|
-
process.stdout.write(lines.join("\n"));
|
|
12033
|
-
process.exit(0);
|
|
12123
|
+
].join("\n"));
|
|
12034
12124
|
}
|
|
12035
12125
|
output(result);
|
|
12036
12126
|
}
|
|
12037
12127
|
function cmdStateGet(cwd, section, raw) {
|
|
12038
|
-
const statePath =
|
|
12128
|
+
const statePath$2 = statePath(cwd);
|
|
12039
12129
|
try {
|
|
12040
|
-
const content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12130
|
+
const content = node_fs.default.readFileSync(statePath$2, "utf-8");
|
|
12041
12131
|
if (!section) {
|
|
12042
12132
|
output({ content }, raw, content);
|
|
12043
12133
|
return;
|
|
@@ -12056,14 +12146,15 @@ function cmdStateGet(cwd, section, raw) {
|
|
|
12056
12146
|
return;
|
|
12057
12147
|
}
|
|
12058
12148
|
output({ error: `Section or field "${section}" not found` }, raw, "");
|
|
12059
|
-
} catch {
|
|
12149
|
+
} catch (e) {
|
|
12150
|
+
rethrowCliSignals(e);
|
|
12060
12151
|
error("STATE.md not found");
|
|
12061
12152
|
}
|
|
12062
12153
|
}
|
|
12063
12154
|
function cmdStatePatch(cwd, patches, raw) {
|
|
12064
|
-
const statePath =
|
|
12155
|
+
const statePath$3 = statePath(cwd);
|
|
12065
12156
|
try {
|
|
12066
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12157
|
+
let content = node_fs.default.readFileSync(statePath$3, "utf-8");
|
|
12067
12158
|
const results = {
|
|
12068
12159
|
updated: [],
|
|
12069
12160
|
failed: []
|
|
@@ -12076,28 +12167,30 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
12076
12167
|
results.updated.push(field);
|
|
12077
12168
|
} else results.failed.push(field);
|
|
12078
12169
|
}
|
|
12079
|
-
if (results.updated.length > 0) node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12170
|
+
if (results.updated.length > 0) node_fs.default.writeFileSync(statePath$3, content, "utf-8");
|
|
12080
12171
|
output(results, raw, results.updated.length > 0 ? "true" : "false");
|
|
12081
|
-
} catch {
|
|
12172
|
+
} catch (e) {
|
|
12173
|
+
rethrowCliSignals(e);
|
|
12082
12174
|
error("STATE.md not found");
|
|
12083
12175
|
}
|
|
12084
12176
|
}
|
|
12085
12177
|
function cmdStateUpdate(cwd, field, value) {
|
|
12086
12178
|
if (!field || value === void 0) error("field and value required for state update");
|
|
12087
|
-
const statePath =
|
|
12179
|
+
const statePath$4 = statePath(cwd);
|
|
12088
12180
|
try {
|
|
12089
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12181
|
+
let content = node_fs.default.readFileSync(statePath$4, "utf-8");
|
|
12090
12182
|
const fieldEscaped = escapeStringRegexp(field);
|
|
12091
12183
|
const pattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, "i");
|
|
12092
12184
|
if (pattern.test(content)) {
|
|
12093
12185
|
content = content.replace(pattern, (_match, prefix) => `${prefix}${value}`);
|
|
12094
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12186
|
+
node_fs.default.writeFileSync(statePath$4, content, "utf-8");
|
|
12095
12187
|
output({ updated: true });
|
|
12096
12188
|
} else output({
|
|
12097
12189
|
updated: false,
|
|
12098
12190
|
reason: `Field "${field}" not found in STATE.md`
|
|
12099
12191
|
});
|
|
12100
|
-
} catch {
|
|
12192
|
+
} catch (e) {
|
|
12193
|
+
rethrowCliSignals(e);
|
|
12101
12194
|
output({
|
|
12102
12195
|
updated: false,
|
|
12103
12196
|
reason: "STATE.md not found"
|
|
@@ -12105,15 +12198,15 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
12105
12198
|
}
|
|
12106
12199
|
}
|
|
12107
12200
|
function cmdStateAdvancePlan(cwd, raw) {
|
|
12108
|
-
const statePath =
|
|
12109
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12201
|
+
const statePath$5 = statePath(cwd);
|
|
12202
|
+
if (!node_fs.default.existsSync(statePath$5)) {
|
|
12110
12203
|
output({ error: "STATE.md not found" }, raw);
|
|
12111
12204
|
return;
|
|
12112
12205
|
}
|
|
12113
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12206
|
+
let content = node_fs.default.readFileSync(statePath$5, "utf-8");
|
|
12114
12207
|
const currentPlan = parseInt(stateExtractField(content, "Current Plan") ?? "", 10);
|
|
12115
12208
|
const totalPlans = parseInt(stateExtractField(content, "Total Plans in Phase") ?? "", 10);
|
|
12116
|
-
const today = (
|
|
12209
|
+
const today = todayISO();
|
|
12117
12210
|
if (isNaN(currentPlan) || isNaN(totalPlans)) {
|
|
12118
12211
|
output({ error: "Cannot parse Current Plan or Total Plans in Phase from STATE.md" }, raw);
|
|
12119
12212
|
return;
|
|
@@ -12121,7 +12214,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12121
12214
|
if (currentPlan >= totalPlans) {
|
|
12122
12215
|
content = stateReplaceField(content, "Status", "Phase complete — ready for verification") || content;
|
|
12123
12216
|
content = stateReplaceField(content, "Last Activity", today) || content;
|
|
12124
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12217
|
+
node_fs.default.writeFileSync(statePath$5, content, "utf-8");
|
|
12125
12218
|
output({
|
|
12126
12219
|
advanced: false,
|
|
12127
12220
|
reason: "last_plan",
|
|
@@ -12134,7 +12227,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12134
12227
|
content = stateReplaceField(content, "Current Plan", String(newPlan)) || content;
|
|
12135
12228
|
content = stateReplaceField(content, "Status", "Ready to execute") || content;
|
|
12136
12229
|
content = stateReplaceField(content, "Last Activity", today) || content;
|
|
12137
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12230
|
+
node_fs.default.writeFileSync(statePath$5, content, "utf-8");
|
|
12138
12231
|
output({
|
|
12139
12232
|
advanced: true,
|
|
12140
12233
|
previous_plan: currentPlan,
|
|
@@ -12144,12 +12237,12 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12144
12237
|
}
|
|
12145
12238
|
}
|
|
12146
12239
|
function cmdStateRecordMetric(cwd, options, raw) {
|
|
12147
|
-
const statePath =
|
|
12148
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12240
|
+
const statePath$6 = statePath(cwd);
|
|
12241
|
+
if (!node_fs.default.existsSync(statePath$6)) {
|
|
12149
12242
|
output({ error: "STATE.md not found" }, raw);
|
|
12150
12243
|
return;
|
|
12151
12244
|
}
|
|
12152
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12245
|
+
let content = node_fs.default.readFileSync(statePath$6, "utf-8");
|
|
12153
12246
|
const { phase, plan, duration, tasks, files } = options;
|
|
12154
12247
|
if (!phase || !plan || !duration) {
|
|
12155
12248
|
output({ error: "phase, plan, and duration required" }, raw);
|
|
@@ -12163,7 +12256,7 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
12163
12256
|
if (tableBody.trim() === "" || tableBody.includes("None yet")) tableBody = newRow;
|
|
12164
12257
|
else tableBody = tableBody + "\n" + newRow;
|
|
12165
12258
|
content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
|
|
12166
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12259
|
+
node_fs.default.writeFileSync(statePath$6, content, "utf-8");
|
|
12167
12260
|
output({
|
|
12168
12261
|
recorded: true,
|
|
12169
12262
|
phase,
|
|
@@ -12176,21 +12269,21 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
12176
12269
|
}, raw, "false");
|
|
12177
12270
|
}
|
|
12178
12271
|
function cmdStateUpdateProgress(cwd, raw) {
|
|
12179
|
-
const statePath =
|
|
12180
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12272
|
+
const statePath$7 = statePath(cwd);
|
|
12273
|
+
if (!node_fs.default.existsSync(statePath$7)) {
|
|
12181
12274
|
output({ error: "STATE.md not found" }, raw);
|
|
12182
12275
|
return;
|
|
12183
12276
|
}
|
|
12184
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12185
|
-
const phasesDir =
|
|
12277
|
+
let content = node_fs.default.readFileSync(statePath$7, "utf-8");
|
|
12278
|
+
const phasesDir = phasesPath(cwd);
|
|
12186
12279
|
let totalPlans = 0;
|
|
12187
12280
|
let totalSummaries = 0;
|
|
12188
12281
|
if (node_fs.default.existsSync(phasesDir)) {
|
|
12189
12282
|
const phaseDirs = node_fs.default.readdirSync(phasesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
12190
12283
|
for (const dir of phaseDirs) {
|
|
12191
12284
|
const files = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
12192
|
-
totalPlans += files.filter((f) => f
|
|
12193
|
-
totalSummaries += files.filter((f) => f
|
|
12285
|
+
totalPlans += files.filter((f) => isPlanFile(f)).length;
|
|
12286
|
+
totalSummaries += files.filter((f) => isSummaryFile(f)).length;
|
|
12194
12287
|
}
|
|
12195
12288
|
}
|
|
12196
12289
|
const percent = totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0;
|
|
@@ -12200,7 +12293,7 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
12200
12293
|
const progressPattern = /(\*\*Progress:\*\*\s*).*/i;
|
|
12201
12294
|
if (progressPattern.test(content)) {
|
|
12202
12295
|
content = content.replace(progressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
12203
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12296
|
+
node_fs.default.writeFileSync(statePath$7, content, "utf-8");
|
|
12204
12297
|
output({
|
|
12205
12298
|
updated: true,
|
|
12206
12299
|
percent,
|
|
@@ -12214,8 +12307,8 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
12214
12307
|
}, raw, "false");
|
|
12215
12308
|
}
|
|
12216
12309
|
function cmdStateAddDecision(cwd, options, raw) {
|
|
12217
|
-
const statePath =
|
|
12218
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12310
|
+
const statePath$8 = statePath(cwd);
|
|
12311
|
+
if (!node_fs.default.existsSync(statePath$8)) {
|
|
12219
12312
|
output({ error: "STATE.md not found" }, raw);
|
|
12220
12313
|
return;
|
|
12221
12314
|
}
|
|
@@ -12236,16 +12329,11 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
12236
12329
|
output({ error: "summary required" }, raw);
|
|
12237
12330
|
return;
|
|
12238
12331
|
}
|
|
12239
|
-
|
|
12332
|
+
const content = node_fs.default.readFileSync(statePath$8, "utf-8");
|
|
12240
12333
|
const entry = `- [Phase ${phase || "?"}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ""}`;
|
|
12241
|
-
const
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
let sectionBody = match[2];
|
|
12245
|
-
sectionBody = sectionBody.replace(/None yet\.?\s*\n?/gi, "").replace(/No decisions yet\.?\s*\n?/gi, "");
|
|
12246
|
-
sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
|
|
12247
|
-
content = content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
|
|
12248
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12334
|
+
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]);
|
|
12335
|
+
if (updated) {
|
|
12336
|
+
node_fs.default.writeFileSync(statePath$8, updated, "utf-8");
|
|
12249
12337
|
output({
|
|
12250
12338
|
added: true,
|
|
12251
12339
|
decision: entry
|
|
@@ -12256,8 +12344,8 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
12256
12344
|
}, raw, "false");
|
|
12257
12345
|
}
|
|
12258
12346
|
function cmdStateAddBlocker(cwd, text, raw) {
|
|
12259
|
-
const statePath =
|
|
12260
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12347
|
+
const statePath$9 = statePath(cwd);
|
|
12348
|
+
if (!node_fs.default.existsSync(statePath$9)) {
|
|
12261
12349
|
output({ error: "STATE.md not found" }, raw);
|
|
12262
12350
|
return;
|
|
12263
12351
|
}
|
|
@@ -12276,16 +12364,9 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
12276
12364
|
output({ error: "text required" }, raw);
|
|
12277
12365
|
return;
|
|
12278
12366
|
}
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
const match = content.match(sectionPattern);
|
|
12283
|
-
if (match) {
|
|
12284
|
-
let sectionBody = match[2];
|
|
12285
|
-
sectionBody = sectionBody.replace(/None\.?\s*\n?/gi, "").replace(/None yet\.?\s*\n?/gi, "");
|
|
12286
|
-
sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
|
|
12287
|
-
content = content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
|
|
12288
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12367
|
+
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]);
|
|
12368
|
+
if (updated) {
|
|
12369
|
+
node_fs.default.writeFileSync(statePath$9, updated, "utf-8");
|
|
12289
12370
|
output({
|
|
12290
12371
|
added: true,
|
|
12291
12372
|
blocker: blockerText
|
|
@@ -12296,8 +12377,8 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
12296
12377
|
}, raw, "false");
|
|
12297
12378
|
}
|
|
12298
12379
|
function cmdStateResolveBlocker(cwd, text, raw) {
|
|
12299
|
-
const statePath =
|
|
12300
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12380
|
+
const statePath$10 = statePath(cwd);
|
|
12381
|
+
if (!node_fs.default.existsSync(statePath$10)) {
|
|
12301
12382
|
output({ error: "STATE.md not found" }, raw);
|
|
12302
12383
|
return;
|
|
12303
12384
|
}
|
|
@@ -12305,7 +12386,7 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12305
12386
|
output({ error: "text required" }, raw);
|
|
12306
12387
|
return;
|
|
12307
12388
|
}
|
|
12308
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12389
|
+
let content = node_fs.default.readFileSync(statePath$10, "utf-8");
|
|
12309
12390
|
const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
|
|
12310
12391
|
const match = content.match(sectionPattern);
|
|
12311
12392
|
if (match) {
|
|
@@ -12315,7 +12396,7 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12315
12396
|
}).join("\n");
|
|
12316
12397
|
if (!newBody.trim() || !newBody.includes("- ")) newBody = "None\n";
|
|
12317
12398
|
content = content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
|
|
12318
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12399
|
+
node_fs.default.writeFileSync(statePath$10, content, "utf-8");
|
|
12319
12400
|
output({
|
|
12320
12401
|
resolved: true,
|
|
12321
12402
|
blocker: text
|
|
@@ -12326,12 +12407,12 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12326
12407
|
}, raw, "false");
|
|
12327
12408
|
}
|
|
12328
12409
|
function cmdStateRecordSession(cwd, options, raw) {
|
|
12329
|
-
const statePath =
|
|
12330
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12410
|
+
const statePath$11 = statePath(cwd);
|
|
12411
|
+
if (!node_fs.default.existsSync(statePath$11)) {
|
|
12331
12412
|
output({ error: "STATE.md not found" }, raw);
|
|
12332
12413
|
return;
|
|
12333
12414
|
}
|
|
12334
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12415
|
+
let content = node_fs.default.readFileSync(statePath$11, "utf-8");
|
|
12335
12416
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12336
12417
|
const updated = [];
|
|
12337
12418
|
let result = stateReplaceField(content, "Last session", now);
|
|
@@ -12360,7 +12441,7 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
12360
12441
|
updated.push("Resume File");
|
|
12361
12442
|
}
|
|
12362
12443
|
if (updated.length > 0) {
|
|
12363
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12444
|
+
node_fs.default.writeFileSync(statePath$11, content, "utf-8");
|
|
12364
12445
|
output({
|
|
12365
12446
|
recorded: true,
|
|
12366
12447
|
updated
|
|
@@ -12371,12 +12452,12 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
12371
12452
|
}, raw, "false");
|
|
12372
12453
|
}
|
|
12373
12454
|
function cmdStateSnapshot(cwd, raw) {
|
|
12374
|
-
const statePath =
|
|
12375
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12455
|
+
const statePath$12 = statePath(cwd);
|
|
12456
|
+
if (!node_fs.default.existsSync(statePath$12)) {
|
|
12376
12457
|
output({ error: "STATE.md not found" }, raw);
|
|
12377
12458
|
return;
|
|
12378
12459
|
}
|
|
12379
|
-
const content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12460
|
+
const content = node_fs.default.readFileSync(statePath$12, "utf-8");
|
|
12380
12461
|
const extractField = (fieldName) => {
|
|
12381
12462
|
const pattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, "i");
|
|
12382
12463
|
const match = content.match(pattern);
|
|
@@ -12454,8 +12535,8 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
12454
12535
|
* Ported from maxsim/bin/lib/roadmap.cjs
|
|
12455
12536
|
*/
|
|
12456
12537
|
function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
12457
|
-
const
|
|
12458
|
-
if (!node_fs.default.existsSync(
|
|
12538
|
+
const rmPath = roadmapPath(cwd);
|
|
12539
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
12459
12540
|
output({
|
|
12460
12541
|
found: false,
|
|
12461
12542
|
error: "ROADMAP.md not found"
|
|
@@ -12463,7 +12544,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
12463
12544
|
return;
|
|
12464
12545
|
}
|
|
12465
12546
|
try {
|
|
12466
|
-
const content = node_fs.default.readFileSync(
|
|
12547
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12467
12548
|
const escapedPhase = phaseNum.replace(/\./g, "\\.");
|
|
12468
12549
|
const phasePattern = getPhasePattern(escapedPhase, "i");
|
|
12469
12550
|
const headerMatch = content.match(phasePattern);
|
|
@@ -12503,12 +12584,13 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
12503
12584
|
section
|
|
12504
12585
|
}, raw, section);
|
|
12505
12586
|
} catch (e) {
|
|
12587
|
+
rethrowCliSignals(e);
|
|
12506
12588
|
error("Failed to read ROADMAP.md: " + e.message);
|
|
12507
12589
|
}
|
|
12508
12590
|
}
|
|
12509
12591
|
function cmdRoadmapAnalyze(cwd, raw) {
|
|
12510
|
-
const
|
|
12511
|
-
if (!node_fs.default.existsSync(
|
|
12592
|
+
const rmPath = roadmapPath(cwd);
|
|
12593
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
12512
12594
|
output({
|
|
12513
12595
|
error: "ROADMAP.md not found",
|
|
12514
12596
|
milestones: [],
|
|
@@ -12517,8 +12599,8 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12517
12599
|
}, raw);
|
|
12518
12600
|
return;
|
|
12519
12601
|
}
|
|
12520
|
-
const content = node_fs.default.readFileSync(
|
|
12521
|
-
const phasesDir =
|
|
12602
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12603
|
+
const phasesDir = phasesPath(cwd);
|
|
12522
12604
|
const phasePattern = getPhasePattern();
|
|
12523
12605
|
const phases = [];
|
|
12524
12606
|
let match;
|
|
@@ -12540,11 +12622,11 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12540
12622
|
let hasContext = false;
|
|
12541
12623
|
let hasResearch = false;
|
|
12542
12624
|
try {
|
|
12543
|
-
const dirMatch =
|
|
12625
|
+
const dirMatch = listSubDirs(phasesDir).find((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
12544
12626
|
if (dirMatch) {
|
|
12545
12627
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dirMatch));
|
|
12546
|
-
planCount = phaseFiles.filter((f) =>
|
|
12547
|
-
summaryCount = phaseFiles.filter((f) =>
|
|
12628
|
+
planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
|
|
12629
|
+
summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
|
|
12548
12630
|
hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
12549
12631
|
hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
12550
12632
|
if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
|
|
@@ -12555,7 +12637,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12555
12637
|
else diskStatus = "empty";
|
|
12556
12638
|
}
|
|
12557
12639
|
} catch (e) {
|
|
12558
|
-
|
|
12640
|
+
debugLog(e);
|
|
12559
12641
|
}
|
|
12560
12642
|
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i");
|
|
12561
12643
|
const checkboxMatch = content.match(checkboxPattern);
|
|
@@ -12606,7 +12688,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12606
12688
|
}
|
|
12607
12689
|
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
12608
12690
|
if (!phaseNum) error("phase number required for roadmap update-plan-progress");
|
|
12609
|
-
const
|
|
12691
|
+
const rmPath = roadmapPath(cwd);
|
|
12610
12692
|
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
12611
12693
|
if (!phaseInfo) error(`Phase ${phaseNum} not found`);
|
|
12612
12694
|
const planCount = phaseInfo.plans.length;
|
|
@@ -12622,8 +12704,8 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
12622
12704
|
}
|
|
12623
12705
|
const isComplete = summaryCount >= planCount;
|
|
12624
12706
|
const status = isComplete ? "Complete" : summaryCount > 0 ? "In Progress" : "Planned";
|
|
12625
|
-
const today = (
|
|
12626
|
-
if (!node_fs.default.existsSync(
|
|
12707
|
+
const today = todayISO();
|
|
12708
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
12627
12709
|
output({
|
|
12628
12710
|
updated: false,
|
|
12629
12711
|
reason: "ROADMAP.md not found",
|
|
@@ -12632,7 +12714,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
12632
12714
|
}, raw, "no roadmap");
|
|
12633
12715
|
return;
|
|
12634
12716
|
}
|
|
12635
|
-
let roadmapContent = node_fs.default.readFileSync(
|
|
12717
|
+
let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12636
12718
|
const phaseEscaped = phaseNum.replace(".", "\\.");
|
|
12637
12719
|
const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i");
|
|
12638
12720
|
const dateField = isComplete ? ` ${today} ` : " ";
|
|
@@ -12644,7 +12726,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
12644
12726
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, "i");
|
|
12645
12727
|
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
12646
12728
|
}
|
|
12647
|
-
node_fs.default.writeFileSync(
|
|
12729
|
+
node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
|
|
12648
12730
|
output({
|
|
12649
12731
|
updated: true,
|
|
12650
12732
|
phase: phaseNum,
|
|
@@ -12666,7 +12748,7 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
12666
12748
|
if (!reqIdsRaw || reqIdsRaw.length === 0) error("requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02");
|
|
12667
12749
|
const reqIds = reqIdsRaw.join(" ").replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
|
|
12668
12750
|
if (reqIds.length === 0) error("no valid requirement IDs found");
|
|
12669
|
-
const reqPath =
|
|
12751
|
+
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
12670
12752
|
if (!node_fs.default.existsSync(reqPath)) {
|
|
12671
12753
|
output({
|
|
12672
12754
|
updated: false,
|
|
@@ -12702,13 +12784,13 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
12702
12784
|
}
|
|
12703
12785
|
function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
12704
12786
|
if (!version) error("version required for milestone complete (e.g., v1.0)");
|
|
12705
|
-
const roadmapPath =
|
|
12706
|
-
const reqPath =
|
|
12707
|
-
const statePath =
|
|
12708
|
-
const milestonesPath =
|
|
12709
|
-
const archiveDir =
|
|
12710
|
-
const phasesDir =
|
|
12711
|
-
const today = (
|
|
12787
|
+
const roadmapPath$1 = roadmapPath(cwd);
|
|
12788
|
+
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
12789
|
+
const statePath$1 = statePath(cwd);
|
|
12790
|
+
const milestonesPath = planningPath(cwd, "MILESTONES.md");
|
|
12791
|
+
const archiveDir = planningPath(cwd, "milestones");
|
|
12792
|
+
const phasesDir = phasesPath(cwd);
|
|
12793
|
+
const today = todayISO();
|
|
12712
12794
|
const milestoneName = options.name || version;
|
|
12713
12795
|
node_fs.default.mkdirSync(archiveDir, { recursive: true });
|
|
12714
12796
|
let phaseCount = 0;
|
|
@@ -12716,12 +12798,12 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12716
12798
|
let totalTasks = 0;
|
|
12717
12799
|
const accomplishments = [];
|
|
12718
12800
|
try {
|
|
12719
|
-
const dirs =
|
|
12801
|
+
const dirs = listSubDirs(phasesDir, true);
|
|
12720
12802
|
for (const dir of dirs) {
|
|
12721
12803
|
phaseCount++;
|
|
12722
12804
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
12723
|
-
const plans = phaseFiles.filter(
|
|
12724
|
-
const summaries = phaseFiles.filter(
|
|
12805
|
+
const plans = phaseFiles.filter(isPlanFile);
|
|
12806
|
+
const summaries = phaseFiles.filter(isSummaryFile);
|
|
12725
12807
|
totalPlans += plans.length;
|
|
12726
12808
|
for (const s of summaries) try {
|
|
12727
12809
|
const content = node_fs.default.readFileSync(node_path.default.join(phasesDir, dir, s), "utf-8");
|
|
@@ -12730,14 +12812,14 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12730
12812
|
const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
12731
12813
|
totalTasks += taskMatches.length;
|
|
12732
12814
|
} catch (e) {
|
|
12733
|
-
|
|
12815
|
+
debugLog(e);
|
|
12734
12816
|
}
|
|
12735
12817
|
}
|
|
12736
12818
|
} catch (e) {
|
|
12737
|
-
|
|
12819
|
+
debugLog(e);
|
|
12738
12820
|
}
|
|
12739
|
-
if (node_fs.default.existsSync(roadmapPath)) {
|
|
12740
|
-
const roadmapContent = node_fs.default.readFileSync(roadmapPath, "utf-8");
|
|
12821
|
+
if (node_fs.default.existsSync(roadmapPath$1)) {
|
|
12822
|
+
const roadmapContent = node_fs.default.readFileSync(roadmapPath$1, "utf-8");
|
|
12741
12823
|
node_fs.default.writeFileSync(node_path.default.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, "utf-8");
|
|
12742
12824
|
}
|
|
12743
12825
|
if (node_fs.default.existsSync(reqPath)) {
|
|
@@ -12753,22 +12835,22 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12753
12835
|
const existing = node_fs.default.readFileSync(milestonesPath, "utf-8");
|
|
12754
12836
|
node_fs.default.writeFileSync(milestonesPath, existing + "\n" + milestoneEntry, "utf-8");
|
|
12755
12837
|
} else node_fs.default.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, "utf-8");
|
|
12756
|
-
if (node_fs.default.existsSync(statePath)) {
|
|
12757
|
-
let stateContent = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12838
|
+
if (node_fs.default.existsSync(statePath$1)) {
|
|
12839
|
+
let stateContent = node_fs.default.readFileSync(statePath$1, "utf-8");
|
|
12758
12840
|
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${version} milestone complete`);
|
|
12759
12841
|
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
12760
12842
|
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1${version} milestone completed and archived`);
|
|
12761
|
-
node_fs.default.writeFileSync(statePath, stateContent, "utf-8");
|
|
12843
|
+
node_fs.default.writeFileSync(statePath$1, stateContent, "utf-8");
|
|
12762
12844
|
}
|
|
12763
12845
|
let phasesArchived = false;
|
|
12764
12846
|
if (options.archivePhases) try {
|
|
12765
12847
|
const phaseArchiveDir = node_path.default.join(archiveDir, `${version}-phases`);
|
|
12766
12848
|
node_fs.default.mkdirSync(phaseArchiveDir, { recursive: true });
|
|
12767
|
-
const phaseDirNames =
|
|
12849
|
+
const phaseDirNames = listSubDirs(phasesDir);
|
|
12768
12850
|
for (const dir of phaseDirNames) node_fs.default.renameSync(node_path.default.join(phasesDir, dir), node_path.default.join(phaseArchiveDir, dir));
|
|
12769
12851
|
phasesArchived = phaseDirNames.length > 0;
|
|
12770
12852
|
} catch (e) {
|
|
12771
|
-
|
|
12853
|
+
debugLog(e);
|
|
12772
12854
|
}
|
|
12773
12855
|
output({
|
|
12774
12856
|
version,
|
|
@@ -12785,7 +12867,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12785
12867
|
phases: phasesArchived
|
|
12786
12868
|
},
|
|
12787
12869
|
milestones_updated: true,
|
|
12788
|
-
state_updated: node_fs.default.existsSync(statePath)
|
|
12870
|
+
state_updated: node_fs.default.existsSync(statePath$1)
|
|
12789
12871
|
}, raw);
|
|
12790
12872
|
}
|
|
12791
12873
|
|
|
@@ -13202,6 +13284,18 @@ const chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
|
13202
13284
|
*
|
|
13203
13285
|
* Ported from maxsim/bin/lib/commands.cjs
|
|
13204
13286
|
*/
|
|
13287
|
+
function parseTodoFrontmatter(content) {
|
|
13288
|
+
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
13289
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
13290
|
+
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
13291
|
+
const completedMatch = content.match(/^completed:\s*(.+)$/m);
|
|
13292
|
+
return {
|
|
13293
|
+
created: createdMatch ? createdMatch[1].trim() : "unknown",
|
|
13294
|
+
title: titleMatch ? titleMatch[1].trim() : "Untitled",
|
|
13295
|
+
area: areaMatch ? areaMatch[1].trim() : "general",
|
|
13296
|
+
...completedMatch && { completed: completedMatch[1].trim() }
|
|
13297
|
+
};
|
|
13298
|
+
}
|
|
13205
13299
|
function cmdGenerateSlug(text, raw) {
|
|
13206
13300
|
if (!text) error("text required for slug generation");
|
|
13207
13301
|
const slug = (0, import_slugify.default)(text, {
|
|
@@ -13215,7 +13309,7 @@ function cmdCurrentTimestamp(format, raw) {
|
|
|
13215
13309
|
let result;
|
|
13216
13310
|
switch (format) {
|
|
13217
13311
|
case "date":
|
|
13218
|
-
result =
|
|
13312
|
+
result = todayISO();
|
|
13219
13313
|
break;
|
|
13220
13314
|
case "filename":
|
|
13221
13315
|
result = now.toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
@@ -13227,31 +13321,27 @@ function cmdCurrentTimestamp(format, raw) {
|
|
|
13227
13321
|
output({ timestamp: result }, raw, result);
|
|
13228
13322
|
}
|
|
13229
13323
|
function cmdListTodos(cwd, area, raw) {
|
|
13230
|
-
const pendingDir =
|
|
13324
|
+
const pendingDir = planningPath(cwd, "todos", "pending");
|
|
13231
13325
|
let count = 0;
|
|
13232
13326
|
const todos = [];
|
|
13233
13327
|
try {
|
|
13234
13328
|
const files = node_fs.default.readdirSync(pendingDir).filter((f) => f.endsWith(".md"));
|
|
13235
13329
|
for (const file of files) try {
|
|
13236
|
-
const
|
|
13237
|
-
|
|
13238
|
-
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
13239
|
-
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
13240
|
-
const todoArea = areaMatch ? areaMatch[1].trim() : "general";
|
|
13241
|
-
if (area && todoArea !== area) continue;
|
|
13330
|
+
const fm = parseTodoFrontmatter(node_fs.default.readFileSync(node_path.default.join(pendingDir, file), "utf-8"));
|
|
13331
|
+
if (area && fm.area !== area) continue;
|
|
13242
13332
|
count++;
|
|
13243
13333
|
todos.push({
|
|
13244
13334
|
file,
|
|
13245
|
-
created:
|
|
13246
|
-
title:
|
|
13247
|
-
area:
|
|
13335
|
+
created: fm.created,
|
|
13336
|
+
title: fm.title,
|
|
13337
|
+
area: fm.area,
|
|
13248
13338
|
path: node_path.default.join(".planning", "todos", "pending", file)
|
|
13249
13339
|
});
|
|
13250
13340
|
} catch (e) {
|
|
13251
|
-
|
|
13341
|
+
debugLog(e);
|
|
13252
13342
|
}
|
|
13253
13343
|
} catch (e) {
|
|
13254
|
-
|
|
13344
|
+
debugLog(e);
|
|
13255
13345
|
}
|
|
13256
13346
|
output({
|
|
13257
13347
|
count,
|
|
@@ -13267,7 +13357,8 @@ function cmdVerifyPathExists(cwd, targetPath, raw) {
|
|
|
13267
13357
|
exists: true,
|
|
13268
13358
|
type: stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other"
|
|
13269
13359
|
}, raw, "true");
|
|
13270
|
-
} catch {
|
|
13360
|
+
} catch (e) {
|
|
13361
|
+
rethrowCliSignals(e);
|
|
13271
13362
|
output({
|
|
13272
13363
|
exists: false,
|
|
13273
13364
|
type: null
|
|
@@ -13275,7 +13366,7 @@ function cmdVerifyPathExists(cwd, targetPath, raw) {
|
|
|
13275
13366
|
}
|
|
13276
13367
|
}
|
|
13277
13368
|
function cmdHistoryDigest(cwd, raw) {
|
|
13278
|
-
const phasesDir =
|
|
13369
|
+
const phasesDir = phasesPath(cwd);
|
|
13279
13370
|
const digest = {
|
|
13280
13371
|
phases: {},
|
|
13281
13372
|
decisions: [],
|
|
@@ -13289,14 +13380,14 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13289
13380
|
milestone: a.milestone
|
|
13290
13381
|
});
|
|
13291
13382
|
if (node_fs.default.existsSync(phasesDir)) try {
|
|
13292
|
-
const currentDirs =
|
|
13383
|
+
const currentDirs = listSubDirs(phasesDir, true);
|
|
13293
13384
|
for (const dir of currentDirs) allPhaseDirs.push({
|
|
13294
13385
|
name: dir,
|
|
13295
13386
|
fullPath: node_path.default.join(phasesDir, dir),
|
|
13296
13387
|
milestone: null
|
|
13297
13388
|
});
|
|
13298
13389
|
} catch (e) {
|
|
13299
|
-
|
|
13390
|
+
debugLog(e);
|
|
13300
13391
|
}
|
|
13301
13392
|
if (allPhaseDirs.length === 0) {
|
|
13302
13393
|
output({
|
|
@@ -13308,7 +13399,7 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13308
13399
|
}
|
|
13309
13400
|
try {
|
|
13310
13401
|
for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
|
|
13311
|
-
const summaries = node_fs.default.readdirSync(dirPath).filter((f) =>
|
|
13402
|
+
const summaries = node_fs.default.readdirSync(dirPath).filter((f) => isSummaryFile(f));
|
|
13312
13403
|
for (const summary of summaries) try {
|
|
13313
13404
|
const fm = extractFrontmatter(node_fs.default.readFileSync(node_path.default.join(dirPath, summary), "utf-8"));
|
|
13314
13405
|
const phaseNum = fm.phase || dir.split("-")[0];
|
|
@@ -13332,7 +13423,7 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13332
13423
|
const techStack = fm["tech-stack"];
|
|
13333
13424
|
if (techStack && techStack.added) techStack.added.forEach((t) => digest.tech_stack.add(typeof t === "string" ? t : t.name));
|
|
13334
13425
|
} catch (e) {
|
|
13335
|
-
|
|
13426
|
+
debugLog(e);
|
|
13336
13427
|
}
|
|
13337
13428
|
}
|
|
13338
13429
|
const outputDigest = {
|
|
@@ -13348,6 +13439,7 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13348
13439
|
};
|
|
13349
13440
|
output(outputDigest, raw);
|
|
13350
13441
|
} catch (e) {
|
|
13442
|
+
rethrowCliSignals(e);
|
|
13351
13443
|
error("Failed to generate history digest: " + e.message);
|
|
13352
13444
|
}
|
|
13353
13445
|
}
|
|
@@ -13520,6 +13612,7 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
13520
13612
|
results
|
|
13521
13613
|
}, raw, results.map((r) => `${r.title}\n${r.url}\n${r.description}`).join("\n\n"));
|
|
13522
13614
|
} catch (err) {
|
|
13615
|
+
rethrowCliSignals(err);
|
|
13523
13616
|
output({
|
|
13524
13617
|
available: false,
|
|
13525
13618
|
error: err.message
|
|
@@ -13527,7 +13620,7 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
13527
13620
|
}
|
|
13528
13621
|
}
|
|
13529
13622
|
function cmdProgressRender(cwd, format, raw) {
|
|
13530
|
-
const phasesDir =
|
|
13623
|
+
const phasesDir = phasesPath(cwd);
|
|
13531
13624
|
const milestone = getMilestoneInfo(cwd);
|
|
13532
13625
|
const phases = [];
|
|
13533
13626
|
let totalPlans = 0;
|
|
@@ -13541,8 +13634,8 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
13541
13634
|
const phaseNum = dm ? dm[1] : dir;
|
|
13542
13635
|
const phaseName = dm && dm[2] ? dm[2].replace(/-/g, " ") : "";
|
|
13543
13636
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
13544
|
-
const planCount = phaseFiles.filter((f) =>
|
|
13545
|
-
const summaryCount = phaseFiles.filter((f) =>
|
|
13637
|
+
const planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
|
|
13638
|
+
const summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
|
|
13546
13639
|
totalPlans += planCount;
|
|
13547
13640
|
totalSummaries += summaryCount;
|
|
13548
13641
|
let status;
|
|
@@ -13559,7 +13652,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
13559
13652
|
});
|
|
13560
13653
|
}
|
|
13561
13654
|
} catch (e) {
|
|
13562
|
-
|
|
13655
|
+
debugLog(e);
|
|
13563
13656
|
}
|
|
13564
13657
|
const percent = totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0;
|
|
13565
13658
|
if (format === "table") {
|
|
@@ -13619,13 +13712,13 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
13619
13712
|
}
|
|
13620
13713
|
function cmdTodoComplete(cwd, filename, raw) {
|
|
13621
13714
|
if (!filename) error("filename required for todo complete");
|
|
13622
|
-
const pendingDir =
|
|
13623
|
-
const completedDir =
|
|
13715
|
+
const pendingDir = planningPath(cwd, "todos", "pending");
|
|
13716
|
+
const completedDir = planningPath(cwd, "todos", "completed");
|
|
13624
13717
|
const sourcePath = node_path.default.join(pendingDir, filename);
|
|
13625
13718
|
if (!node_fs.default.existsSync(sourcePath)) error(`Todo not found: ${filename}`);
|
|
13626
13719
|
node_fs.default.mkdirSync(completedDir, { recursive: true });
|
|
13627
13720
|
let content = node_fs.default.readFileSync(sourcePath, "utf-8");
|
|
13628
|
-
const today = (
|
|
13721
|
+
const today = todayISO();
|
|
13629
13722
|
content = `completed: ${today}\n` + content;
|
|
13630
13723
|
node_fs.default.writeFileSync(node_path.default.join(completedDir, filename), content, "utf-8");
|
|
13631
13724
|
node_fs.default.unlinkSync(sourcePath);
|
|
@@ -13638,7 +13731,7 @@ function cmdTodoComplete(cwd, filename, raw) {
|
|
|
13638
13731
|
function cmdScaffold(cwd, type, options, raw) {
|
|
13639
13732
|
const { phase, name } = options;
|
|
13640
13733
|
const padded = phase ? normalizePhaseName(phase) : "00";
|
|
13641
|
-
const today = (
|
|
13734
|
+
const today = todayISO();
|
|
13642
13735
|
const phaseInfo = phase ? findPhaseInternal(cwd, phase) : null;
|
|
13643
13736
|
const phaseDir = phaseInfo ? node_path.default.join(cwd, phaseInfo.directory) : null;
|
|
13644
13737
|
if (phase && !phaseDir && type !== "phase-dir") error(`Phase ${phase} directory not found`);
|
|
@@ -13660,7 +13753,7 @@ function cmdScaffold(cwd, type, options, raw) {
|
|
|
13660
13753
|
case "phase-dir": {
|
|
13661
13754
|
if (!phase || !name) error("phase and name required for phase-dir scaffold");
|
|
13662
13755
|
const dirName = `${padded}-${generateSlugInternal(name)}`;
|
|
13663
|
-
const phasesParent =
|
|
13756
|
+
const phasesParent = phasesPath(cwd);
|
|
13664
13757
|
node_fs.default.mkdirSync(phasesParent, { recursive: true });
|
|
13665
13758
|
const dirPath = node_path.default.join(phasesParent, dirName);
|
|
13666
13759
|
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
@@ -13853,10 +13946,10 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
13853
13946
|
output({ error: "Cannot read phase directory" }, raw);
|
|
13854
13947
|
return;
|
|
13855
13948
|
}
|
|
13856
|
-
const plans = files.filter((f) =>
|
|
13857
|
-
const summaries = files.filter((f) =>
|
|
13858
|
-
const planIds = new Set(plans.map((p) => p
|
|
13859
|
-
const summaryIds = new Set(summaries.map((s) => s
|
|
13949
|
+
const plans = files.filter((f) => isPlanFile(f));
|
|
13950
|
+
const summaries = files.filter((f) => isSummaryFile(f));
|
|
13951
|
+
const planIds = new Set(plans.map((p) => planId(p)));
|
|
13952
|
+
const summaryIds = new Set(summaries.map((s) => summaryId(s)));
|
|
13860
13953
|
const incompletePlans = [...planIds].filter((id) => !summaryIds.has(id));
|
|
13861
13954
|
if (incompletePlans.length > 0) errors.push(`Plans without summaries: ${incompletePlans.join(", ")}`);
|
|
13862
13955
|
const orphanSummaries = [...summaryIds].filter((id) => !planIds.has(id));
|
|
@@ -14041,11 +14134,11 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
14041
14134
|
}, raw, verified === results.length ? "valid" : "invalid");
|
|
14042
14135
|
}
|
|
14043
14136
|
function cmdValidateConsistency(cwd, raw) {
|
|
14044
|
-
const
|
|
14045
|
-
const phasesDir =
|
|
14137
|
+
const rmPath = roadmapPath(cwd);
|
|
14138
|
+
const phasesDir = phasesPath(cwd);
|
|
14046
14139
|
const errors = [];
|
|
14047
14140
|
const warnings = [];
|
|
14048
|
-
if (!node_fs.default.existsSync(
|
|
14141
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
14049
14142
|
errors.push("ROADMAP.md not found");
|
|
14050
14143
|
output({
|
|
14051
14144
|
passed: false,
|
|
@@ -14054,20 +14147,20 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
14054
14147
|
}, raw, "failed");
|
|
14055
14148
|
return;
|
|
14056
14149
|
}
|
|
14057
|
-
const roadmapContent = node_fs.default.readFileSync(
|
|
14150
|
+
const roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14058
14151
|
const roadmapPhases = /* @__PURE__ */ new Set();
|
|
14059
14152
|
const phasePattern = getPhasePattern();
|
|
14060
14153
|
let m;
|
|
14061
14154
|
while ((m = phasePattern.exec(roadmapContent)) !== null) roadmapPhases.add(m[1]);
|
|
14062
14155
|
const diskPhases = /* @__PURE__ */ new Set();
|
|
14063
14156
|
try {
|
|
14064
|
-
const dirs =
|
|
14157
|
+
const dirs = listSubDirs(phasesDir);
|
|
14065
14158
|
for (const dir of dirs) {
|
|
14066
14159
|
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
|
|
14067
14160
|
if (dm) diskPhases.add(dm[1]);
|
|
14068
14161
|
}
|
|
14069
14162
|
} catch (e) {
|
|
14070
|
-
|
|
14163
|
+
debugLog(e);
|
|
14071
14164
|
}
|
|
14072
14165
|
for (const p of roadmapPhases) if (!diskPhases.has(p) && !diskPhases.has(normalizePhaseName(p))) warnings.push(`Phase ${p} in ROADMAP.md but no directory on disk`);
|
|
14073
14166
|
for (const p of diskPhases) {
|
|
@@ -14077,31 +14170,31 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
14077
14170
|
const integerPhases = [...diskPhases].filter((p) => !p.includes(".")).map((p) => parseInt(p, 10)).sort((a, b) => a - b);
|
|
14078
14171
|
for (let i = 1; i < integerPhases.length; i++) if (integerPhases[i] !== integerPhases[i - 1] + 1) warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} → ${integerPhases[i]}`);
|
|
14079
14172
|
try {
|
|
14080
|
-
const dirs =
|
|
14173
|
+
const dirs = listSubDirs(phasesDir, true);
|
|
14081
14174
|
for (const dir of dirs) {
|
|
14082
14175
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
14083
|
-
const plans = phaseFiles.filter((f) => f
|
|
14176
|
+
const plans = phaseFiles.filter((f) => isPlanFile(f)).sort();
|
|
14084
14177
|
const planNums = plans.map((p) => {
|
|
14085
14178
|
const pm = p.match(/-(\d{2})-PLAN\.md$/);
|
|
14086
14179
|
return pm ? parseInt(pm[1], 10) : null;
|
|
14087
14180
|
}).filter((n) => n !== null);
|
|
14088
14181
|
for (let i = 1; i < planNums.length; i++) if (planNums[i] !== planNums[i - 1] + 1) warnings.push(`Gap in plan numbering in ${dir}: plan ${planNums[i - 1]} → ${planNums[i]}`);
|
|
14089
|
-
const summaries = phaseFiles.filter((f) => f
|
|
14090
|
-
const planIdsSet = new Set(plans.map((p) => p
|
|
14091
|
-
const summaryIdsSet = new Set(summaries.map((s) => s
|
|
14182
|
+
const summaries = phaseFiles.filter((f) => isSummaryFile(f));
|
|
14183
|
+
const planIdsSet = new Set(plans.map((p) => planId(p)));
|
|
14184
|
+
const summaryIdsSet = new Set(summaries.map((s) => summaryId(s)));
|
|
14092
14185
|
for (const sid of summaryIdsSet) if (!planIdsSet.has(sid)) warnings.push(`Summary ${sid}-SUMMARY.md in ${dir} has no matching PLAN.md`);
|
|
14093
14186
|
}
|
|
14094
14187
|
} catch (e) {
|
|
14095
|
-
|
|
14188
|
+
debugLog(e);
|
|
14096
14189
|
}
|
|
14097
14190
|
try {
|
|
14098
|
-
const dirs =
|
|
14191
|
+
const dirs = listSubDirs(phasesDir);
|
|
14099
14192
|
for (const dir of dirs) {
|
|
14100
|
-
const plans = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).filter((f) => f
|
|
14193
|
+
const plans = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).filter((f) => isPlanFile(f));
|
|
14101
14194
|
for (const plan of plans) if (!extractFrontmatter(node_fs.default.readFileSync(node_path.default.join(phasesDir, dir, plan), "utf-8")).wave) warnings.push(`${dir}/${plan}: missing 'wave' in frontmatter`);
|
|
14102
14195
|
}
|
|
14103
14196
|
} catch (e) {
|
|
14104
|
-
|
|
14197
|
+
debugLog(e);
|
|
14105
14198
|
}
|
|
14106
14199
|
const passed = errors.length === 0;
|
|
14107
14200
|
output({
|
|
@@ -14112,12 +14205,12 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
14112
14205
|
}, raw, passed ? "passed" : "failed");
|
|
14113
14206
|
}
|
|
14114
14207
|
function cmdValidateHealth(cwd, options, raw) {
|
|
14115
|
-
const planningDir =
|
|
14116
|
-
const projectPath =
|
|
14117
|
-
const
|
|
14118
|
-
const
|
|
14119
|
-
const
|
|
14120
|
-
const phasesDir =
|
|
14208
|
+
const planningDir = planningPath(cwd);
|
|
14209
|
+
const projectPath = planningPath(cwd, "PROJECT.md");
|
|
14210
|
+
const rmPath = roadmapPath(cwd);
|
|
14211
|
+
const stPath = statePath(cwd);
|
|
14212
|
+
const cfgPath = configPath(cwd);
|
|
14213
|
+
const phasesDir = phasesPath(cwd);
|
|
14121
14214
|
const errors = [];
|
|
14122
14215
|
const warnings = [];
|
|
14123
14216
|
const info = [];
|
|
@@ -14153,21 +14246,20 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14153
14246
|
"## Requirements"
|
|
14154
14247
|
]) if (!content.includes(section)) addIssue("warning", "W001", `PROJECT.md missing section: ${section}`, "Add section manually");
|
|
14155
14248
|
}
|
|
14156
|
-
if (!node_fs.default.existsSync(
|
|
14157
|
-
if (!node_fs.default.existsSync(
|
|
14249
|
+
if (!node_fs.default.existsSync(rmPath)) addIssue("error", "E003", "ROADMAP.md not found", "Run /maxsim:new-milestone to create roadmap");
|
|
14250
|
+
if (!node_fs.default.existsSync(stPath)) {
|
|
14158
14251
|
addIssue("error", "E004", "STATE.md not found", "Run /maxsim:health --repair to regenerate", true);
|
|
14159
14252
|
repairs.push("regenerateState");
|
|
14160
14253
|
} else {
|
|
14161
|
-
const phaseRefs = [...node_fs.default.readFileSync(
|
|
14254
|
+
const phaseRefs = [...node_fs.default.readFileSync(stPath, "utf-8").matchAll(/[Pp]hase\s+(\d+(?:\.\d+)?)/g)].map((m) => m[1]);
|
|
14162
14255
|
const diskPhases = /* @__PURE__ */ new Set();
|
|
14163
14256
|
try {
|
|
14164
|
-
const
|
|
14165
|
-
|
|
14166
|
-
const dm = e.name.match(/^(\d+(?:\.\d+)?)/);
|
|
14257
|
+
for (const dir of listSubDirs(phasesDir)) {
|
|
14258
|
+
const dm = dir.match(/^(\d+(?:\.\d+)?)/);
|
|
14167
14259
|
if (dm) diskPhases.add(dm[1]);
|
|
14168
14260
|
}
|
|
14169
14261
|
} catch (e) {
|
|
14170
|
-
|
|
14262
|
+
debugLog(e);
|
|
14171
14263
|
}
|
|
14172
14264
|
for (const ref of phaseRefs) {
|
|
14173
14265
|
const normalizedRef = String(parseInt(ref, 10)).padStart(2, "0");
|
|
@@ -14179,11 +14271,11 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14179
14271
|
}
|
|
14180
14272
|
}
|
|
14181
14273
|
}
|
|
14182
|
-
if (!node_fs.default.existsSync(
|
|
14274
|
+
if (!node_fs.default.existsSync(cfgPath)) {
|
|
14183
14275
|
addIssue("warning", "W003", "config.json not found", "Run /maxsim:health --repair to create with defaults", true);
|
|
14184
14276
|
repairs.push("createConfig");
|
|
14185
14277
|
} else try {
|
|
14186
|
-
const rawContent = node_fs.default.readFileSync(
|
|
14278
|
+
const rawContent = node_fs.default.readFileSync(cfgPath, "utf-8");
|
|
14187
14279
|
const parsed = JSON.parse(rawContent);
|
|
14188
14280
|
const validProfiles = [
|
|
14189
14281
|
"quality",
|
|
@@ -14197,42 +14289,39 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14197
14289
|
repairs.push("resetConfig");
|
|
14198
14290
|
}
|
|
14199
14291
|
try {
|
|
14200
|
-
const
|
|
14201
|
-
for (const e of entries) if (e.isDirectory() && !e.name.match(/^\d{2}(?:\.\d+)?-[\w-]+$/)) addIssue("warning", "W005", `Phase directory "${e.name}" doesn't follow NN-name format`, "Rename to match pattern (e.g., 01-setup)");
|
|
14292
|
+
for (const dirName of listSubDirs(phasesDir)) if (!dirName.match(/^\d{2}(?:\.\d+)?-[\w-]+$/)) addIssue("warning", "W005", `Phase directory "${dirName}" doesn't follow NN-name format`, "Rename to match pattern (e.g., 01-setup)");
|
|
14202
14293
|
} catch (e) {
|
|
14203
|
-
|
|
14294
|
+
debugLog(e);
|
|
14204
14295
|
}
|
|
14205
14296
|
try {
|
|
14206
|
-
const
|
|
14207
|
-
for (const
|
|
14208
|
-
|
|
14209
|
-
const
|
|
14210
|
-
const
|
|
14211
|
-
const
|
|
14212
|
-
const summaryBases = new Set(summaries.map((s) => s.replace("-SUMMARY.md", "").replace("SUMMARY.md", "")));
|
|
14297
|
+
const orphanDirs = listSubDirs(phasesDir);
|
|
14298
|
+
for (const dirName of orphanDirs) {
|
|
14299
|
+
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dirName));
|
|
14300
|
+
const plans = phaseFiles.filter((f) => isPlanFile(f));
|
|
14301
|
+
const summaries = phaseFiles.filter((f) => isSummaryFile(f));
|
|
14302
|
+
const summaryBases = new Set(summaries.map((s) => summaryId(s)));
|
|
14213
14303
|
for (const plan of plans) {
|
|
14214
|
-
const planBase = plan
|
|
14215
|
-
if (!summaryBases.has(planBase)) addIssue("info", "I001", `${
|
|
14304
|
+
const planBase = planId(plan);
|
|
14305
|
+
if (!summaryBases.has(planBase)) addIssue("info", "I001", `${dirName}/${plan} has no SUMMARY.md`, "May be in progress");
|
|
14216
14306
|
}
|
|
14217
14307
|
}
|
|
14218
14308
|
} catch (e) {
|
|
14219
|
-
|
|
14309
|
+
debugLog(e);
|
|
14220
14310
|
}
|
|
14221
|
-
if (node_fs.default.existsSync(
|
|
14222
|
-
const roadmapContent = node_fs.default.readFileSync(
|
|
14311
|
+
if (node_fs.default.existsSync(rmPath)) {
|
|
14312
|
+
const roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14223
14313
|
const roadmapPhases = /* @__PURE__ */ new Set();
|
|
14224
14314
|
const phasePattern = getPhasePattern();
|
|
14225
14315
|
let m;
|
|
14226
14316
|
while ((m = phasePattern.exec(roadmapContent)) !== null) roadmapPhases.add(m[1]);
|
|
14227
14317
|
const diskPhases = /* @__PURE__ */ new Set();
|
|
14228
14318
|
try {
|
|
14229
|
-
const
|
|
14230
|
-
|
|
14231
|
-
const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
|
|
14319
|
+
for (const dir of listSubDirs(phasesDir)) {
|
|
14320
|
+
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
|
|
14232
14321
|
if (dm) diskPhases.add(dm[1]);
|
|
14233
14322
|
}
|
|
14234
14323
|
} catch (e) {
|
|
14235
|
-
|
|
14324
|
+
debugLog(e);
|
|
14236
14325
|
}
|
|
14237
14326
|
for (const p of roadmapPhases) {
|
|
14238
14327
|
const padded = String(parseInt(p, 10)).padStart(2, "0");
|
|
@@ -14248,7 +14337,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14248
14337
|
switch (repair) {
|
|
14249
14338
|
case "createConfig":
|
|
14250
14339
|
case "resetConfig":
|
|
14251
|
-
node_fs.default.writeFileSync(
|
|
14340
|
+
node_fs.default.writeFileSync(cfgPath, JSON.stringify({
|
|
14252
14341
|
model_profile: "balanced",
|
|
14253
14342
|
commit_docs: true,
|
|
14254
14343
|
search_gitignored: false,
|
|
@@ -14265,9 +14354,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14265
14354
|
});
|
|
14266
14355
|
break;
|
|
14267
14356
|
case "regenerateState": {
|
|
14268
|
-
if (node_fs.default.existsSync(
|
|
14269
|
-
const backupPath = `${
|
|
14270
|
-
node_fs.default.copyFileSync(
|
|
14357
|
+
if (node_fs.default.existsSync(stPath)) {
|
|
14358
|
+
const backupPath = `${stPath}.bak-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
|
|
14359
|
+
node_fs.default.copyFileSync(stPath, backupPath);
|
|
14271
14360
|
repairActions.push({
|
|
14272
14361
|
action: "backupState",
|
|
14273
14362
|
success: true,
|
|
@@ -14283,8 +14372,8 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14283
14372
|
stateContent += `**Current phase:** (determining...)\n`;
|
|
14284
14373
|
stateContent += `**Status:** Resuming\n\n`;
|
|
14285
14374
|
stateContent += `## Session Log\n\n`;
|
|
14286
|
-
stateContent += `- ${(
|
|
14287
|
-
node_fs.default.writeFileSync(
|
|
14375
|
+
stateContent += `- ${todayISO()}: STATE.md regenerated by /maxsim:health --repair\n`;
|
|
14376
|
+
node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
|
|
14288
14377
|
repairActions.push({
|
|
14289
14378
|
action: repair,
|
|
14290
14379
|
success: true,
|
|
@@ -14323,10 +14412,170 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14323
14412
|
*
|
|
14324
14413
|
* Ported from maxsim/bin/lib/phase.cjs
|
|
14325
14414
|
*/
|
|
14415
|
+
function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
14416
|
+
const today = todayISO();
|
|
14417
|
+
node_fs.default.writeFileSync(node_path.default.join(dirPath, `${phaseId}-CONTEXT.md`), `# Phase ${phaseId} Context: ${name}\n\n**Created:** ${today}\n**Phase goal:** [To be defined during /maxsim:discuss-phase]\n\n---\n\n_Context will be populated by /maxsim:discuss-phase_\n`);
|
|
14418
|
+
node_fs.default.writeFileSync(node_path.default.join(dirPath, `${phaseId}-RESEARCH.md`), `# Phase ${phaseId}: ${name} - Research\n\n**Researched:** Not yet\n**Domain:** TBD\n**Confidence:** TBD\n\n---\n\n_Research will be populated by /maxsim:research-phase_\n`);
|
|
14419
|
+
}
|
|
14420
|
+
function phaseAddCore(cwd, description, options) {
|
|
14421
|
+
const rmPath = roadmapPath(cwd);
|
|
14422
|
+
if (!node_fs.default.existsSync(rmPath)) throw new Error("ROADMAP.md not found");
|
|
14423
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14424
|
+
const slug = generateSlugInternal(description);
|
|
14425
|
+
const phasePattern = getPhasePattern();
|
|
14426
|
+
let maxPhase = 0;
|
|
14427
|
+
let m;
|
|
14428
|
+
while ((m = phasePattern.exec(content)) !== null) {
|
|
14429
|
+
const num = parseInt(m[1], 10);
|
|
14430
|
+
if (num > maxPhase) maxPhase = num;
|
|
14431
|
+
}
|
|
14432
|
+
const newPhaseNum = maxPhase + 1;
|
|
14433
|
+
const paddedNum = String(newPhaseNum).padStart(2, "0");
|
|
14434
|
+
const dirName = `${paddedNum}-${slug}`;
|
|
14435
|
+
const dirPath = planningPath(cwd, "phases", dirName);
|
|
14436
|
+
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
14437
|
+
node_fs.default.writeFileSync(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14438
|
+
if (options?.includeStubs) scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
14439
|
+
const phaseEntry = `\n### Phase ${newPhaseNum}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${maxPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /maxsim:plan-phase ${newPhaseNum} to break down)\n`;
|
|
14440
|
+
let updatedContent;
|
|
14441
|
+
const lastSeparator = content.lastIndexOf("\n---");
|
|
14442
|
+
if (lastSeparator > 0) updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
|
|
14443
|
+
else updatedContent = content + phaseEntry;
|
|
14444
|
+
node_fs.default.writeFileSync(rmPath, updatedContent, "utf-8");
|
|
14445
|
+
return {
|
|
14446
|
+
phase_number: newPhaseNum,
|
|
14447
|
+
padded: paddedNum,
|
|
14448
|
+
slug,
|
|
14449
|
+
directory: `.planning/phases/${dirName}`,
|
|
14450
|
+
description
|
|
14451
|
+
};
|
|
14452
|
+
}
|
|
14453
|
+
function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
14454
|
+
const rmPath = roadmapPath(cwd);
|
|
14455
|
+
if (!node_fs.default.existsSync(rmPath)) throw new Error("ROADMAP.md not found");
|
|
14456
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14457
|
+
const slug = generateSlugInternal(description);
|
|
14458
|
+
const afterPhaseEscaped = "0*" + normalizePhaseName(afterPhase).replace(/^0+/, "").replace(/\./g, "\\.");
|
|
14459
|
+
if (!getPhasePattern(afterPhaseEscaped, "i").test(content)) throw new Error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
14460
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14461
|
+
const normalizedBase = normalizePhaseName(afterPhase);
|
|
14462
|
+
const existingDecimals = [];
|
|
14463
|
+
try {
|
|
14464
|
+
const dirs = listSubDirs(phasesDirPath);
|
|
14465
|
+
const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
|
|
14466
|
+
for (const dir of dirs) {
|
|
14467
|
+
const dm = dir.match(decimalPattern);
|
|
14468
|
+
if (dm) existingDecimals.push(parseInt(dm[1], 10));
|
|
14469
|
+
}
|
|
14470
|
+
} catch (e) {
|
|
14471
|
+
debugLog(e);
|
|
14472
|
+
}
|
|
14473
|
+
const decimalPhase = `${normalizedBase}.${existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1}`;
|
|
14474
|
+
const dirName = `${decimalPhase}-${slug}`;
|
|
14475
|
+
const dirPath = planningPath(cwd, "phases", dirName);
|
|
14476
|
+
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
14477
|
+
node_fs.default.writeFileSync(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14478
|
+
if (options?.includeStubs) scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
14479
|
+
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /maxsim:plan-phase ${decimalPhase} to break down)\n`;
|
|
14480
|
+
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, "i");
|
|
14481
|
+
const headerMatch = content.match(headerPattern);
|
|
14482
|
+
if (!headerMatch) throw new Error(`Could not find Phase ${afterPhase} header`);
|
|
14483
|
+
const headerIdx = content.indexOf(headerMatch[0]);
|
|
14484
|
+
const nextPhaseMatch = content.slice(headerIdx + headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
14485
|
+
let insertIdx;
|
|
14486
|
+
if (nextPhaseMatch) insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
14487
|
+
else insertIdx = content.length;
|
|
14488
|
+
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
14489
|
+
node_fs.default.writeFileSync(rmPath, updatedContent, "utf-8");
|
|
14490
|
+
return {
|
|
14491
|
+
phase_number: decimalPhase,
|
|
14492
|
+
after_phase: afterPhase,
|
|
14493
|
+
slug,
|
|
14494
|
+
directory: `.planning/phases/${dirName}`,
|
|
14495
|
+
description
|
|
14496
|
+
};
|
|
14497
|
+
}
|
|
14498
|
+
function phaseCompleteCore(cwd, phaseNum) {
|
|
14499
|
+
const rmPath = roadmapPath(cwd);
|
|
14500
|
+
const stPath = statePath(cwd);
|
|
14501
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14502
|
+
const today = todayISO();
|
|
14503
|
+
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
14504
|
+
if (!phaseInfo) throw new Error(`Phase ${phaseNum} not found`);
|
|
14505
|
+
const planCount = phaseInfo.plans.length;
|
|
14506
|
+
const summaryCount = phaseInfo.summaries.length;
|
|
14507
|
+
let requirementsUpdated = false;
|
|
14508
|
+
if (node_fs.default.existsSync(rmPath)) {
|
|
14509
|
+
let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14510
|
+
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${escapePhaseNum(phaseNum)}[:\\s][^\\n]*)`, "i");
|
|
14511
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
14512
|
+
const phaseEscaped = escapePhaseNum(phaseNum);
|
|
14513
|
+
const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i");
|
|
14514
|
+
roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
|
|
14515
|
+
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i");
|
|
14516
|
+
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
14517
|
+
node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
|
|
14518
|
+
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
14519
|
+
if (node_fs.default.existsSync(reqPath)) {
|
|
14520
|
+
const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${escapePhaseNum(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, "i"));
|
|
14521
|
+
if (reqMatch) {
|
|
14522
|
+
const reqIds = reqMatch[1].replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
|
|
14523
|
+
let reqContent = node_fs.default.readFileSync(reqPath, "utf-8");
|
|
14524
|
+
for (const reqId of reqIds) {
|
|
14525
|
+
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, "gi"), "$1x$2");
|
|
14526
|
+
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, "gi"), "$1 Complete $2");
|
|
14527
|
+
}
|
|
14528
|
+
node_fs.default.writeFileSync(reqPath, reqContent, "utf-8");
|
|
14529
|
+
requirementsUpdated = true;
|
|
14530
|
+
}
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
let nextPhaseNum = null;
|
|
14534
|
+
let nextPhaseName = null;
|
|
14535
|
+
let isLastPhase = true;
|
|
14536
|
+
try {
|
|
14537
|
+
const dirs = listSubDirs(phasesDirPath, true);
|
|
14538
|
+
for (const dir of dirs) {
|
|
14539
|
+
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
14540
|
+
if (dm) {
|
|
14541
|
+
if (comparePhaseNum(dm[1], phaseNum) > 0) {
|
|
14542
|
+
nextPhaseNum = dm[1];
|
|
14543
|
+
nextPhaseName = dm[2] || null;
|
|
14544
|
+
isLastPhase = false;
|
|
14545
|
+
break;
|
|
14546
|
+
}
|
|
14547
|
+
}
|
|
14548
|
+
}
|
|
14549
|
+
} catch (e) {
|
|
14550
|
+
debugLog(e);
|
|
14551
|
+
}
|
|
14552
|
+
if (node_fs.default.existsSync(stPath)) {
|
|
14553
|
+
let stateContent = node_fs.default.readFileSync(stPath, "utf-8");
|
|
14554
|
+
stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
|
|
14555
|
+
if (nextPhaseName) stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, " ")}`);
|
|
14556
|
+
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? "Milestone complete" : "Ready to plan"}`);
|
|
14557
|
+
stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
|
|
14558
|
+
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
14559
|
+
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ""}`);
|
|
14560
|
+
node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
|
|
14561
|
+
}
|
|
14562
|
+
return {
|
|
14563
|
+
completed_phase: phaseNum,
|
|
14564
|
+
phase_name: phaseInfo.phase_name,
|
|
14565
|
+
plans_executed: `${summaryCount}/${planCount}`,
|
|
14566
|
+
next_phase: nextPhaseNum,
|
|
14567
|
+
next_phase_name: nextPhaseName,
|
|
14568
|
+
is_last_phase: isLastPhase,
|
|
14569
|
+
date: today,
|
|
14570
|
+
roadmap_updated: node_fs.default.existsSync(rmPath),
|
|
14571
|
+
state_updated: node_fs.default.existsSync(stPath),
|
|
14572
|
+
requirements_updated: requirementsUpdated
|
|
14573
|
+
};
|
|
14574
|
+
}
|
|
14326
14575
|
function cmdPhasesList(cwd, options, raw) {
|
|
14327
|
-
const
|
|
14576
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14328
14577
|
const { type, phase, includeArchived } = options;
|
|
14329
|
-
if (!node_fs.default.existsSync(
|
|
14578
|
+
if (!node_fs.default.existsSync(phasesDirPath)) {
|
|
14330
14579
|
if (type) output({
|
|
14331
14580
|
files: [],
|
|
14332
14581
|
count: 0
|
|
@@ -14338,7 +14587,7 @@ function cmdPhasesList(cwd, options, raw) {
|
|
|
14338
14587
|
return;
|
|
14339
14588
|
}
|
|
14340
14589
|
try {
|
|
14341
|
-
let dirs =
|
|
14590
|
+
let dirs = listSubDirs(phasesDirPath);
|
|
14342
14591
|
if (includeArchived) {
|
|
14343
14592
|
const archived = getArchivedPhaseDirs(cwd);
|
|
14344
14593
|
for (const a of archived) dirs.push(`${a.name} [${a.milestone}]`);
|
|
@@ -14361,11 +14610,11 @@ function cmdPhasesList(cwd, options, raw) {
|
|
|
14361
14610
|
if (type) {
|
|
14362
14611
|
const files = [];
|
|
14363
14612
|
for (const dir of dirs) {
|
|
14364
|
-
const dirPath = node_path.default.join(
|
|
14613
|
+
const dirPath = node_path.default.join(phasesDirPath, dir);
|
|
14365
14614
|
const dirFiles = node_fs.default.readdirSync(dirPath);
|
|
14366
14615
|
let filtered;
|
|
14367
|
-
if (type === "plans") filtered = dirFiles.filter(
|
|
14368
|
-
else if (type === "summaries") filtered = dirFiles.filter(
|
|
14616
|
+
if (type === "plans") filtered = dirFiles.filter(isPlanFile);
|
|
14617
|
+
else if (type === "summaries") filtered = dirFiles.filter(isSummaryFile);
|
|
14369
14618
|
else filtered = dirFiles;
|
|
14370
14619
|
files.push(...filtered.sort());
|
|
14371
14620
|
}
|
|
@@ -14381,13 +14630,14 @@ function cmdPhasesList(cwd, options, raw) {
|
|
|
14381
14630
|
count: dirs.length
|
|
14382
14631
|
}, raw, dirs.join("\n"));
|
|
14383
14632
|
} catch (e) {
|
|
14633
|
+
rethrowCliSignals(e);
|
|
14384
14634
|
error("Failed to list phases: " + e.message);
|
|
14385
14635
|
}
|
|
14386
14636
|
}
|
|
14387
14637
|
function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
14388
|
-
const
|
|
14638
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14389
14639
|
const normalized = normalizePhaseName(basePhase);
|
|
14390
|
-
if (!node_fs.default.existsSync(
|
|
14640
|
+
if (!node_fs.default.existsSync(phasesDirPath)) {
|
|
14391
14641
|
output({
|
|
14392
14642
|
found: false,
|
|
14393
14643
|
base_phase: normalized,
|
|
@@ -14397,7 +14647,7 @@ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
|
14397
14647
|
return;
|
|
14398
14648
|
}
|
|
14399
14649
|
try {
|
|
14400
|
-
const dirs =
|
|
14650
|
+
const dirs = listSubDirs(phasesDirPath);
|
|
14401
14651
|
const baseExists = dirs.some((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
14402
14652
|
const decimalPattern = new RegExp(`^${normalized}\\.(\\d+)`);
|
|
14403
14653
|
const existingDecimals = [];
|
|
@@ -14421,12 +14671,13 @@ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
|
14421
14671
|
existing: existingDecimals
|
|
14422
14672
|
}, raw, nextDecimal);
|
|
14423
14673
|
} catch (e) {
|
|
14674
|
+
rethrowCliSignals(e);
|
|
14424
14675
|
error("Failed to calculate next decimal phase: " + e.message);
|
|
14425
14676
|
}
|
|
14426
14677
|
}
|
|
14427
14678
|
function cmdFindPhase(cwd, phase, raw) {
|
|
14428
14679
|
if (!phase) error("phase identifier required");
|
|
14429
|
-
const
|
|
14680
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14430
14681
|
const normalized = normalizePhaseName(phase);
|
|
14431
14682
|
const notFound = {
|
|
14432
14683
|
found: false,
|
|
@@ -14437,7 +14688,7 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
14437
14688
|
summaries: []
|
|
14438
14689
|
};
|
|
14439
14690
|
try {
|
|
14440
|
-
const match =
|
|
14691
|
+
const match = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized));
|
|
14441
14692
|
if (!match) {
|
|
14442
14693
|
output(notFound, raw, "");
|
|
14443
14694
|
return;
|
|
@@ -14445,10 +14696,10 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
14445
14696
|
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
14446
14697
|
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
14447
14698
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
14448
|
-
const phaseDir = node_path.default.join(
|
|
14699
|
+
const phaseDir = node_path.default.join(phasesDirPath, match);
|
|
14449
14700
|
const phaseFiles = node_fs.default.readdirSync(phaseDir);
|
|
14450
|
-
const plans = phaseFiles.filter(
|
|
14451
|
-
const summaries = phaseFiles.filter(
|
|
14701
|
+
const plans = phaseFiles.filter(isPlanFile).sort();
|
|
14702
|
+
const summaries = phaseFiles.filter(isSummaryFile).sort();
|
|
14452
14703
|
const result = {
|
|
14453
14704
|
found: true,
|
|
14454
14705
|
directory: node_path.default.join(".planning", "phases", match),
|
|
@@ -14458,20 +14709,21 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
14458
14709
|
summaries
|
|
14459
14710
|
};
|
|
14460
14711
|
output(result, raw, result.directory);
|
|
14461
|
-
} catch {
|
|
14712
|
+
} catch (e) {
|
|
14713
|
+
rethrowCliSignals(e);
|
|
14462
14714
|
output(notFound, raw, "");
|
|
14463
14715
|
}
|
|
14464
14716
|
}
|
|
14465
14717
|
function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
14466
14718
|
if (!phase) error("phase required for phase-plan-index");
|
|
14467
|
-
const
|
|
14719
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14468
14720
|
const normalized = normalizePhaseName(phase);
|
|
14469
14721
|
let phaseDir = null;
|
|
14470
14722
|
try {
|
|
14471
|
-
const match =
|
|
14472
|
-
if (match) phaseDir = node_path.default.join(
|
|
14723
|
+
const match = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized));
|
|
14724
|
+
if (match) phaseDir = node_path.default.join(phasesDirPath, match);
|
|
14473
14725
|
} catch (e) {
|
|
14474
|
-
|
|
14726
|
+
debugLog(e);
|
|
14475
14727
|
}
|
|
14476
14728
|
if (!phaseDir) {
|
|
14477
14729
|
output({
|
|
@@ -14485,15 +14737,15 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14485
14737
|
return;
|
|
14486
14738
|
}
|
|
14487
14739
|
const phaseFiles = node_fs.default.readdirSync(phaseDir);
|
|
14488
|
-
const planFiles = phaseFiles.filter(
|
|
14489
|
-
const summaryFiles = phaseFiles.filter(
|
|
14490
|
-
const completedPlanIds = new Set(summaryFiles.map(
|
|
14740
|
+
const planFiles = phaseFiles.filter(isPlanFile).sort();
|
|
14741
|
+
const summaryFiles = phaseFiles.filter(isSummaryFile);
|
|
14742
|
+
const completedPlanIds = new Set(summaryFiles.map(summaryId));
|
|
14491
14743
|
const plans = [];
|
|
14492
14744
|
const waves = {};
|
|
14493
14745
|
const incomplete = [];
|
|
14494
14746
|
let hasCheckpoints = false;
|
|
14495
14747
|
for (const planFile of planFiles) {
|
|
14496
|
-
const
|
|
14748
|
+
const id = planId(planFile);
|
|
14497
14749
|
const planPath = node_path.default.join(phaseDir, planFile);
|
|
14498
14750
|
const content = node_fs.default.readFileSync(planPath, "utf-8");
|
|
14499
14751
|
const fm = extractFrontmatter(content);
|
|
@@ -14504,10 +14756,10 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14504
14756
|
if (!autonomous) hasCheckpoints = true;
|
|
14505
14757
|
let filesModified = [];
|
|
14506
14758
|
if (fm["files-modified"]) filesModified = Array.isArray(fm["files-modified"]) ? fm["files-modified"] : [fm["files-modified"]];
|
|
14507
|
-
const hasSummary = completedPlanIds.has(
|
|
14508
|
-
if (!hasSummary) incomplete.push(
|
|
14759
|
+
const hasSummary = completedPlanIds.has(id);
|
|
14760
|
+
if (!hasSummary) incomplete.push(id);
|
|
14509
14761
|
const plan = {
|
|
14510
|
-
id
|
|
14762
|
+
id,
|
|
14511
14763
|
wave,
|
|
14512
14764
|
autonomous,
|
|
14513
14765
|
objective: fm.objective || null,
|
|
@@ -14518,7 +14770,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14518
14770
|
plans.push(plan);
|
|
14519
14771
|
const waveKey = String(wave);
|
|
14520
14772
|
if (!waves[waveKey]) waves[waveKey] = [];
|
|
14521
|
-
waves[waveKey].push(
|
|
14773
|
+
waves[waveKey].push(id);
|
|
14522
14774
|
}
|
|
14523
14775
|
output({
|
|
14524
14776
|
phase: normalized,
|
|
@@ -14530,102 +14782,56 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14530
14782
|
}
|
|
14531
14783
|
function cmdPhaseAdd(cwd, description, raw) {
|
|
14532
14784
|
if (!description) error("description required for phase add");
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14785
|
+
try {
|
|
14786
|
+
const result = phaseAddCore(cwd, description, { includeStubs: false });
|
|
14787
|
+
output({
|
|
14788
|
+
phase_number: result.phase_number,
|
|
14789
|
+
padded: result.padded,
|
|
14790
|
+
name: result.description,
|
|
14791
|
+
slug: result.slug,
|
|
14792
|
+
directory: result.directory
|
|
14793
|
+
}, raw, result.padded);
|
|
14794
|
+
} catch (e) {
|
|
14795
|
+
rethrowCliSignals(e);
|
|
14796
|
+
error(e.message);
|
|
14543
14797
|
}
|
|
14544
|
-
const newPhaseNum = maxPhase + 1;
|
|
14545
|
-
const paddedNum = String(newPhaseNum).padStart(2, "0");
|
|
14546
|
-
const dirName = `${paddedNum}-${slug}`;
|
|
14547
|
-
const dirPath = node_path.default.join(cwd, ".planning", "phases", dirName);
|
|
14548
|
-
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
14549
|
-
node_fs.default.writeFileSync(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14550
|
-
const phaseEntry = `\n### Phase ${newPhaseNum}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${maxPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /maxsim:plan-phase ${newPhaseNum} to break down)\n`;
|
|
14551
|
-
let updatedContent;
|
|
14552
|
-
const lastSeparator = content.lastIndexOf("\n---");
|
|
14553
|
-
if (lastSeparator > 0) updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
|
|
14554
|
-
else updatedContent = content + phaseEntry;
|
|
14555
|
-
node_fs.default.writeFileSync(roadmapPath, updatedContent, "utf-8");
|
|
14556
|
-
output({
|
|
14557
|
-
phase_number: newPhaseNum,
|
|
14558
|
-
padded: paddedNum,
|
|
14559
|
-
name: description,
|
|
14560
|
-
slug,
|
|
14561
|
-
directory: `.planning/phases/${dirName}`
|
|
14562
|
-
}, raw, paddedNum);
|
|
14563
14798
|
}
|
|
14564
14799
|
function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
14565
14800
|
if (!afterPhase || !description) error("after-phase and description required for phase insert");
|
|
14566
|
-
const roadmapPath = node_path.default.join(cwd, ".planning", "ROADMAP.md");
|
|
14567
|
-
if (!node_fs.default.existsSync(roadmapPath)) error("ROADMAP.md not found");
|
|
14568
|
-
const content = node_fs.default.readFileSync(roadmapPath, "utf-8");
|
|
14569
|
-
const slug = generateSlugInternal(description);
|
|
14570
|
-
const afterPhaseEscaped = "0*" + normalizePhaseName(afterPhase).replace(/^0+/, "").replace(/\./g, "\\.");
|
|
14571
|
-
if (!getPhasePattern(afterPhaseEscaped, "i").test(content)) error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
14572
|
-
const phasesDir = node_path.default.join(cwd, ".planning", "phases");
|
|
14573
|
-
const normalizedBase = normalizePhaseName(afterPhase);
|
|
14574
|
-
const existingDecimals = [];
|
|
14575
14801
|
try {
|
|
14576
|
-
const
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14802
|
+
const result = phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
14803
|
+
output({
|
|
14804
|
+
phase_number: result.phase_number,
|
|
14805
|
+
after_phase: result.after_phase,
|
|
14806
|
+
name: result.description,
|
|
14807
|
+
slug: result.slug,
|
|
14808
|
+
directory: result.directory
|
|
14809
|
+
}, raw, result.phase_number);
|
|
14582
14810
|
} catch (e) {
|
|
14583
|
-
|
|
14811
|
+
rethrowCliSignals(e);
|
|
14812
|
+
error(e.message);
|
|
14584
14813
|
}
|
|
14585
|
-
const decimalPhase = `${normalizedBase}.${existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1}`;
|
|
14586
|
-
const dirName = `${decimalPhase}-${slug}`;
|
|
14587
|
-
const dirPath = node_path.default.join(cwd, ".planning", "phases", dirName);
|
|
14588
|
-
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
14589
|
-
node_fs.default.writeFileSync(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14590
|
-
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /maxsim:plan-phase ${decimalPhase} to break down)\n`;
|
|
14591
|
-
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, "i");
|
|
14592
|
-
const headerMatch = content.match(headerPattern);
|
|
14593
|
-
if (!headerMatch) error(`Could not find Phase ${afterPhase} header`);
|
|
14594
|
-
const headerIdx = content.indexOf(headerMatch[0]);
|
|
14595
|
-
const nextPhaseMatch = content.slice(headerIdx + headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
14596
|
-
let insertIdx;
|
|
14597
|
-
if (nextPhaseMatch) insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
14598
|
-
else insertIdx = content.length;
|
|
14599
|
-
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
14600
|
-
node_fs.default.writeFileSync(roadmapPath, updatedContent, "utf-8");
|
|
14601
|
-
output({
|
|
14602
|
-
phase_number: decimalPhase,
|
|
14603
|
-
after_phase: afterPhase,
|
|
14604
|
-
name: description,
|
|
14605
|
-
slug,
|
|
14606
|
-
directory: `.planning/phases/${dirName}`
|
|
14607
|
-
}, raw, decimalPhase);
|
|
14608
14814
|
}
|
|
14609
14815
|
function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
14610
14816
|
if (!targetPhase) error("phase number required for phase remove");
|
|
14611
|
-
const
|
|
14612
|
-
const
|
|
14817
|
+
const rmPath = roadmapPath(cwd);
|
|
14818
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14613
14819
|
const force = options.force || false;
|
|
14614
|
-
if (!node_fs.default.existsSync(
|
|
14820
|
+
if (!node_fs.default.existsSync(rmPath)) error("ROADMAP.md not found");
|
|
14615
14821
|
const normalized = normalizePhaseName(targetPhase);
|
|
14616
14822
|
const isDecimal = targetPhase.includes(".");
|
|
14617
14823
|
let targetDir = null;
|
|
14618
14824
|
try {
|
|
14619
|
-
targetDir =
|
|
14825
|
+
targetDir = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized + "-") || d === normalized) || null;
|
|
14620
14826
|
} catch (e) {
|
|
14621
|
-
|
|
14827
|
+
debugLog(e);
|
|
14622
14828
|
}
|
|
14623
14829
|
if (targetDir && !force) {
|
|
14624
|
-
const targetPath = node_path.default.join(
|
|
14625
|
-
const summaries = node_fs.default.readdirSync(targetPath).filter(
|
|
14830
|
+
const targetPath = node_path.default.join(phasesDirPath, targetDir);
|
|
14831
|
+
const summaries = node_fs.default.readdirSync(targetPath).filter(isSummaryFile);
|
|
14626
14832
|
if (summaries.length > 0) error(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
|
|
14627
14833
|
}
|
|
14628
|
-
if (targetDir) node_fs.default.rmSync(node_path.default.join(
|
|
14834
|
+
if (targetDir) node_fs.default.rmSync(node_path.default.join(phasesDirPath, targetDir), {
|
|
14629
14835
|
recursive: true,
|
|
14630
14836
|
force: true
|
|
14631
14837
|
});
|
|
@@ -14636,7 +14842,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14636
14842
|
const baseInt = baseParts[0];
|
|
14637
14843
|
const removedDecimal = parseInt(baseParts[1], 10);
|
|
14638
14844
|
try {
|
|
14639
|
-
const dirs =
|
|
14845
|
+
const dirs = listSubDirs(phasesDirPath, true);
|
|
14640
14846
|
const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
|
|
14641
14847
|
const toRename = [];
|
|
14642
14848
|
for (const dir of dirs) {
|
|
@@ -14653,15 +14859,15 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14653
14859
|
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
14654
14860
|
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
14655
14861
|
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
14656
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14862
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, item.dir), node_path.default.join(phasesDirPath, newDirName));
|
|
14657
14863
|
renamedDirs.push({
|
|
14658
14864
|
from: item.dir,
|
|
14659
14865
|
to: newDirName
|
|
14660
14866
|
});
|
|
14661
|
-
const dirFiles = node_fs.default.readdirSync(node_path.default.join(
|
|
14867
|
+
const dirFiles = node_fs.default.readdirSync(node_path.default.join(phasesDirPath, newDirName));
|
|
14662
14868
|
for (const f of dirFiles) if (f.includes(oldPhaseId)) {
|
|
14663
14869
|
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
14664
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14870
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, newDirName, f), node_path.default.join(phasesDirPath, newDirName, newFileName));
|
|
14665
14871
|
renamedFiles.push({
|
|
14666
14872
|
from: f,
|
|
14667
14873
|
to: newFileName
|
|
@@ -14669,12 +14875,12 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14669
14875
|
}
|
|
14670
14876
|
}
|
|
14671
14877
|
} catch (e) {
|
|
14672
|
-
|
|
14878
|
+
debugLog(e);
|
|
14673
14879
|
}
|
|
14674
14880
|
} else {
|
|
14675
14881
|
const removedInt = parseInt(normalized, 10);
|
|
14676
14882
|
try {
|
|
14677
|
-
const dirs =
|
|
14883
|
+
const dirs = listSubDirs(phasesDirPath, true);
|
|
14678
14884
|
const toRename = [];
|
|
14679
14885
|
for (const dir of dirs) {
|
|
14680
14886
|
const dm = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
|
@@ -14701,15 +14907,15 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14701
14907
|
const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
|
|
14702
14908
|
const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
|
|
14703
14909
|
const newDirName = `${newPrefix}-${item.slug}`;
|
|
14704
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14910
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, item.dir), node_path.default.join(phasesDirPath, newDirName));
|
|
14705
14911
|
renamedDirs.push({
|
|
14706
14912
|
from: item.dir,
|
|
14707
14913
|
to: newDirName
|
|
14708
14914
|
});
|
|
14709
|
-
const dirFiles = node_fs.default.readdirSync(node_path.default.join(
|
|
14915
|
+
const dirFiles = node_fs.default.readdirSync(node_path.default.join(phasesDirPath, newDirName));
|
|
14710
14916
|
for (const f of dirFiles) if (f.startsWith(oldPrefix)) {
|
|
14711
14917
|
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
14712
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14918
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, newDirName, f), node_path.default.join(phasesDirPath, newDirName, newFileName));
|
|
14713
14919
|
renamedFiles.push({
|
|
14714
14920
|
from: f,
|
|
14715
14921
|
to: newFileName
|
|
@@ -14717,11 +14923,11 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14717
14923
|
}
|
|
14718
14924
|
}
|
|
14719
14925
|
} catch (e) {
|
|
14720
|
-
|
|
14926
|
+
debugLog(e);
|
|
14721
14927
|
}
|
|
14722
14928
|
}
|
|
14723
|
-
let roadmapContent = node_fs.default.readFileSync(
|
|
14724
|
-
const targetEscaped = targetPhase
|
|
14929
|
+
let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14930
|
+
const targetEscaped = escapePhaseNum(targetPhase);
|
|
14725
14931
|
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`, "i");
|
|
14726
14932
|
roadmapContent = roadmapContent.replace(sectionPattern, "");
|
|
14727
14933
|
const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`, "gi");
|
|
@@ -14743,10 +14949,10 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14743
14949
|
roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, "gi"), `$1${newStr}`);
|
|
14744
14950
|
}
|
|
14745
14951
|
}
|
|
14746
|
-
node_fs.default.writeFileSync(
|
|
14747
|
-
const
|
|
14748
|
-
if (node_fs.default.existsSync(
|
|
14749
|
-
let stateContent = node_fs.default.readFileSync(
|
|
14952
|
+
node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
|
|
14953
|
+
const stPath = statePath(cwd);
|
|
14954
|
+
if (node_fs.default.existsSync(stPath)) {
|
|
14955
|
+
let stateContent = node_fs.default.readFileSync(stPath, "utf-8");
|
|
14750
14956
|
const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
|
|
14751
14957
|
const totalMatch = stateContent.match(totalPattern);
|
|
14752
14958
|
if (totalMatch) {
|
|
@@ -14759,7 +14965,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14759
14965
|
const oldTotal = parseInt(ofMatch[2], 10);
|
|
14760
14966
|
stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
|
|
14761
14967
|
}
|
|
14762
|
-
node_fs.default.writeFileSync(
|
|
14968
|
+
node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
|
|
14763
14969
|
}
|
|
14764
14970
|
output({
|
|
14765
14971
|
removed: targetPhase,
|
|
@@ -14767,84 +14973,28 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14767
14973
|
renamed_directories: renamedDirs,
|
|
14768
14974
|
renamed_files: renamedFiles,
|
|
14769
14975
|
roadmap_updated: true,
|
|
14770
|
-
state_updated: node_fs.default.existsSync(
|
|
14976
|
+
state_updated: node_fs.default.existsSync(stPath)
|
|
14771
14977
|
}, raw);
|
|
14772
14978
|
}
|
|
14773
14979
|
function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
14774
14980
|
if (!phaseNum) error("phase number required for phase complete");
|
|
14775
|
-
const roadmapPath = node_path.default.join(cwd, ".planning", "ROADMAP.md");
|
|
14776
|
-
const statePath = node_path.default.join(cwd, ".planning", "STATE.md");
|
|
14777
|
-
const phasesDir = node_path.default.join(cwd, ".planning", "phases");
|
|
14778
|
-
normalizePhaseName(phaseNum);
|
|
14779
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
14780
|
-
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
14781
|
-
if (!phaseInfo) error(`Phase ${phaseNum} not found`);
|
|
14782
|
-
const planCount = phaseInfo.plans.length;
|
|
14783
|
-
const summaryCount = phaseInfo.summaries.length;
|
|
14784
|
-
if (node_fs.default.existsSync(roadmapPath)) {
|
|
14785
|
-
let roadmapContent = node_fs.default.readFileSync(roadmapPath, "utf-8");
|
|
14786
|
-
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}[:\\s][^\\n]*)`, "i");
|
|
14787
|
-
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
14788
|
-
const phaseEscaped = phaseNum.replace(".", "\\.");
|
|
14789
|
-
const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i");
|
|
14790
|
-
roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
|
|
14791
|
-
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i");
|
|
14792
|
-
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
14793
|
-
node_fs.default.writeFileSync(roadmapPath, roadmapContent, "utf-8");
|
|
14794
|
-
const reqPath = node_path.default.join(cwd, ".planning", "REQUIREMENTS.md");
|
|
14795
|
-
if (node_fs.default.existsSync(reqPath)) {
|
|
14796
|
-
const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${phaseNum.replace(".", "\\.")}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, "i"));
|
|
14797
|
-
if (reqMatch) {
|
|
14798
|
-
const reqIds = reqMatch[1].replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
|
|
14799
|
-
let reqContent = node_fs.default.readFileSync(reqPath, "utf-8");
|
|
14800
|
-
for (const reqId of reqIds) {
|
|
14801
|
-
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, "gi"), "$1x$2");
|
|
14802
|
-
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, "gi"), "$1 Complete $2");
|
|
14803
|
-
}
|
|
14804
|
-
node_fs.default.writeFileSync(reqPath, reqContent, "utf-8");
|
|
14805
|
-
}
|
|
14806
|
-
}
|
|
14807
|
-
}
|
|
14808
|
-
let nextPhaseNum = null;
|
|
14809
|
-
let nextPhaseName = null;
|
|
14810
|
-
let isLastPhase = true;
|
|
14811
14981
|
try {
|
|
14812
|
-
const
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
}
|
|
14982
|
+
const result = phaseCompleteCore(cwd, phaseNum);
|
|
14983
|
+
output({
|
|
14984
|
+
completed_phase: result.completed_phase,
|
|
14985
|
+
phase_name: result.phase_name,
|
|
14986
|
+
plans_executed: result.plans_executed,
|
|
14987
|
+
next_phase: result.next_phase,
|
|
14988
|
+
next_phase_name: result.next_phase_name,
|
|
14989
|
+
is_last_phase: result.is_last_phase,
|
|
14990
|
+
date: result.date,
|
|
14991
|
+
roadmap_updated: result.roadmap_updated,
|
|
14992
|
+
state_updated: result.state_updated
|
|
14993
|
+
}, raw);
|
|
14824
14994
|
} catch (e) {
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
if (node_fs.default.existsSync(statePath)) {
|
|
14828
|
-
let stateContent = node_fs.default.readFileSync(statePath, "utf-8");
|
|
14829
|
-
stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
|
|
14830
|
-
if (nextPhaseName) stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, " ")}`);
|
|
14831
|
-
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? "Milestone complete" : "Ready to plan"}`);
|
|
14832
|
-
stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
|
|
14833
|
-
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
14834
|
-
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ""}`);
|
|
14835
|
-
node_fs.default.writeFileSync(statePath, stateContent, "utf-8");
|
|
14995
|
+
rethrowCliSignals(e);
|
|
14996
|
+
error(e.message);
|
|
14836
14997
|
}
|
|
14837
|
-
output({
|
|
14838
|
-
completed_phase: phaseNum,
|
|
14839
|
-
phase_name: phaseInfo.phase_name,
|
|
14840
|
-
plans_executed: `${summaryCount}/${planCount}`,
|
|
14841
|
-
next_phase: nextPhaseNum,
|
|
14842
|
-
next_phase_name: nextPhaseName,
|
|
14843
|
-
is_last_phase: isLastPhase,
|
|
14844
|
-
date: today,
|
|
14845
|
-
roadmap_updated: node_fs.default.existsSync(roadmapPath),
|
|
14846
|
-
state_updated: node_fs.default.existsSync(statePath)
|
|
14847
|
-
}, raw);
|
|
14848
14998
|
}
|
|
14849
14999
|
|
|
14850
15000
|
//#endregion
|
|
@@ -14902,7 +15052,7 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
|
14902
15052
|
return;
|
|
14903
15053
|
}
|
|
14904
15054
|
const padded = normalizePhaseName(options.phase);
|
|
14905
|
-
const today = (
|
|
15055
|
+
const today = todayISO();
|
|
14906
15056
|
const phaseName = options.name || phaseInfo.phase_name || "Unnamed";
|
|
14907
15057
|
const phaseId = `${padded}-${phaseInfo.phase_slug || generateSlugInternal(phaseName)}`;
|
|
14908
15058
|
const planNum = (options.plan || "01").padStart(2, "0");
|
|
@@ -15068,6 +15218,155 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
|
15068
15218
|
}, raw, relPath);
|
|
15069
15219
|
}
|
|
15070
15220
|
|
|
15221
|
+
//#endregion
|
|
15222
|
+
//#region src/core/dashboard-launcher.ts
|
|
15223
|
+
/**
|
|
15224
|
+
* Dashboard Launcher — Shared dashboard lifecycle utilities
|
|
15225
|
+
*
|
|
15226
|
+
* Used by both cli.ts (tool-router) and install.ts (npx entry point).
|
|
15227
|
+
*/
|
|
15228
|
+
const DEFAULT_PORT = 3333;
|
|
15229
|
+
const PORT_RANGE_END = 3343;
|
|
15230
|
+
const HEALTH_TIMEOUT_MS = 1500;
|
|
15231
|
+
/**
|
|
15232
|
+
* Check if a dashboard health endpoint is responding on the given port.
|
|
15233
|
+
*/
|
|
15234
|
+
async function checkHealth(port, timeoutMs = HEALTH_TIMEOUT_MS) {
|
|
15235
|
+
try {
|
|
15236
|
+
const controller = new AbortController();
|
|
15237
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15238
|
+
const res = await fetch(`http://localhost:${port}/api/health`, { signal: controller.signal });
|
|
15239
|
+
clearTimeout(timer);
|
|
15240
|
+
if (res.ok) return (await res.json()).status === "ok";
|
|
15241
|
+
return false;
|
|
15242
|
+
} catch {
|
|
15243
|
+
return false;
|
|
15244
|
+
}
|
|
15245
|
+
}
|
|
15246
|
+
/**
|
|
15247
|
+
* Scan the port range for a running dashboard instance.
|
|
15248
|
+
* Returns the port number if found, null otherwise.
|
|
15249
|
+
*/
|
|
15250
|
+
async function findRunningDashboard(timeoutMs = HEALTH_TIMEOUT_MS) {
|
|
15251
|
+
for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port, timeoutMs)) return port;
|
|
15252
|
+
return null;
|
|
15253
|
+
}
|
|
15254
|
+
/**
|
|
15255
|
+
* Kill processes listening on the given port. Cross-platform.
|
|
15256
|
+
*/
|
|
15257
|
+
function killProcessOnPort(port) {
|
|
15258
|
+
if (process.platform === "win32") try {
|
|
15259
|
+
const lines = (0, node_child_process.execSync)(`netstat -ano | findstr :${port} | findstr LISTENING`, { encoding: "utf-8" }).trim().split("\n");
|
|
15260
|
+
const pids = /* @__PURE__ */ new Set();
|
|
15261
|
+
for (const line of lines) {
|
|
15262
|
+
const parts = line.trim().split(/\s+/);
|
|
15263
|
+
const pid = parts[parts.length - 1];
|
|
15264
|
+
if (pid && pid !== "0") pids.add(pid);
|
|
15265
|
+
}
|
|
15266
|
+
for (const pid of pids) try {
|
|
15267
|
+
(0, node_child_process.execSync)(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
|
|
15268
|
+
} catch {}
|
|
15269
|
+
} catch {}
|
|
15270
|
+
else try {
|
|
15271
|
+
(0, node_child_process.execSync)(`lsof -i :${port} -t | xargs kill -SIGTERM 2>/dev/null`, { stdio: "ignore" });
|
|
15272
|
+
} catch {}
|
|
15273
|
+
}
|
|
15274
|
+
/**
|
|
15275
|
+
* Resolve the dashboard server entry point path.
|
|
15276
|
+
* Tries: local project install, global install, @maxsim/dashboard package, monorepo walk.
|
|
15277
|
+
*/
|
|
15278
|
+
function resolveDashboardServer() {
|
|
15279
|
+
const localDashboard = node_path.default.join(process.cwd(), ".claude", "dashboard", "server.js");
|
|
15280
|
+
if (node_fs.default.existsSync(localDashboard)) return localDashboard;
|
|
15281
|
+
const globalDashboard = node_path.default.join(node_os.default.homedir(), ".claude", "dashboard", "server.js");
|
|
15282
|
+
if (node_fs.default.existsSync(globalDashboard)) return globalDashboard;
|
|
15283
|
+
try {
|
|
15284
|
+
const pkgPath = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href).resolve("@maxsim/dashboard/package.json");
|
|
15285
|
+
const pkgDir = node_path.default.dirname(pkgPath);
|
|
15286
|
+
const serverJs = node_path.default.join(pkgDir, "server.js");
|
|
15287
|
+
if (node_fs.default.existsSync(serverJs)) return serverJs;
|
|
15288
|
+
const serverTs = node_path.default.join(pkgDir, "server.ts");
|
|
15289
|
+
if (node_fs.default.existsSync(serverTs)) return serverTs;
|
|
15290
|
+
} catch {}
|
|
15291
|
+
try {
|
|
15292
|
+
let dir = node_path.default.dirname(new URL(require("url").pathToFileURL(__filename).href).pathname);
|
|
15293
|
+
if (process.platform === "win32" && dir.startsWith("/")) dir = dir.slice(1);
|
|
15294
|
+
for (let i = 0; i < 5; i++) {
|
|
15295
|
+
const candidate = node_path.default.join(dir, "packages", "dashboard", "server.ts");
|
|
15296
|
+
if (node_fs.default.existsSync(candidate)) return candidate;
|
|
15297
|
+
const candidateJs = node_path.default.join(dir, "packages", "dashboard", "server.js");
|
|
15298
|
+
if (node_fs.default.existsSync(candidateJs)) return candidateJs;
|
|
15299
|
+
dir = node_path.default.dirname(dir);
|
|
15300
|
+
}
|
|
15301
|
+
} catch {}
|
|
15302
|
+
return null;
|
|
15303
|
+
}
|
|
15304
|
+
/**
|
|
15305
|
+
* Ensure node-pty is installed in the dashboard directory.
|
|
15306
|
+
* Returns true if node-pty is available after this call.
|
|
15307
|
+
*/
|
|
15308
|
+
function ensureNodePty(serverDir) {
|
|
15309
|
+
const ptyModulePath = node_path.default.join(serverDir, "node_modules", "node-pty");
|
|
15310
|
+
if (node_fs.default.existsSync(ptyModulePath)) return true;
|
|
15311
|
+
const dashPkgPath = node_path.default.join(serverDir, "package.json");
|
|
15312
|
+
if (!node_fs.default.existsSync(dashPkgPath)) node_fs.default.writeFileSync(dashPkgPath, "{\"private\":true}\n");
|
|
15313
|
+
try {
|
|
15314
|
+
(0, node_child_process.execSync)("npm install node-pty --save-optional --no-audit --no-fund --loglevel=error", {
|
|
15315
|
+
cwd: serverDir,
|
|
15316
|
+
stdio: "inherit",
|
|
15317
|
+
timeout: 12e4
|
|
15318
|
+
});
|
|
15319
|
+
return true;
|
|
15320
|
+
} catch {
|
|
15321
|
+
return false;
|
|
15322
|
+
}
|
|
15323
|
+
}
|
|
15324
|
+
/**
|
|
15325
|
+
* Read dashboard.json config from the parent directory of the dashboard dir.
|
|
15326
|
+
*/
|
|
15327
|
+
function readDashboardConfig(serverPath) {
|
|
15328
|
+
const dashboardDir = node_path.default.dirname(serverPath);
|
|
15329
|
+
const dashboardConfigPath = node_path.default.join(node_path.default.dirname(dashboardDir), "dashboard.json");
|
|
15330
|
+
let projectCwd = process.cwd();
|
|
15331
|
+
let networkMode = false;
|
|
15332
|
+
if (node_fs.default.existsSync(dashboardConfigPath)) try {
|
|
15333
|
+
const config = JSON.parse(node_fs.default.readFileSync(dashboardConfigPath, "utf8"));
|
|
15334
|
+
if (config.projectCwd) projectCwd = config.projectCwd;
|
|
15335
|
+
networkMode = config.networkMode ?? false;
|
|
15336
|
+
} catch {}
|
|
15337
|
+
return {
|
|
15338
|
+
projectCwd,
|
|
15339
|
+
networkMode
|
|
15340
|
+
};
|
|
15341
|
+
}
|
|
15342
|
+
/**
|
|
15343
|
+
* Spawn the dashboard server as a detached background process.
|
|
15344
|
+
* Returns the child process PID, or null if spawn failed.
|
|
15345
|
+
*/
|
|
15346
|
+
function spawnDashboard(options) {
|
|
15347
|
+
const { serverPath, projectCwd, networkMode = false, nodeEnv = "production" } = options;
|
|
15348
|
+
const serverDir = node_path.default.dirname(serverPath);
|
|
15349
|
+
const isTsFile = serverPath.endsWith(".ts");
|
|
15350
|
+
const child = (0, node_child_process.spawn)("node", isTsFile ? [
|
|
15351
|
+
"--import",
|
|
15352
|
+
"tsx",
|
|
15353
|
+
serverPath
|
|
15354
|
+
] : [serverPath], {
|
|
15355
|
+
cwd: serverDir,
|
|
15356
|
+
detached: true,
|
|
15357
|
+
stdio: "ignore",
|
|
15358
|
+
env: {
|
|
15359
|
+
...process.env,
|
|
15360
|
+
MAXSIM_PROJECT_CWD: projectCwd,
|
|
15361
|
+
MAXSIM_NETWORK_MODE: networkMode ? "1" : "0",
|
|
15362
|
+
NODE_ENV: isTsFile ? "development" : nodeEnv
|
|
15363
|
+
},
|
|
15364
|
+
...process.platform === "win32" ? { shell: true } : {}
|
|
15365
|
+
});
|
|
15366
|
+
child.unref();
|
|
15367
|
+
return child.pid ?? null;
|
|
15368
|
+
}
|
|
15369
|
+
|
|
15071
15370
|
//#endregion
|
|
15072
15371
|
//#region src/core/init.ts
|
|
15073
15372
|
/**
|
|
@@ -15094,7 +15393,7 @@ function scanPhaseArtifacts(cwd, phaseDirectory) {
|
|
|
15094
15393
|
const uatFile = files.find((f) => f.endsWith("-UAT.md") || f === "UAT.md");
|
|
15095
15394
|
if (uatFile) result.uat_path = node_path.default.join(phaseDirectory, uatFile);
|
|
15096
15395
|
} catch (e) {
|
|
15097
|
-
|
|
15396
|
+
debugLog(e);
|
|
15098
15397
|
}
|
|
15099
15398
|
return result;
|
|
15100
15399
|
}
|
|
@@ -15147,7 +15446,6 @@ function cmdInitPlanPhase(cwd, phase, raw) {
|
|
|
15147
15446
|
checker_model: resolveModelInternal(cwd, "maxsim-plan-checker"),
|
|
15148
15447
|
research_enabled: config.research,
|
|
15149
15448
|
plan_checker_enabled: config.plan_checker,
|
|
15150
|
-
nyquist_validation_enabled: false,
|
|
15151
15449
|
commit_docs: config.commit_docs,
|
|
15152
15450
|
phase_found: !!phaseInfo,
|
|
15153
15451
|
phase_dir: phaseInfo?.directory ?? null,
|
|
@@ -15193,7 +15491,7 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
15193
15491
|
]
|
|
15194
15492
|
}).trim().length > 0;
|
|
15195
15493
|
} catch (e) {
|
|
15196
|
-
|
|
15494
|
+
debugLog(e);
|
|
15197
15495
|
}
|
|
15198
15496
|
hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
|
|
15199
15497
|
output({
|
|
@@ -15236,13 +15534,13 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
15236
15534
|
const config = loadConfig(cwd);
|
|
15237
15535
|
const now = /* @__PURE__ */ new Date();
|
|
15238
15536
|
const slug = description ? generateSlugInternal(description)?.substring(0, 40) ?? null : null;
|
|
15239
|
-
const quickDir =
|
|
15537
|
+
const quickDir = planningPath(cwd, "quick");
|
|
15240
15538
|
let nextNum = 1;
|
|
15241
15539
|
try {
|
|
15242
15540
|
const existing = node_fs.default.readdirSync(quickDir).filter((f) => /^\d+-/.test(f)).map((f) => parseInt(f.split("-")[0], 10)).filter((n) => !isNaN(n));
|
|
15243
15541
|
if (existing.length > 0) nextNum = Math.max(...existing) + 1;
|
|
15244
15542
|
} catch (e) {
|
|
15245
|
-
|
|
15543
|
+
debugLog(e);
|
|
15246
15544
|
}
|
|
15247
15545
|
output({
|
|
15248
15546
|
planner_model: resolveModelInternal(cwd, "maxsim-planner"),
|
|
@@ -15253,7 +15551,7 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
15253
15551
|
next_num: nextNum,
|
|
15254
15552
|
slug,
|
|
15255
15553
|
description: description ?? null,
|
|
15256
|
-
date:
|
|
15554
|
+
date: todayISO(),
|
|
15257
15555
|
timestamp: now.toISOString(),
|
|
15258
15556
|
quick_dir: ".planning/quick",
|
|
15259
15557
|
task_dir: slug ? `.planning/quick/${nextNum}-${slug}` : null,
|
|
@@ -15265,9 +15563,9 @@ function cmdInitResume(cwd, raw) {
|
|
|
15265
15563
|
const config = loadConfig(cwd);
|
|
15266
15564
|
let interruptedAgentId = null;
|
|
15267
15565
|
try {
|
|
15268
|
-
interruptedAgentId = node_fs.default.readFileSync(
|
|
15566
|
+
interruptedAgentId = node_fs.default.readFileSync(planningPath(cwd, "current-agent-id.txt"), "utf-8").trim();
|
|
15269
15567
|
} catch (e) {
|
|
15270
|
-
|
|
15568
|
+
debugLog(e);
|
|
15271
15569
|
}
|
|
15272
15570
|
output({
|
|
15273
15571
|
state_exists: pathExistsInternal(cwd, ".planning/STATE.md"),
|
|
@@ -15351,7 +15649,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
15351
15649
|
function cmdInitTodos(cwd, area, raw) {
|
|
15352
15650
|
const config = loadConfig(cwd);
|
|
15353
15651
|
const now = /* @__PURE__ */ new Date();
|
|
15354
|
-
const pendingDir =
|
|
15652
|
+
const pendingDir = planningPath(cwd, "todos", "pending");
|
|
15355
15653
|
let count = 0;
|
|
15356
15654
|
const todos = [];
|
|
15357
15655
|
try {
|
|
@@ -15372,14 +15670,14 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
15372
15670
|
path: node_path.default.join(".planning", "todos", "pending", file)
|
|
15373
15671
|
});
|
|
15374
15672
|
} catch (e) {
|
|
15375
|
-
|
|
15673
|
+
debugLog(e);
|
|
15376
15674
|
}
|
|
15377
15675
|
} catch (e) {
|
|
15378
|
-
|
|
15676
|
+
debugLog(e);
|
|
15379
15677
|
}
|
|
15380
15678
|
output({
|
|
15381
15679
|
commit_docs: config.commit_docs,
|
|
15382
|
-
date:
|
|
15680
|
+
date: todayISO(),
|
|
15383
15681
|
timestamp: now.toISOString(),
|
|
15384
15682
|
todo_count: count,
|
|
15385
15683
|
todos,
|
|
@@ -15396,24 +15694,24 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
15396
15694
|
const milestone = getMilestoneInfo(cwd);
|
|
15397
15695
|
let phaseCount = 0;
|
|
15398
15696
|
let completedPhases = 0;
|
|
15399
|
-
const phasesDir =
|
|
15697
|
+
const phasesDir = phasesPath(cwd);
|
|
15400
15698
|
try {
|
|
15401
|
-
const dirs =
|
|
15699
|
+
const dirs = listSubDirs(phasesDir);
|
|
15402
15700
|
phaseCount = dirs.length;
|
|
15403
15701
|
for (const dir of dirs) try {
|
|
15404
|
-
if (node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).some((f) =>
|
|
15702
|
+
if (node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).some((f) => isSummaryFile(f))) completedPhases++;
|
|
15405
15703
|
} catch (e) {
|
|
15406
|
-
|
|
15704
|
+
debugLog(e);
|
|
15407
15705
|
}
|
|
15408
15706
|
} catch (e) {
|
|
15409
|
-
|
|
15707
|
+
debugLog(e);
|
|
15410
15708
|
}
|
|
15411
|
-
const archiveDir =
|
|
15709
|
+
const archiveDir = planningPath(cwd, "archive");
|
|
15412
15710
|
let archivedMilestones = [];
|
|
15413
15711
|
try {
|
|
15414
|
-
archivedMilestones =
|
|
15712
|
+
archivedMilestones = listSubDirs(archiveDir);
|
|
15415
15713
|
} catch (e) {
|
|
15416
|
-
|
|
15714
|
+
debugLog(e);
|
|
15417
15715
|
}
|
|
15418
15716
|
output({
|
|
15419
15717
|
commit_docs: config.commit_docs,
|
|
@@ -15434,12 +15732,12 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
15434
15732
|
}
|
|
15435
15733
|
function cmdInitMapCodebase(cwd, raw) {
|
|
15436
15734
|
const config = loadConfig(cwd);
|
|
15437
|
-
const codebaseDir =
|
|
15735
|
+
const codebaseDir = planningPath(cwd, "codebase");
|
|
15438
15736
|
let existingMaps = [];
|
|
15439
15737
|
try {
|
|
15440
15738
|
existingMaps = node_fs.default.readdirSync(codebaseDir).filter((f) => f.endsWith(".md"));
|
|
15441
15739
|
} catch (e) {
|
|
15442
|
-
|
|
15740
|
+
debugLog(e);
|
|
15443
15741
|
}
|
|
15444
15742
|
output({
|
|
15445
15743
|
mapper_model: resolveModelInternal(cwd, "maxsim-codebase-mapper"),
|
|
@@ -15471,15 +15769,15 @@ function cmdInitExisting(cwd, raw) {
|
|
|
15471
15769
|
]
|
|
15472
15770
|
}).trim().length > 0;
|
|
15473
15771
|
} catch (e) {
|
|
15474
|
-
|
|
15772
|
+
debugLog(e);
|
|
15475
15773
|
}
|
|
15476
15774
|
hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
|
|
15477
15775
|
let planningFiles = [];
|
|
15478
15776
|
try {
|
|
15479
|
-
const planDir =
|
|
15777
|
+
const planDir = planningPath(cwd);
|
|
15480
15778
|
if (node_fs.default.existsSync(planDir)) planningFiles = node_fs.default.readdirSync(planDir, { recursive: true }).map((f) => String(f)).filter((f) => !f.startsWith("."));
|
|
15481
15779
|
} catch (e) {
|
|
15482
|
-
|
|
15780
|
+
debugLog(e);
|
|
15483
15781
|
}
|
|
15484
15782
|
output({
|
|
15485
15783
|
researcher_model: resolveModelInternal(cwd, "maxsim-project-researcher"),
|
|
@@ -15506,20 +15804,20 @@ function cmdInitExisting(cwd, raw) {
|
|
|
15506
15804
|
function cmdInitProgress(cwd, raw) {
|
|
15507
15805
|
const config = loadConfig(cwd);
|
|
15508
15806
|
const milestone = getMilestoneInfo(cwd);
|
|
15509
|
-
const
|
|
15807
|
+
const progressPhasesDir = phasesPath(cwd);
|
|
15510
15808
|
const phases = [];
|
|
15511
15809
|
let currentPhase = null;
|
|
15512
15810
|
let nextPhase = null;
|
|
15513
15811
|
try {
|
|
15514
|
-
const dirs =
|
|
15812
|
+
const dirs = listSubDirs(progressPhasesDir, true);
|
|
15515
15813
|
for (const dir of dirs) {
|
|
15516
15814
|
const match = dir.match(/^(\d+(?:\.\d+)?)-?(.*)/);
|
|
15517
15815
|
const phaseNumber = match ? match[1] : dir;
|
|
15518
15816
|
const phaseName = match && match[2] ? match[2] : null;
|
|
15519
|
-
const
|
|
15520
|
-
const phaseFiles = node_fs.default.readdirSync(
|
|
15521
|
-
const plans = phaseFiles.filter((f) =>
|
|
15522
|
-
const summaries = phaseFiles.filter((f) =>
|
|
15817
|
+
const phaseDirPath = node_path.default.join(progressPhasesDir, dir);
|
|
15818
|
+
const phaseFiles = node_fs.default.readdirSync(phaseDirPath);
|
|
15819
|
+
const plans = phaseFiles.filter((f) => isPlanFile(f));
|
|
15820
|
+
const summaries = phaseFiles.filter((f) => isSummaryFile(f));
|
|
15523
15821
|
const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
15524
15822
|
const status = summaries.length >= plans.length && plans.length > 0 ? "complete" : plans.length > 0 ? "in_progress" : hasResearch ? "researched" : "pending";
|
|
15525
15823
|
const phaseInfo = {
|
|
@@ -15536,14 +15834,14 @@ function cmdInitProgress(cwd, raw) {
|
|
|
15536
15834
|
if (!nextPhase && status === "pending") nextPhase = phaseInfo;
|
|
15537
15835
|
}
|
|
15538
15836
|
} catch (e) {
|
|
15539
|
-
|
|
15837
|
+
debugLog(e);
|
|
15540
15838
|
}
|
|
15541
15839
|
let pausedAt = null;
|
|
15542
15840
|
try {
|
|
15543
|
-
const pauseMatch = node_fs.default.readFileSync(
|
|
15841
|
+
const pauseMatch = node_fs.default.readFileSync(planningPath(cwd, "STATE.md"), "utf-8").match(/\*\*Paused At:\*\*\s*(.+)/);
|
|
15544
15842
|
if (pauseMatch) pausedAt = pauseMatch[1].trim();
|
|
15545
15843
|
} catch (e) {
|
|
15546
|
-
|
|
15844
|
+
debugLog(e);
|
|
15547
15845
|
}
|
|
15548
15846
|
output({
|
|
15549
15847
|
executor_model: resolveModelInternal(cwd, "maxsim-executor"),
|
|
@@ -15828,33 +16126,49 @@ const COMMANDS = {
|
|
|
15828
16126
|
freshness: f.freshness ?? void 0
|
|
15829
16127
|
}, raw);
|
|
15830
16128
|
},
|
|
15831
|
-
"dashboard": (args) => handleDashboard(args.slice(1))
|
|
16129
|
+
"dashboard": (args) => handleDashboard(args.slice(1)),
|
|
16130
|
+
"start-server": async () => {
|
|
16131
|
+
const serverPath = node_path.join(__dirname, "mcp-server.cjs");
|
|
16132
|
+
(0, node_child_process.spawn)(process.execPath, [serverPath], { stdio: "inherit" }).on("exit", (code) => process.exit(code ?? 0));
|
|
16133
|
+
}
|
|
15832
16134
|
};
|
|
15833
16135
|
async function main() {
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
16136
|
+
try {
|
|
16137
|
+
const args = process.argv.slice(2);
|
|
16138
|
+
let cwd = process.cwd();
|
|
16139
|
+
const cwdEqArg = args.find((arg) => arg.startsWith("--cwd="));
|
|
16140
|
+
const cwdIdx = args.indexOf("--cwd");
|
|
16141
|
+
if (cwdEqArg) {
|
|
16142
|
+
const value = cwdEqArg.slice(6).trim();
|
|
16143
|
+
if (!value) error("Missing value for --cwd");
|
|
16144
|
+
args.splice(args.indexOf(cwdEqArg), 1);
|
|
16145
|
+
cwd = node_path.resolve(value);
|
|
16146
|
+
} else if (cwdIdx !== -1) {
|
|
16147
|
+
const value = args[cwdIdx + 1];
|
|
16148
|
+
if (!value || value.startsWith("--")) error("Missing value for --cwd");
|
|
16149
|
+
args.splice(cwdIdx, 2);
|
|
16150
|
+
cwd = node_path.resolve(value);
|
|
16151
|
+
}
|
|
16152
|
+
if (!node_fs.existsSync(cwd) || !node_fs.statSync(cwd).isDirectory()) error(`Invalid --cwd: ${cwd}`);
|
|
16153
|
+
const rawIndex = args.indexOf("--raw");
|
|
16154
|
+
const raw = rawIndex !== -1;
|
|
16155
|
+
if (rawIndex !== -1) args.splice(rawIndex, 1);
|
|
16156
|
+
const command = args[0];
|
|
16157
|
+
if (!command) error(`Usage: maxsim-tools <command> [args] [--raw] [--cwd <path>]\nCommands: ${Object.keys(COMMANDS).join(", ")}`);
|
|
16158
|
+
const handler = COMMANDS[command];
|
|
16159
|
+
if (!handler) error(`Unknown command: ${command}`);
|
|
16160
|
+
await handler(args, cwd, raw);
|
|
16161
|
+
} catch (thrown) {
|
|
16162
|
+
if (thrown instanceof CliOutput) {
|
|
16163
|
+
writeOutput(thrown);
|
|
16164
|
+
process.exit(0);
|
|
16165
|
+
}
|
|
16166
|
+
if (thrown instanceof CliError) {
|
|
16167
|
+
process.stderr.write("Error: " + thrown.message + "\n");
|
|
16168
|
+
process.exit(1);
|
|
16169
|
+
}
|
|
16170
|
+
throw thrown;
|
|
16171
|
+
}
|
|
15858
16172
|
}
|
|
15859
16173
|
/**
|
|
15860
16174
|
* Dashboard launch command.
|
|
@@ -15864,40 +16178,20 @@ async function main() {
|
|
|
15864
16178
|
* Supports --stop to kill a running instance.
|
|
15865
16179
|
*/
|
|
15866
16180
|
async function handleDashboard(args) {
|
|
15867
|
-
const DEFAULT_PORT = 3333;
|
|
15868
|
-
const PORT_RANGE_END = 3343;
|
|
15869
|
-
const HEALTH_TIMEOUT_MS = 1500;
|
|
15870
16181
|
const networkMode = args.includes("--network");
|
|
15871
16182
|
if (args.includes("--stop")) {
|
|
15872
|
-
for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port
|
|
15873
|
-
console.log(`Dashboard found on port ${port} —
|
|
15874
|
-
|
|
15875
|
-
console.log(
|
|
15876
|
-
try {
|
|
15877
|
-
if (process.platform === "win32") {
|
|
15878
|
-
const lines = (0, node_child_process.execSync)(`netstat -ano | findstr :${port} | findstr LISTENING`, { encoding: "utf-8" }).trim().split("\n");
|
|
15879
|
-
const pids = /* @__PURE__ */ new Set();
|
|
15880
|
-
for (const line of lines) {
|
|
15881
|
-
const parts = line.trim().split(/\s+/);
|
|
15882
|
-
const pid = parts[parts.length - 1];
|
|
15883
|
-
if (pid && pid !== "0") pids.add(pid);
|
|
15884
|
-
}
|
|
15885
|
-
for (const pid of pids) try {
|
|
15886
|
-
(0, node_child_process.execSync)(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
|
|
15887
|
-
console.log(`Killed process ${pid}`);
|
|
15888
|
-
} catch {}
|
|
15889
|
-
} else (0, node_child_process.execSync)(`lsof -i :${port} -t | xargs kill -SIGTERM 2>/dev/null`, { stdio: "ignore" });
|
|
15890
|
-
console.log("Dashboard stopped.");
|
|
15891
|
-
} catch {
|
|
15892
|
-
console.log("Could not automatically stop the dashboard. Kill the process manually.");
|
|
15893
|
-
}
|
|
16183
|
+
for (let port = DEFAULT_PORT; port <= PORT_RANGE_END; port++) if (await checkHealth(port)) {
|
|
16184
|
+
console.log(`Dashboard found on port ${port} — stopping...`);
|
|
16185
|
+
killProcessOnPort(port);
|
|
16186
|
+
console.log("Dashboard stopped.");
|
|
15894
16187
|
return;
|
|
15895
16188
|
}
|
|
15896
16189
|
console.log("No running dashboard found.");
|
|
15897
16190
|
return;
|
|
15898
16191
|
}
|
|
15899
|
-
|
|
15900
|
-
|
|
16192
|
+
const runningPort = await findRunningDashboard();
|
|
16193
|
+
if (runningPort) {
|
|
16194
|
+
console.log(`Dashboard already running at http://localhost:${runningPort}`);
|
|
15901
16195
|
return;
|
|
15902
16196
|
}
|
|
15903
16197
|
const serverPath = resolveDashboardServer();
|
|
@@ -15906,100 +16200,25 @@ async function handleDashboard(args) {
|
|
|
15906
16200
|
console.error("Ensure @maxsim/dashboard is installed and built.");
|
|
15907
16201
|
process.exit(1);
|
|
15908
16202
|
}
|
|
15909
|
-
const isTsFile = serverPath.endsWith(".ts");
|
|
15910
|
-
const runner = "node";
|
|
15911
|
-
const runnerArgs = isTsFile ? [
|
|
15912
|
-
"--import",
|
|
15913
|
-
"tsx",
|
|
15914
|
-
serverPath
|
|
15915
|
-
] : [serverPath];
|
|
15916
16203
|
const serverDir = node_path.dirname(serverPath);
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
if (
|
|
15920
|
-
const config = JSON.parse(node_fs.readFileSync(dashboardConfigPath, "utf8"));
|
|
15921
|
-
if (config.projectCwd) projectCwd = config.projectCwd;
|
|
15922
|
-
} catch {}
|
|
15923
|
-
const ptyModulePath = node_path.join(serverDir, "node_modules", "node-pty");
|
|
15924
|
-
if (!node_fs.existsSync(ptyModulePath)) {
|
|
15925
|
-
console.log("Installing node-pty for terminal support...");
|
|
15926
|
-
try {
|
|
15927
|
-
(0, node_child_process.execSync)("npm install node-pty --save-optional --no-audit --no-fund --loglevel=error", {
|
|
15928
|
-
cwd: serverDir,
|
|
15929
|
-
stdio: "inherit",
|
|
15930
|
-
timeout: 12e4
|
|
15931
|
-
});
|
|
15932
|
-
} catch {
|
|
15933
|
-
console.warn("node-pty installation failed — terminal will be unavailable.");
|
|
15934
|
-
}
|
|
15935
|
-
}
|
|
16204
|
+
const dashConfig = readDashboardConfig(serverPath);
|
|
16205
|
+
console.log("Installing node-pty for terminal support...");
|
|
16206
|
+
if (!ensureNodePty(serverDir)) console.warn("node-pty installation failed — terminal will be unavailable.");
|
|
15936
16207
|
console.log("Dashboard starting...");
|
|
15937
|
-
const
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
env: {
|
|
15942
|
-
...process.env,
|
|
15943
|
-
MAXSIM_PROJECT_CWD: projectCwd,
|
|
15944
|
-
NODE_ENV: isTsFile ? "development" : "production",
|
|
15945
|
-
...networkMode ? { MAXSIM_NETWORK_MODE: "1" } : {}
|
|
15946
|
-
},
|
|
15947
|
-
...process.platform === "win32" ? { shell: true } : {}
|
|
16208
|
+
const pid = spawnDashboard({
|
|
16209
|
+
serverPath,
|
|
16210
|
+
projectCwd: dashConfig.projectCwd,
|
|
16211
|
+
networkMode
|
|
15948
16212
|
});
|
|
15949
|
-
child.unref();
|
|
15950
16213
|
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
15951
|
-
|
|
15952
|
-
|
|
16214
|
+
const readyPort = await findRunningDashboard();
|
|
16215
|
+
if (readyPort) {
|
|
16216
|
+
console.log(`Dashboard ready at http://localhost:${readyPort}`);
|
|
15953
16217
|
return;
|
|
15954
16218
|
}
|
|
15955
|
-
console.log(`Dashboard spawned (PID ${
|
|
16219
|
+
console.log(`Dashboard spawned (PID ${pid}). It may take a moment to start.`);
|
|
15956
16220
|
console.log(`Check http://localhost:${DEFAULT_PORT} once ready.`);
|
|
15957
16221
|
}
|
|
15958
|
-
/**
|
|
15959
|
-
* Check if a dashboard health endpoint is responding on the given port.
|
|
15960
|
-
*/
|
|
15961
|
-
async function checkHealth(port, timeoutMs) {
|
|
15962
|
-
try {
|
|
15963
|
-
const controller = new AbortController();
|
|
15964
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15965
|
-
const res = await fetch(`http://localhost:${port}/api/health`, { signal: controller.signal });
|
|
15966
|
-
clearTimeout(timer);
|
|
15967
|
-
if (res.ok) return (await res.json()).status === "ok";
|
|
15968
|
-
return false;
|
|
15969
|
-
} catch {
|
|
15970
|
-
return false;
|
|
15971
|
-
}
|
|
15972
|
-
}
|
|
15973
|
-
/**
|
|
15974
|
-
* Resolve the dashboard server entry point path.
|
|
15975
|
-
* Tries: built server.js first, then source server.ts for dev mode.
|
|
15976
|
-
*/
|
|
15977
|
-
function resolveDashboardServer() {
|
|
15978
|
-
const localDashboard = node_path.join(process.cwd(), ".claude", "dashboard", "server.js");
|
|
15979
|
-
if (node_fs.existsSync(localDashboard)) return localDashboard;
|
|
15980
|
-
const globalDashboard = node_path.join(node_os.homedir(), ".claude", "dashboard", "server.js");
|
|
15981
|
-
if (node_fs.existsSync(globalDashboard)) return globalDashboard;
|
|
15982
|
-
try {
|
|
15983
|
-
const pkgPath = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href).resolve("@maxsim/dashboard/package.json");
|
|
15984
|
-
const pkgDir = node_path.dirname(pkgPath);
|
|
15985
|
-
const serverJs = node_path.join(pkgDir, "server.js");
|
|
15986
|
-
if (node_fs.existsSync(serverJs)) return serverJs;
|
|
15987
|
-
const serverTs = node_path.join(pkgDir, "server.ts");
|
|
15988
|
-
if (node_fs.existsSync(serverTs)) return serverTs;
|
|
15989
|
-
} catch {}
|
|
15990
|
-
try {
|
|
15991
|
-
let dir = node_path.dirname(new URL(require("url").pathToFileURL(__filename).href).pathname);
|
|
15992
|
-
if (process.platform === "win32" && dir.startsWith("/")) dir = dir.slice(1);
|
|
15993
|
-
for (let i = 0; i < 5; i++) {
|
|
15994
|
-
const candidate = node_path.join(dir, "packages", "dashboard", "server.ts");
|
|
15995
|
-
if (node_fs.existsSync(candidate)) return candidate;
|
|
15996
|
-
const candidateJs = node_path.join(dir, "packages", "dashboard", "server.js");
|
|
15997
|
-
if (node_fs.existsSync(candidateJs)) return candidateJs;
|
|
15998
|
-
dir = node_path.dirname(dir);
|
|
15999
|
-
}
|
|
16000
|
-
} catch {}
|
|
16001
|
-
return null;
|
|
16002
|
-
}
|
|
16003
16222
|
main();
|
|
16004
16223
|
|
|
16005
16224
|
//#endregion
|