maxsimcli 3.10.3 → 3.11.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 +7 -0
- package/dist/assets/dashboard/server.js +5 -1
- package/dist/assets/templates/workflows/execute-plan.md +10 -0
- package/dist/cli.cjs +554 -439
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -0
- 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 +36 -34
- package/dist/core/commands.js.map +1 -1
- package/dist/core/core.d.ts +21 -1
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.js +81 -36
- package/dist/core/core.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +2 -2
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +33 -51
- 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 +275 -224
- package/dist/core/phase.js.map +1 -1
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/roadmap.js +16 -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 +44 -37
- 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 +3 -2
- package/dist/core/types.d.ts.map +1 -1
- 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.cjs +23 -0
- package/dist/install.cjs.map +1 -1
- package/dist/install.js +37 -0
- package/dist/install.js.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/cli.cjs
CHANGED
|
@@ -4727,6 +4727,45 @@ function error(message) {
|
|
|
4727
4727
|
process.stderr.write("Error: " + message + "\n");
|
|
4728
4728
|
process.exit(1);
|
|
4729
4729
|
}
|
|
4730
|
+
/** Today's date as YYYY-MM-DD. */
|
|
4731
|
+
function todayISO() {
|
|
4732
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4733
|
+
}
|
|
4734
|
+
/** Canonical .planning/ sub-paths. */
|
|
4735
|
+
function planningPath(cwd, ...segments) {
|
|
4736
|
+
return node_path.default.join(cwd, ".planning", ...segments);
|
|
4737
|
+
}
|
|
4738
|
+
function statePath(cwd) {
|
|
4739
|
+
return planningPath(cwd, "STATE.md");
|
|
4740
|
+
}
|
|
4741
|
+
function roadmapPath(cwd) {
|
|
4742
|
+
return planningPath(cwd, "ROADMAP.md");
|
|
4743
|
+
}
|
|
4744
|
+
function configPath(cwd) {
|
|
4745
|
+
return planningPath(cwd, "config.json");
|
|
4746
|
+
}
|
|
4747
|
+
function phasesPath(cwd) {
|
|
4748
|
+
return planningPath(cwd, "phases");
|
|
4749
|
+
}
|
|
4750
|
+
/** Phase-file predicates. */
|
|
4751
|
+
const isPlanFile = (f) => f.endsWith("-PLAN.md") || f === "PLAN.md";
|
|
4752
|
+
const isSummaryFile = (f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md";
|
|
4753
|
+
/** Strip suffix to get plan/summary ID. */
|
|
4754
|
+
const planId = (f) => f.replace("-PLAN.md", "").replace("PLAN.md", "");
|
|
4755
|
+
const summaryId = (f) => f.replace("-SUMMARY.md", "").replace("SUMMARY.md", "");
|
|
4756
|
+
/** List subdirectory names, optionally sorted by phase number. */
|
|
4757
|
+
function listSubDirs(dir, sortByPhase = false) {
|
|
4758
|
+
const dirs = node_fs.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
4759
|
+
return sortByPhase ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
|
|
4760
|
+
}
|
|
4761
|
+
/** Log only when MAXSIM_DEBUG is set. */
|
|
4762
|
+
function debugLog(e) {
|
|
4763
|
+
if (process.env.MAXSIM_DEBUG) console.error(e);
|
|
4764
|
+
}
|
|
4765
|
+
/** Escape a phase number for use in regex. */
|
|
4766
|
+
function escapePhaseNum(phase) {
|
|
4767
|
+
return String(phase).replace(/\./g, "\\.");
|
|
4768
|
+
}
|
|
4730
4769
|
function safeReadFile(filePath) {
|
|
4731
4770
|
try {
|
|
4732
4771
|
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
@@ -4734,8 +4773,10 @@ function safeReadFile(filePath) {
|
|
|
4734
4773
|
return null;
|
|
4735
4774
|
}
|
|
4736
4775
|
}
|
|
4776
|
+
let _configCache = null;
|
|
4737
4777
|
function loadConfig(cwd) {
|
|
4738
|
-
|
|
4778
|
+
if (_configCache && _configCache.cwd === cwd) return _configCache.config;
|
|
4779
|
+
const cfgPath = configPath(cwd);
|
|
4739
4780
|
const defaults = {
|
|
4740
4781
|
model_profile: "balanced",
|
|
4741
4782
|
commit_docs: true,
|
|
@@ -4750,7 +4791,7 @@ function loadConfig(cwd) {
|
|
|
4750
4791
|
brave_search: false
|
|
4751
4792
|
};
|
|
4752
4793
|
try {
|
|
4753
|
-
const raw = node_fs.default.readFileSync(
|
|
4794
|
+
const raw = node_fs.default.readFileSync(cfgPath, "utf-8");
|
|
4754
4795
|
const parsed = JSON.parse(raw);
|
|
4755
4796
|
const get = (key, nested) => {
|
|
4756
4797
|
if (parsed[key] !== void 0) return parsed[key];
|
|
@@ -4765,7 +4806,7 @@ function loadConfig(cwd) {
|
|
|
4765
4806
|
if (typeof val === "object" && val !== null && "enabled" in val) return val.enabled;
|
|
4766
4807
|
return defaults.parallelization;
|
|
4767
4808
|
})();
|
|
4768
|
-
|
|
4809
|
+
const result = {
|
|
4769
4810
|
model_profile: get("model_profile") ?? defaults.model_profile,
|
|
4770
4811
|
commit_docs: get("commit_docs", {
|
|
4771
4812
|
section: "planning",
|
|
@@ -4803,7 +4844,16 @@ function loadConfig(cwd) {
|
|
|
4803
4844
|
brave_search: get("brave_search") ?? defaults.brave_search,
|
|
4804
4845
|
model_overrides: parsed["model_overrides"]
|
|
4805
4846
|
};
|
|
4847
|
+
_configCache = {
|
|
4848
|
+
cwd,
|
|
4849
|
+
config: result
|
|
4850
|
+
};
|
|
4851
|
+
return result;
|
|
4806
4852
|
} catch {
|
|
4853
|
+
_configCache = {
|
|
4854
|
+
cwd,
|
|
4855
|
+
config: defaults
|
|
4856
|
+
};
|
|
4807
4857
|
return defaults;
|
|
4808
4858
|
}
|
|
4809
4859
|
}
|
|
@@ -4873,23 +4923,20 @@ function getPhasePattern(escapedPhaseNum, flags = "gim") {
|
|
|
4873
4923
|
}
|
|
4874
4924
|
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
4875
4925
|
try {
|
|
4876
|
-
const match =
|
|
4926
|
+
const match = listSubDirs(baseDir, true).find((d) => d.startsWith(normalized));
|
|
4877
4927
|
if (!match) return null;
|
|
4878
4928
|
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
4879
4929
|
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
4880
4930
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
4881
4931
|
const phaseDir = node_path.default.join(baseDir, match);
|
|
4882
4932
|
const phaseFiles = node_fs.default.readdirSync(phaseDir);
|
|
4883
|
-
const plans = phaseFiles.filter(
|
|
4884
|
-
const summaries = phaseFiles.filter(
|
|
4933
|
+
const plans = phaseFiles.filter(isPlanFile).sort();
|
|
4934
|
+
const summaries = phaseFiles.filter(isSummaryFile).sort();
|
|
4885
4935
|
const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
4886
4936
|
const hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
4887
4937
|
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
|
-
});
|
|
4938
|
+
const completedPlanIds = new Set(summaries.map(summaryId));
|
|
4939
|
+
const incompletePlans = plans.filter((p) => !completedPlanIds.has(planId(p)));
|
|
4893
4940
|
return {
|
|
4894
4941
|
found: true,
|
|
4895
4942
|
directory: node_path.default.join(relBase, match),
|
|
@@ -4909,12 +4956,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
4909
4956
|
}
|
|
4910
4957
|
function findPhaseInternal(cwd, phase) {
|
|
4911
4958
|
if (!phase) return null;
|
|
4912
|
-
const
|
|
4959
|
+
const pd = phasesPath(cwd);
|
|
4913
4960
|
const normalized = normalizePhaseName(phase);
|
|
4914
|
-
const current = searchPhaseInDir(
|
|
4961
|
+
const current = searchPhaseInDir(pd, node_path.default.join(".planning", "phases"), normalized);
|
|
4915
4962
|
if (current) return current;
|
|
4916
|
-
const milestonesDir =
|
|
4917
|
-
|
|
4963
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
4964
|
+
try {
|
|
4965
|
+
node_fs.default.statSync(milestonesDir);
|
|
4966
|
+
} catch {
|
|
4967
|
+
return null;
|
|
4968
|
+
}
|
|
4918
4969
|
try {
|
|
4919
4970
|
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
4971
|
for (const archiveName of archiveDirs) {
|
|
@@ -4928,14 +4979,13 @@ function findPhaseInternal(cwd, phase) {
|
|
|
4928
4979
|
}
|
|
4929
4980
|
}
|
|
4930
4981
|
} catch (e) {
|
|
4931
|
-
|
|
4982
|
+
debugLog(e);
|
|
4932
4983
|
}
|
|
4933
4984
|
return null;
|
|
4934
4985
|
}
|
|
4935
4986
|
function getArchivedPhaseDirs(cwd) {
|
|
4936
|
-
const milestonesDir =
|
|
4987
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
4937
4988
|
const results = [];
|
|
4938
|
-
if (!node_fs.default.existsSync(milestonesDir)) return results;
|
|
4939
4989
|
try {
|
|
4940
4990
|
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
4991
|
for (const archiveName of phaseDirs) {
|
|
@@ -4943,7 +4993,7 @@ function getArchivedPhaseDirs(cwd) {
|
|
|
4943
4993
|
if (!versionMatch) continue;
|
|
4944
4994
|
const version = versionMatch[1];
|
|
4945
4995
|
const archivePath = node_path.default.join(milestonesDir, archiveName);
|
|
4946
|
-
const dirs =
|
|
4996
|
+
const dirs = listSubDirs(archivePath, true);
|
|
4947
4997
|
for (const dir of dirs) results.push({
|
|
4948
4998
|
name: dir,
|
|
4949
4999
|
milestone: version,
|
|
@@ -4952,17 +5002,16 @@ function getArchivedPhaseDirs(cwd) {
|
|
|
4952
5002
|
});
|
|
4953
5003
|
}
|
|
4954
5004
|
} catch (e) {
|
|
4955
|
-
|
|
5005
|
+
debugLog(e);
|
|
4956
5006
|
}
|
|
4957
5007
|
return results;
|
|
4958
5008
|
}
|
|
4959
5009
|
function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
4960
5010
|
if (!phaseNum) return null;
|
|
4961
|
-
const
|
|
4962
|
-
if (!node_fs.default.existsSync(roadmapPath)) return null;
|
|
5011
|
+
const rp = roadmapPath(cwd);
|
|
4963
5012
|
try {
|
|
4964
|
-
const content = node_fs.default.readFileSync(
|
|
4965
|
-
const phasePattern = getPhasePattern(phaseNum
|
|
5013
|
+
const content = node_fs.default.readFileSync(rp, "utf-8");
|
|
5014
|
+
const phasePattern = getPhasePattern(escapePhaseNum(phaseNum), "i");
|
|
4966
5015
|
const headerMatch = content.match(phasePattern);
|
|
4967
5016
|
if (!headerMatch) return null;
|
|
4968
5017
|
const phaseName = headerMatch[1].trim();
|
|
@@ -4983,8 +5032,8 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
4983
5032
|
return null;
|
|
4984
5033
|
}
|
|
4985
5034
|
}
|
|
4986
|
-
function resolveModelInternal(cwd, agentType) {
|
|
4987
|
-
|
|
5035
|
+
function resolveModelInternal(cwd, agentType, config) {
|
|
5036
|
+
config = config ?? loadConfig(cwd);
|
|
4988
5037
|
const override = config.model_overrides?.[agentType];
|
|
4989
5038
|
if (override) return override === "opus" ? "inherit" : override;
|
|
4990
5039
|
const profile = config.model_profile || "balanced";
|
|
@@ -5011,7 +5060,7 @@ function generateSlugInternal(text) {
|
|
|
5011
5060
|
}
|
|
5012
5061
|
function getMilestoneInfo(cwd) {
|
|
5013
5062
|
try {
|
|
5014
|
-
const roadmap = node_fs.default.readFileSync(
|
|
5063
|
+
const roadmap = node_fs.default.readFileSync(roadmapPath(cwd), "utf-8");
|
|
5015
5064
|
const versionMatch = roadmap.match(/v(\d+\.\d+)/);
|
|
5016
5065
|
const nameMatch = roadmap.match(/## .*v\d+\.\d+[:\s]+([^\n(]+)/);
|
|
5017
5066
|
return {
|
|
@@ -11994,17 +12043,32 @@ function readTextArgOrFile(cwd, value, filePath, label) {
|
|
|
11994
12043
|
throw new Error(`${label} file not found: ${filePath}`);
|
|
11995
12044
|
}
|
|
11996
12045
|
}
|
|
12046
|
+
/**
|
|
12047
|
+
* Append an entry to a section in STATE.md content, removing placeholder text.
|
|
12048
|
+
* Returns updated content or null if section not found.
|
|
12049
|
+
*/
|
|
12050
|
+
function appendToStateSection(content, sectionPattern, entry, placeholderPatterns) {
|
|
12051
|
+
const match = content.match(sectionPattern);
|
|
12052
|
+
if (!match) return null;
|
|
12053
|
+
let sectionBody = match[2];
|
|
12054
|
+
for (const pat of placeholderPatterns || [
|
|
12055
|
+
/None yet\.?\s*\n?/gi,
|
|
12056
|
+
/No decisions yet\.?\s*\n?/gi,
|
|
12057
|
+
/None\.?\s*\n?/gi
|
|
12058
|
+
]) sectionBody = sectionBody.replace(pat, "");
|
|
12059
|
+
sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
|
|
12060
|
+
return content.replace(sectionPattern, (_m, header) => `${header}${sectionBody}`);
|
|
12061
|
+
}
|
|
11997
12062
|
function cmdStateLoad(cwd, raw) {
|
|
11998
12063
|
const config = loadConfig(cwd);
|
|
11999
|
-
const planningDir = node_path.default.join(cwd, ".planning");
|
|
12000
12064
|
let stateRaw = "";
|
|
12001
12065
|
try {
|
|
12002
|
-
stateRaw = node_fs.default.readFileSync(
|
|
12066
|
+
stateRaw = node_fs.default.readFileSync(statePath(cwd), "utf-8");
|
|
12003
12067
|
} catch (e) {
|
|
12004
|
-
|
|
12068
|
+
debugLog(e);
|
|
12005
12069
|
}
|
|
12006
|
-
const configExists = node_fs.default.existsSync(
|
|
12007
|
-
const roadmapExists = node_fs.default.existsSync(
|
|
12070
|
+
const configExists = node_fs.default.existsSync(configPath(cwd));
|
|
12071
|
+
const roadmapExists = node_fs.default.existsSync(roadmapPath(cwd));
|
|
12008
12072
|
const stateExists = stateRaw.length > 0;
|
|
12009
12073
|
const result = {
|
|
12010
12074
|
config,
|
|
@@ -12035,9 +12099,9 @@ function cmdStateLoad(cwd, raw) {
|
|
|
12035
12099
|
output(result);
|
|
12036
12100
|
}
|
|
12037
12101
|
function cmdStateGet(cwd, section, raw) {
|
|
12038
|
-
const statePath =
|
|
12102
|
+
const statePath$2 = statePath(cwd);
|
|
12039
12103
|
try {
|
|
12040
|
-
const content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12104
|
+
const content = node_fs.default.readFileSync(statePath$2, "utf-8");
|
|
12041
12105
|
if (!section) {
|
|
12042
12106
|
output({ content }, raw, content);
|
|
12043
12107
|
return;
|
|
@@ -12061,9 +12125,9 @@ function cmdStateGet(cwd, section, raw) {
|
|
|
12061
12125
|
}
|
|
12062
12126
|
}
|
|
12063
12127
|
function cmdStatePatch(cwd, patches, raw) {
|
|
12064
|
-
const statePath =
|
|
12128
|
+
const statePath$3 = statePath(cwd);
|
|
12065
12129
|
try {
|
|
12066
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12130
|
+
let content = node_fs.default.readFileSync(statePath$3, "utf-8");
|
|
12067
12131
|
const results = {
|
|
12068
12132
|
updated: [],
|
|
12069
12133
|
failed: []
|
|
@@ -12076,7 +12140,7 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
12076
12140
|
results.updated.push(field);
|
|
12077
12141
|
} else results.failed.push(field);
|
|
12078
12142
|
}
|
|
12079
|
-
if (results.updated.length > 0) node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12143
|
+
if (results.updated.length > 0) node_fs.default.writeFileSync(statePath$3, content, "utf-8");
|
|
12080
12144
|
output(results, raw, results.updated.length > 0 ? "true" : "false");
|
|
12081
12145
|
} catch {
|
|
12082
12146
|
error("STATE.md not found");
|
|
@@ -12084,14 +12148,14 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
12084
12148
|
}
|
|
12085
12149
|
function cmdStateUpdate(cwd, field, value) {
|
|
12086
12150
|
if (!field || value === void 0) error("field and value required for state update");
|
|
12087
|
-
const statePath =
|
|
12151
|
+
const statePath$4 = statePath(cwd);
|
|
12088
12152
|
try {
|
|
12089
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12153
|
+
let content = node_fs.default.readFileSync(statePath$4, "utf-8");
|
|
12090
12154
|
const fieldEscaped = escapeStringRegexp(field);
|
|
12091
12155
|
const pattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, "i");
|
|
12092
12156
|
if (pattern.test(content)) {
|
|
12093
12157
|
content = content.replace(pattern, (_match, prefix) => `${prefix}${value}`);
|
|
12094
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12158
|
+
node_fs.default.writeFileSync(statePath$4, content, "utf-8");
|
|
12095
12159
|
output({ updated: true });
|
|
12096
12160
|
} else output({
|
|
12097
12161
|
updated: false,
|
|
@@ -12105,15 +12169,15 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
12105
12169
|
}
|
|
12106
12170
|
}
|
|
12107
12171
|
function cmdStateAdvancePlan(cwd, raw) {
|
|
12108
|
-
const statePath =
|
|
12109
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12172
|
+
const statePath$5 = statePath(cwd);
|
|
12173
|
+
if (!node_fs.default.existsSync(statePath$5)) {
|
|
12110
12174
|
output({ error: "STATE.md not found" }, raw);
|
|
12111
12175
|
return;
|
|
12112
12176
|
}
|
|
12113
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12177
|
+
let content = node_fs.default.readFileSync(statePath$5, "utf-8");
|
|
12114
12178
|
const currentPlan = parseInt(stateExtractField(content, "Current Plan") ?? "", 10);
|
|
12115
12179
|
const totalPlans = parseInt(stateExtractField(content, "Total Plans in Phase") ?? "", 10);
|
|
12116
|
-
const today = (
|
|
12180
|
+
const today = todayISO();
|
|
12117
12181
|
if (isNaN(currentPlan) || isNaN(totalPlans)) {
|
|
12118
12182
|
output({ error: "Cannot parse Current Plan or Total Plans in Phase from STATE.md" }, raw);
|
|
12119
12183
|
return;
|
|
@@ -12121,7 +12185,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12121
12185
|
if (currentPlan >= totalPlans) {
|
|
12122
12186
|
content = stateReplaceField(content, "Status", "Phase complete — ready for verification") || content;
|
|
12123
12187
|
content = stateReplaceField(content, "Last Activity", today) || content;
|
|
12124
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12188
|
+
node_fs.default.writeFileSync(statePath$5, content, "utf-8");
|
|
12125
12189
|
output({
|
|
12126
12190
|
advanced: false,
|
|
12127
12191
|
reason: "last_plan",
|
|
@@ -12134,7 +12198,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12134
12198
|
content = stateReplaceField(content, "Current Plan", String(newPlan)) || content;
|
|
12135
12199
|
content = stateReplaceField(content, "Status", "Ready to execute") || content;
|
|
12136
12200
|
content = stateReplaceField(content, "Last Activity", today) || content;
|
|
12137
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12201
|
+
node_fs.default.writeFileSync(statePath$5, content, "utf-8");
|
|
12138
12202
|
output({
|
|
12139
12203
|
advanced: true,
|
|
12140
12204
|
previous_plan: currentPlan,
|
|
@@ -12144,12 +12208,12 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12144
12208
|
}
|
|
12145
12209
|
}
|
|
12146
12210
|
function cmdStateRecordMetric(cwd, options, raw) {
|
|
12147
|
-
const statePath =
|
|
12148
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12211
|
+
const statePath$6 = statePath(cwd);
|
|
12212
|
+
if (!node_fs.default.existsSync(statePath$6)) {
|
|
12149
12213
|
output({ error: "STATE.md not found" }, raw);
|
|
12150
12214
|
return;
|
|
12151
12215
|
}
|
|
12152
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12216
|
+
let content = node_fs.default.readFileSync(statePath$6, "utf-8");
|
|
12153
12217
|
const { phase, plan, duration, tasks, files } = options;
|
|
12154
12218
|
if (!phase || !plan || !duration) {
|
|
12155
12219
|
output({ error: "phase, plan, and duration required" }, raw);
|
|
@@ -12163,7 +12227,7 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
12163
12227
|
if (tableBody.trim() === "" || tableBody.includes("None yet")) tableBody = newRow;
|
|
12164
12228
|
else tableBody = tableBody + "\n" + newRow;
|
|
12165
12229
|
content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
|
|
12166
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12230
|
+
node_fs.default.writeFileSync(statePath$6, content, "utf-8");
|
|
12167
12231
|
output({
|
|
12168
12232
|
recorded: true,
|
|
12169
12233
|
phase,
|
|
@@ -12176,21 +12240,21 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
12176
12240
|
}, raw, "false");
|
|
12177
12241
|
}
|
|
12178
12242
|
function cmdStateUpdateProgress(cwd, raw) {
|
|
12179
|
-
const statePath =
|
|
12180
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12243
|
+
const statePath$7 = statePath(cwd);
|
|
12244
|
+
if (!node_fs.default.existsSync(statePath$7)) {
|
|
12181
12245
|
output({ error: "STATE.md not found" }, raw);
|
|
12182
12246
|
return;
|
|
12183
12247
|
}
|
|
12184
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12185
|
-
const phasesDir =
|
|
12248
|
+
let content = node_fs.default.readFileSync(statePath$7, "utf-8");
|
|
12249
|
+
const phasesDir = phasesPath(cwd);
|
|
12186
12250
|
let totalPlans = 0;
|
|
12187
12251
|
let totalSummaries = 0;
|
|
12188
12252
|
if (node_fs.default.existsSync(phasesDir)) {
|
|
12189
12253
|
const phaseDirs = node_fs.default.readdirSync(phasesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
12190
12254
|
for (const dir of phaseDirs) {
|
|
12191
12255
|
const files = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
12192
|
-
totalPlans += files.filter((f) => f
|
|
12193
|
-
totalSummaries += files.filter((f) => f
|
|
12256
|
+
totalPlans += files.filter((f) => isPlanFile(f)).length;
|
|
12257
|
+
totalSummaries += files.filter((f) => isSummaryFile(f)).length;
|
|
12194
12258
|
}
|
|
12195
12259
|
}
|
|
12196
12260
|
const percent = totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0;
|
|
@@ -12200,7 +12264,7 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
12200
12264
|
const progressPattern = /(\*\*Progress:\*\*\s*).*/i;
|
|
12201
12265
|
if (progressPattern.test(content)) {
|
|
12202
12266
|
content = content.replace(progressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
12203
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12267
|
+
node_fs.default.writeFileSync(statePath$7, content, "utf-8");
|
|
12204
12268
|
output({
|
|
12205
12269
|
updated: true,
|
|
12206
12270
|
percent,
|
|
@@ -12214,8 +12278,8 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
12214
12278
|
}, raw, "false");
|
|
12215
12279
|
}
|
|
12216
12280
|
function cmdStateAddDecision(cwd, options, raw) {
|
|
12217
|
-
const statePath =
|
|
12218
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12281
|
+
const statePath$8 = statePath(cwd);
|
|
12282
|
+
if (!node_fs.default.existsSync(statePath$8)) {
|
|
12219
12283
|
output({ error: "STATE.md not found" }, raw);
|
|
12220
12284
|
return;
|
|
12221
12285
|
}
|
|
@@ -12236,16 +12300,11 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
12236
12300
|
output({ error: "summary required" }, raw);
|
|
12237
12301
|
return;
|
|
12238
12302
|
}
|
|
12239
|
-
|
|
12303
|
+
const content = node_fs.default.readFileSync(statePath$8, "utf-8");
|
|
12240
12304
|
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");
|
|
12305
|
+
const updated = appendToStateSection(content, /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, entry, [/None yet\.?\s*\n?/gi, /No decisions yet\.?\s*\n?/gi]);
|
|
12306
|
+
if (updated) {
|
|
12307
|
+
node_fs.default.writeFileSync(statePath$8, updated, "utf-8");
|
|
12249
12308
|
output({
|
|
12250
12309
|
added: true,
|
|
12251
12310
|
decision: entry
|
|
@@ -12256,8 +12315,8 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
12256
12315
|
}, raw, "false");
|
|
12257
12316
|
}
|
|
12258
12317
|
function cmdStateAddBlocker(cwd, text, raw) {
|
|
12259
|
-
const statePath =
|
|
12260
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12318
|
+
const statePath$9 = statePath(cwd);
|
|
12319
|
+
if (!node_fs.default.existsSync(statePath$9)) {
|
|
12261
12320
|
output({ error: "STATE.md not found" }, raw);
|
|
12262
12321
|
return;
|
|
12263
12322
|
}
|
|
@@ -12276,16 +12335,9 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
12276
12335
|
output({ error: "text required" }, raw);
|
|
12277
12336
|
return;
|
|
12278
12337
|
}
|
|
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");
|
|
12338
|
+
const updated = appendToStateSection(node_fs.default.readFileSync(statePath$9, "utf-8"), /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, `- ${blockerText}`, [/None\.?\s*\n?/gi, /None yet\.?\s*\n?/gi]);
|
|
12339
|
+
if (updated) {
|
|
12340
|
+
node_fs.default.writeFileSync(statePath$9, updated, "utf-8");
|
|
12289
12341
|
output({
|
|
12290
12342
|
added: true,
|
|
12291
12343
|
blocker: blockerText
|
|
@@ -12296,8 +12348,8 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
12296
12348
|
}, raw, "false");
|
|
12297
12349
|
}
|
|
12298
12350
|
function cmdStateResolveBlocker(cwd, text, raw) {
|
|
12299
|
-
const statePath =
|
|
12300
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12351
|
+
const statePath$10 = statePath(cwd);
|
|
12352
|
+
if (!node_fs.default.existsSync(statePath$10)) {
|
|
12301
12353
|
output({ error: "STATE.md not found" }, raw);
|
|
12302
12354
|
return;
|
|
12303
12355
|
}
|
|
@@ -12305,7 +12357,7 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12305
12357
|
output({ error: "text required" }, raw);
|
|
12306
12358
|
return;
|
|
12307
12359
|
}
|
|
12308
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12360
|
+
let content = node_fs.default.readFileSync(statePath$10, "utf-8");
|
|
12309
12361
|
const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
|
|
12310
12362
|
const match = content.match(sectionPattern);
|
|
12311
12363
|
if (match) {
|
|
@@ -12315,7 +12367,7 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12315
12367
|
}).join("\n");
|
|
12316
12368
|
if (!newBody.trim() || !newBody.includes("- ")) newBody = "None\n";
|
|
12317
12369
|
content = content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
|
|
12318
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12370
|
+
node_fs.default.writeFileSync(statePath$10, content, "utf-8");
|
|
12319
12371
|
output({
|
|
12320
12372
|
resolved: true,
|
|
12321
12373
|
blocker: text
|
|
@@ -12326,12 +12378,12 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12326
12378
|
}, raw, "false");
|
|
12327
12379
|
}
|
|
12328
12380
|
function cmdStateRecordSession(cwd, options, raw) {
|
|
12329
|
-
const statePath =
|
|
12330
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12381
|
+
const statePath$11 = statePath(cwd);
|
|
12382
|
+
if (!node_fs.default.existsSync(statePath$11)) {
|
|
12331
12383
|
output({ error: "STATE.md not found" }, raw);
|
|
12332
12384
|
return;
|
|
12333
12385
|
}
|
|
12334
|
-
let content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12386
|
+
let content = node_fs.default.readFileSync(statePath$11, "utf-8");
|
|
12335
12387
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12336
12388
|
const updated = [];
|
|
12337
12389
|
let result = stateReplaceField(content, "Last session", now);
|
|
@@ -12360,7 +12412,7 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
12360
12412
|
updated.push("Resume File");
|
|
12361
12413
|
}
|
|
12362
12414
|
if (updated.length > 0) {
|
|
12363
|
-
node_fs.default.writeFileSync(statePath, content, "utf-8");
|
|
12415
|
+
node_fs.default.writeFileSync(statePath$11, content, "utf-8");
|
|
12364
12416
|
output({
|
|
12365
12417
|
recorded: true,
|
|
12366
12418
|
updated
|
|
@@ -12371,12 +12423,12 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
12371
12423
|
}, raw, "false");
|
|
12372
12424
|
}
|
|
12373
12425
|
function cmdStateSnapshot(cwd, raw) {
|
|
12374
|
-
const statePath =
|
|
12375
|
-
if (!node_fs.default.existsSync(statePath)) {
|
|
12426
|
+
const statePath$12 = statePath(cwd);
|
|
12427
|
+
if (!node_fs.default.existsSync(statePath$12)) {
|
|
12376
12428
|
output({ error: "STATE.md not found" }, raw);
|
|
12377
12429
|
return;
|
|
12378
12430
|
}
|
|
12379
|
-
const content = node_fs.default.readFileSync(statePath, "utf-8");
|
|
12431
|
+
const content = node_fs.default.readFileSync(statePath$12, "utf-8");
|
|
12380
12432
|
const extractField = (fieldName) => {
|
|
12381
12433
|
const pattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, "i");
|
|
12382
12434
|
const match = content.match(pattern);
|
|
@@ -12454,8 +12506,8 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
12454
12506
|
* Ported from maxsim/bin/lib/roadmap.cjs
|
|
12455
12507
|
*/
|
|
12456
12508
|
function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
12457
|
-
const
|
|
12458
|
-
if (!node_fs.default.existsSync(
|
|
12509
|
+
const rmPath = roadmapPath(cwd);
|
|
12510
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
12459
12511
|
output({
|
|
12460
12512
|
found: false,
|
|
12461
12513
|
error: "ROADMAP.md not found"
|
|
@@ -12463,7 +12515,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
12463
12515
|
return;
|
|
12464
12516
|
}
|
|
12465
12517
|
try {
|
|
12466
|
-
const content = node_fs.default.readFileSync(
|
|
12518
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12467
12519
|
const escapedPhase = phaseNum.replace(/\./g, "\\.");
|
|
12468
12520
|
const phasePattern = getPhasePattern(escapedPhase, "i");
|
|
12469
12521
|
const headerMatch = content.match(phasePattern);
|
|
@@ -12507,8 +12559,8 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
12507
12559
|
}
|
|
12508
12560
|
}
|
|
12509
12561
|
function cmdRoadmapAnalyze(cwd, raw) {
|
|
12510
|
-
const
|
|
12511
|
-
if (!node_fs.default.existsSync(
|
|
12562
|
+
const rmPath = roadmapPath(cwd);
|
|
12563
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
12512
12564
|
output({
|
|
12513
12565
|
error: "ROADMAP.md not found",
|
|
12514
12566
|
milestones: [],
|
|
@@ -12517,8 +12569,8 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12517
12569
|
}, raw);
|
|
12518
12570
|
return;
|
|
12519
12571
|
}
|
|
12520
|
-
const content = node_fs.default.readFileSync(
|
|
12521
|
-
const phasesDir =
|
|
12572
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12573
|
+
const phasesDir = phasesPath(cwd);
|
|
12522
12574
|
const phasePattern = getPhasePattern();
|
|
12523
12575
|
const phases = [];
|
|
12524
12576
|
let match;
|
|
@@ -12540,11 +12592,11 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12540
12592
|
let hasContext = false;
|
|
12541
12593
|
let hasResearch = false;
|
|
12542
12594
|
try {
|
|
12543
|
-
const dirMatch =
|
|
12595
|
+
const dirMatch = listSubDirs(phasesDir).find((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
12544
12596
|
if (dirMatch) {
|
|
12545
12597
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dirMatch));
|
|
12546
|
-
planCount = phaseFiles.filter((f) =>
|
|
12547
|
-
summaryCount = phaseFiles.filter((f) =>
|
|
12598
|
+
planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
|
|
12599
|
+
summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
|
|
12548
12600
|
hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
12549
12601
|
hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
12550
12602
|
if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
|
|
@@ -12555,7 +12607,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12555
12607
|
else diskStatus = "empty";
|
|
12556
12608
|
}
|
|
12557
12609
|
} catch (e) {
|
|
12558
|
-
|
|
12610
|
+
debugLog(e);
|
|
12559
12611
|
}
|
|
12560
12612
|
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i");
|
|
12561
12613
|
const checkboxMatch = content.match(checkboxPattern);
|
|
@@ -12606,7 +12658,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
12606
12658
|
}
|
|
12607
12659
|
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
12608
12660
|
if (!phaseNum) error("phase number required for roadmap update-plan-progress");
|
|
12609
|
-
const
|
|
12661
|
+
const rmPath = roadmapPath(cwd);
|
|
12610
12662
|
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
12611
12663
|
if (!phaseInfo) error(`Phase ${phaseNum} not found`);
|
|
12612
12664
|
const planCount = phaseInfo.plans.length;
|
|
@@ -12622,8 +12674,8 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
12622
12674
|
}
|
|
12623
12675
|
const isComplete = summaryCount >= planCount;
|
|
12624
12676
|
const status = isComplete ? "Complete" : summaryCount > 0 ? "In Progress" : "Planned";
|
|
12625
|
-
const today = (
|
|
12626
|
-
if (!node_fs.default.existsSync(
|
|
12677
|
+
const today = todayISO();
|
|
12678
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
12627
12679
|
output({
|
|
12628
12680
|
updated: false,
|
|
12629
12681
|
reason: "ROADMAP.md not found",
|
|
@@ -12632,7 +12684,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
12632
12684
|
}, raw, "no roadmap");
|
|
12633
12685
|
return;
|
|
12634
12686
|
}
|
|
12635
|
-
let roadmapContent = node_fs.default.readFileSync(
|
|
12687
|
+
let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12636
12688
|
const phaseEscaped = phaseNum.replace(".", "\\.");
|
|
12637
12689
|
const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i");
|
|
12638
12690
|
const dateField = isComplete ? ` ${today} ` : " ";
|
|
@@ -12644,7 +12696,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
12644
12696
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, "i");
|
|
12645
12697
|
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
12646
12698
|
}
|
|
12647
|
-
node_fs.default.writeFileSync(
|
|
12699
|
+
node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
|
|
12648
12700
|
output({
|
|
12649
12701
|
updated: true,
|
|
12650
12702
|
phase: phaseNum,
|
|
@@ -12666,7 +12718,7 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
12666
12718
|
if (!reqIdsRaw || reqIdsRaw.length === 0) error("requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02");
|
|
12667
12719
|
const reqIds = reqIdsRaw.join(" ").replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
|
|
12668
12720
|
if (reqIds.length === 0) error("no valid requirement IDs found");
|
|
12669
|
-
const reqPath =
|
|
12721
|
+
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
12670
12722
|
if (!node_fs.default.existsSync(reqPath)) {
|
|
12671
12723
|
output({
|
|
12672
12724
|
updated: false,
|
|
@@ -12702,13 +12754,13 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
12702
12754
|
}
|
|
12703
12755
|
function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
12704
12756
|
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 = (
|
|
12757
|
+
const roadmapPath$1 = roadmapPath(cwd);
|
|
12758
|
+
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
12759
|
+
const statePath$1 = statePath(cwd);
|
|
12760
|
+
const milestonesPath = planningPath(cwd, "MILESTONES.md");
|
|
12761
|
+
const archiveDir = planningPath(cwd, "milestones");
|
|
12762
|
+
const phasesDir = phasesPath(cwd);
|
|
12763
|
+
const today = todayISO();
|
|
12712
12764
|
const milestoneName = options.name || version;
|
|
12713
12765
|
node_fs.default.mkdirSync(archiveDir, { recursive: true });
|
|
12714
12766
|
let phaseCount = 0;
|
|
@@ -12716,12 +12768,12 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12716
12768
|
let totalTasks = 0;
|
|
12717
12769
|
const accomplishments = [];
|
|
12718
12770
|
try {
|
|
12719
|
-
const dirs =
|
|
12771
|
+
const dirs = listSubDirs(phasesDir, true);
|
|
12720
12772
|
for (const dir of dirs) {
|
|
12721
12773
|
phaseCount++;
|
|
12722
12774
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
12723
|
-
const plans = phaseFiles.filter(
|
|
12724
|
-
const summaries = phaseFiles.filter(
|
|
12775
|
+
const plans = phaseFiles.filter(isPlanFile);
|
|
12776
|
+
const summaries = phaseFiles.filter(isSummaryFile);
|
|
12725
12777
|
totalPlans += plans.length;
|
|
12726
12778
|
for (const s of summaries) try {
|
|
12727
12779
|
const content = node_fs.default.readFileSync(node_path.default.join(phasesDir, dir, s), "utf-8");
|
|
@@ -12730,14 +12782,14 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12730
12782
|
const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
12731
12783
|
totalTasks += taskMatches.length;
|
|
12732
12784
|
} catch (e) {
|
|
12733
|
-
|
|
12785
|
+
debugLog(e);
|
|
12734
12786
|
}
|
|
12735
12787
|
}
|
|
12736
12788
|
} catch (e) {
|
|
12737
|
-
|
|
12789
|
+
debugLog(e);
|
|
12738
12790
|
}
|
|
12739
|
-
if (node_fs.default.existsSync(roadmapPath)) {
|
|
12740
|
-
const roadmapContent = node_fs.default.readFileSync(roadmapPath, "utf-8");
|
|
12791
|
+
if (node_fs.default.existsSync(roadmapPath$1)) {
|
|
12792
|
+
const roadmapContent = node_fs.default.readFileSync(roadmapPath$1, "utf-8");
|
|
12741
12793
|
node_fs.default.writeFileSync(node_path.default.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, "utf-8");
|
|
12742
12794
|
}
|
|
12743
12795
|
if (node_fs.default.existsSync(reqPath)) {
|
|
@@ -12753,22 +12805,22 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12753
12805
|
const existing = node_fs.default.readFileSync(milestonesPath, "utf-8");
|
|
12754
12806
|
node_fs.default.writeFileSync(milestonesPath, existing + "\n" + milestoneEntry, "utf-8");
|
|
12755
12807
|
} 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");
|
|
12808
|
+
if (node_fs.default.existsSync(statePath$1)) {
|
|
12809
|
+
let stateContent = node_fs.default.readFileSync(statePath$1, "utf-8");
|
|
12758
12810
|
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${version} milestone complete`);
|
|
12759
12811
|
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
12760
12812
|
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1${version} milestone completed and archived`);
|
|
12761
|
-
node_fs.default.writeFileSync(statePath, stateContent, "utf-8");
|
|
12813
|
+
node_fs.default.writeFileSync(statePath$1, stateContent, "utf-8");
|
|
12762
12814
|
}
|
|
12763
12815
|
let phasesArchived = false;
|
|
12764
12816
|
if (options.archivePhases) try {
|
|
12765
12817
|
const phaseArchiveDir = node_path.default.join(archiveDir, `${version}-phases`);
|
|
12766
12818
|
node_fs.default.mkdirSync(phaseArchiveDir, { recursive: true });
|
|
12767
|
-
const phaseDirNames =
|
|
12819
|
+
const phaseDirNames = listSubDirs(phasesDir);
|
|
12768
12820
|
for (const dir of phaseDirNames) node_fs.default.renameSync(node_path.default.join(phasesDir, dir), node_path.default.join(phaseArchiveDir, dir));
|
|
12769
12821
|
phasesArchived = phaseDirNames.length > 0;
|
|
12770
12822
|
} catch (e) {
|
|
12771
|
-
|
|
12823
|
+
debugLog(e);
|
|
12772
12824
|
}
|
|
12773
12825
|
output({
|
|
12774
12826
|
version,
|
|
@@ -12785,7 +12837,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
12785
12837
|
phases: phasesArchived
|
|
12786
12838
|
},
|
|
12787
12839
|
milestones_updated: true,
|
|
12788
|
-
state_updated: node_fs.default.existsSync(statePath)
|
|
12840
|
+
state_updated: node_fs.default.existsSync(statePath$1)
|
|
12789
12841
|
}, raw);
|
|
12790
12842
|
}
|
|
12791
12843
|
|
|
@@ -13202,6 +13254,18 @@ const chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
|
13202
13254
|
*
|
|
13203
13255
|
* Ported from maxsim/bin/lib/commands.cjs
|
|
13204
13256
|
*/
|
|
13257
|
+
function parseTodoFrontmatter(content) {
|
|
13258
|
+
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
13259
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
13260
|
+
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
13261
|
+
const completedMatch = content.match(/^completed:\s*(.+)$/m);
|
|
13262
|
+
return {
|
|
13263
|
+
created: createdMatch ? createdMatch[1].trim() : "unknown",
|
|
13264
|
+
title: titleMatch ? titleMatch[1].trim() : "Untitled",
|
|
13265
|
+
area: areaMatch ? areaMatch[1].trim() : "general",
|
|
13266
|
+
...completedMatch && { completed: completedMatch[1].trim() }
|
|
13267
|
+
};
|
|
13268
|
+
}
|
|
13205
13269
|
function cmdGenerateSlug(text, raw) {
|
|
13206
13270
|
if (!text) error("text required for slug generation");
|
|
13207
13271
|
const slug = (0, import_slugify.default)(text, {
|
|
@@ -13215,7 +13279,7 @@ function cmdCurrentTimestamp(format, raw) {
|
|
|
13215
13279
|
let result;
|
|
13216
13280
|
switch (format) {
|
|
13217
13281
|
case "date":
|
|
13218
|
-
result =
|
|
13282
|
+
result = todayISO();
|
|
13219
13283
|
break;
|
|
13220
13284
|
case "filename":
|
|
13221
13285
|
result = now.toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
@@ -13227,31 +13291,27 @@ function cmdCurrentTimestamp(format, raw) {
|
|
|
13227
13291
|
output({ timestamp: result }, raw, result);
|
|
13228
13292
|
}
|
|
13229
13293
|
function cmdListTodos(cwd, area, raw) {
|
|
13230
|
-
const pendingDir =
|
|
13294
|
+
const pendingDir = planningPath(cwd, "todos", "pending");
|
|
13231
13295
|
let count = 0;
|
|
13232
13296
|
const todos = [];
|
|
13233
13297
|
try {
|
|
13234
13298
|
const files = node_fs.default.readdirSync(pendingDir).filter((f) => f.endsWith(".md"));
|
|
13235
13299
|
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;
|
|
13300
|
+
const fm = parseTodoFrontmatter(node_fs.default.readFileSync(node_path.default.join(pendingDir, file), "utf-8"));
|
|
13301
|
+
if (area && fm.area !== area) continue;
|
|
13242
13302
|
count++;
|
|
13243
13303
|
todos.push({
|
|
13244
13304
|
file,
|
|
13245
|
-
created:
|
|
13246
|
-
title:
|
|
13247
|
-
area:
|
|
13305
|
+
created: fm.created,
|
|
13306
|
+
title: fm.title,
|
|
13307
|
+
area: fm.area,
|
|
13248
13308
|
path: node_path.default.join(".planning", "todos", "pending", file)
|
|
13249
13309
|
});
|
|
13250
13310
|
} catch (e) {
|
|
13251
|
-
|
|
13311
|
+
debugLog(e);
|
|
13252
13312
|
}
|
|
13253
13313
|
} catch (e) {
|
|
13254
|
-
|
|
13314
|
+
debugLog(e);
|
|
13255
13315
|
}
|
|
13256
13316
|
output({
|
|
13257
13317
|
count,
|
|
@@ -13275,7 +13335,7 @@ function cmdVerifyPathExists(cwd, targetPath, raw) {
|
|
|
13275
13335
|
}
|
|
13276
13336
|
}
|
|
13277
13337
|
function cmdHistoryDigest(cwd, raw) {
|
|
13278
|
-
const phasesDir =
|
|
13338
|
+
const phasesDir = phasesPath(cwd);
|
|
13279
13339
|
const digest = {
|
|
13280
13340
|
phases: {},
|
|
13281
13341
|
decisions: [],
|
|
@@ -13289,14 +13349,14 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13289
13349
|
milestone: a.milestone
|
|
13290
13350
|
});
|
|
13291
13351
|
if (node_fs.default.existsSync(phasesDir)) try {
|
|
13292
|
-
const currentDirs =
|
|
13352
|
+
const currentDirs = listSubDirs(phasesDir, true);
|
|
13293
13353
|
for (const dir of currentDirs) allPhaseDirs.push({
|
|
13294
13354
|
name: dir,
|
|
13295
13355
|
fullPath: node_path.default.join(phasesDir, dir),
|
|
13296
13356
|
milestone: null
|
|
13297
13357
|
});
|
|
13298
13358
|
} catch (e) {
|
|
13299
|
-
|
|
13359
|
+
debugLog(e);
|
|
13300
13360
|
}
|
|
13301
13361
|
if (allPhaseDirs.length === 0) {
|
|
13302
13362
|
output({
|
|
@@ -13308,7 +13368,7 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13308
13368
|
}
|
|
13309
13369
|
try {
|
|
13310
13370
|
for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
|
|
13311
|
-
const summaries = node_fs.default.readdirSync(dirPath).filter((f) =>
|
|
13371
|
+
const summaries = node_fs.default.readdirSync(dirPath).filter((f) => isSummaryFile(f));
|
|
13312
13372
|
for (const summary of summaries) try {
|
|
13313
13373
|
const fm = extractFrontmatter(node_fs.default.readFileSync(node_path.default.join(dirPath, summary), "utf-8"));
|
|
13314
13374
|
const phaseNum = fm.phase || dir.split("-")[0];
|
|
@@ -13332,7 +13392,7 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
13332
13392
|
const techStack = fm["tech-stack"];
|
|
13333
13393
|
if (techStack && techStack.added) techStack.added.forEach((t) => digest.tech_stack.add(typeof t === "string" ? t : t.name));
|
|
13334
13394
|
} catch (e) {
|
|
13335
|
-
|
|
13395
|
+
debugLog(e);
|
|
13336
13396
|
}
|
|
13337
13397
|
}
|
|
13338
13398
|
const outputDigest = {
|
|
@@ -13527,7 +13587,7 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
13527
13587
|
}
|
|
13528
13588
|
}
|
|
13529
13589
|
function cmdProgressRender(cwd, format, raw) {
|
|
13530
|
-
const phasesDir =
|
|
13590
|
+
const phasesDir = phasesPath(cwd);
|
|
13531
13591
|
const milestone = getMilestoneInfo(cwd);
|
|
13532
13592
|
const phases = [];
|
|
13533
13593
|
let totalPlans = 0;
|
|
@@ -13541,8 +13601,8 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
13541
13601
|
const phaseNum = dm ? dm[1] : dir;
|
|
13542
13602
|
const phaseName = dm && dm[2] ? dm[2].replace(/-/g, " ") : "";
|
|
13543
13603
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
13544
|
-
const planCount = phaseFiles.filter((f) =>
|
|
13545
|
-
const summaryCount = phaseFiles.filter((f) =>
|
|
13604
|
+
const planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
|
|
13605
|
+
const summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
|
|
13546
13606
|
totalPlans += planCount;
|
|
13547
13607
|
totalSummaries += summaryCount;
|
|
13548
13608
|
let status;
|
|
@@ -13559,7 +13619,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
13559
13619
|
});
|
|
13560
13620
|
}
|
|
13561
13621
|
} catch (e) {
|
|
13562
|
-
|
|
13622
|
+
debugLog(e);
|
|
13563
13623
|
}
|
|
13564
13624
|
const percent = totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0;
|
|
13565
13625
|
if (format === "table") {
|
|
@@ -13619,13 +13679,13 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
13619
13679
|
}
|
|
13620
13680
|
function cmdTodoComplete(cwd, filename, raw) {
|
|
13621
13681
|
if (!filename) error("filename required for todo complete");
|
|
13622
|
-
const pendingDir =
|
|
13623
|
-
const completedDir =
|
|
13682
|
+
const pendingDir = planningPath(cwd, "todos", "pending");
|
|
13683
|
+
const completedDir = planningPath(cwd, "todos", "completed");
|
|
13624
13684
|
const sourcePath = node_path.default.join(pendingDir, filename);
|
|
13625
13685
|
if (!node_fs.default.existsSync(sourcePath)) error(`Todo not found: ${filename}`);
|
|
13626
13686
|
node_fs.default.mkdirSync(completedDir, { recursive: true });
|
|
13627
13687
|
let content = node_fs.default.readFileSync(sourcePath, "utf-8");
|
|
13628
|
-
const today = (
|
|
13688
|
+
const today = todayISO();
|
|
13629
13689
|
content = `completed: ${today}\n` + content;
|
|
13630
13690
|
node_fs.default.writeFileSync(node_path.default.join(completedDir, filename), content, "utf-8");
|
|
13631
13691
|
node_fs.default.unlinkSync(sourcePath);
|
|
@@ -13638,7 +13698,7 @@ function cmdTodoComplete(cwd, filename, raw) {
|
|
|
13638
13698
|
function cmdScaffold(cwd, type, options, raw) {
|
|
13639
13699
|
const { phase, name } = options;
|
|
13640
13700
|
const padded = phase ? normalizePhaseName(phase) : "00";
|
|
13641
|
-
const today = (
|
|
13701
|
+
const today = todayISO();
|
|
13642
13702
|
const phaseInfo = phase ? findPhaseInternal(cwd, phase) : null;
|
|
13643
13703
|
const phaseDir = phaseInfo ? node_path.default.join(cwd, phaseInfo.directory) : null;
|
|
13644
13704
|
if (phase && !phaseDir && type !== "phase-dir") error(`Phase ${phase} directory not found`);
|
|
@@ -13660,7 +13720,7 @@ function cmdScaffold(cwd, type, options, raw) {
|
|
|
13660
13720
|
case "phase-dir": {
|
|
13661
13721
|
if (!phase || !name) error("phase and name required for phase-dir scaffold");
|
|
13662
13722
|
const dirName = `${padded}-${generateSlugInternal(name)}`;
|
|
13663
|
-
const phasesParent =
|
|
13723
|
+
const phasesParent = phasesPath(cwd);
|
|
13664
13724
|
node_fs.default.mkdirSync(phasesParent, { recursive: true });
|
|
13665
13725
|
const dirPath = node_path.default.join(phasesParent, dirName);
|
|
13666
13726
|
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
@@ -13853,10 +13913,10 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
13853
13913
|
output({ error: "Cannot read phase directory" }, raw);
|
|
13854
13914
|
return;
|
|
13855
13915
|
}
|
|
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
|
|
13916
|
+
const plans = files.filter((f) => isPlanFile(f));
|
|
13917
|
+
const summaries = files.filter((f) => isSummaryFile(f));
|
|
13918
|
+
const planIds = new Set(plans.map((p) => planId(p)));
|
|
13919
|
+
const summaryIds = new Set(summaries.map((s) => summaryId(s)));
|
|
13860
13920
|
const incompletePlans = [...planIds].filter((id) => !summaryIds.has(id));
|
|
13861
13921
|
if (incompletePlans.length > 0) errors.push(`Plans without summaries: ${incompletePlans.join(", ")}`);
|
|
13862
13922
|
const orphanSummaries = [...summaryIds].filter((id) => !planIds.has(id));
|
|
@@ -14041,11 +14101,11 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
14041
14101
|
}, raw, verified === results.length ? "valid" : "invalid");
|
|
14042
14102
|
}
|
|
14043
14103
|
function cmdValidateConsistency(cwd, raw) {
|
|
14044
|
-
const
|
|
14045
|
-
const phasesDir =
|
|
14104
|
+
const rmPath = roadmapPath(cwd);
|
|
14105
|
+
const phasesDir = phasesPath(cwd);
|
|
14046
14106
|
const errors = [];
|
|
14047
14107
|
const warnings = [];
|
|
14048
|
-
if (!node_fs.default.existsSync(
|
|
14108
|
+
if (!node_fs.default.existsSync(rmPath)) {
|
|
14049
14109
|
errors.push("ROADMAP.md not found");
|
|
14050
14110
|
output({
|
|
14051
14111
|
passed: false,
|
|
@@ -14054,20 +14114,20 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
14054
14114
|
}, raw, "failed");
|
|
14055
14115
|
return;
|
|
14056
14116
|
}
|
|
14057
|
-
const roadmapContent = node_fs.default.readFileSync(
|
|
14117
|
+
const roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14058
14118
|
const roadmapPhases = /* @__PURE__ */ new Set();
|
|
14059
14119
|
const phasePattern = getPhasePattern();
|
|
14060
14120
|
let m;
|
|
14061
14121
|
while ((m = phasePattern.exec(roadmapContent)) !== null) roadmapPhases.add(m[1]);
|
|
14062
14122
|
const diskPhases = /* @__PURE__ */ new Set();
|
|
14063
14123
|
try {
|
|
14064
|
-
const dirs =
|
|
14124
|
+
const dirs = listSubDirs(phasesDir);
|
|
14065
14125
|
for (const dir of dirs) {
|
|
14066
14126
|
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
|
|
14067
14127
|
if (dm) diskPhases.add(dm[1]);
|
|
14068
14128
|
}
|
|
14069
14129
|
} catch (e) {
|
|
14070
|
-
|
|
14130
|
+
debugLog(e);
|
|
14071
14131
|
}
|
|
14072
14132
|
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
14133
|
for (const p of diskPhases) {
|
|
@@ -14077,31 +14137,31 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
14077
14137
|
const integerPhases = [...diskPhases].filter((p) => !p.includes(".")).map((p) => parseInt(p, 10)).sort((a, b) => a - b);
|
|
14078
14138
|
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
14139
|
try {
|
|
14080
|
-
const dirs =
|
|
14140
|
+
const dirs = listSubDirs(phasesDir, true);
|
|
14081
14141
|
for (const dir of dirs) {
|
|
14082
14142
|
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
|
|
14083
|
-
const plans = phaseFiles.filter((f) => f
|
|
14143
|
+
const plans = phaseFiles.filter((f) => isPlanFile(f)).sort();
|
|
14084
14144
|
const planNums = plans.map((p) => {
|
|
14085
14145
|
const pm = p.match(/-(\d{2})-PLAN\.md$/);
|
|
14086
14146
|
return pm ? parseInt(pm[1], 10) : null;
|
|
14087
14147
|
}).filter((n) => n !== null);
|
|
14088
14148
|
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
|
|
14149
|
+
const summaries = phaseFiles.filter((f) => isSummaryFile(f));
|
|
14150
|
+
const planIdsSet = new Set(plans.map((p) => planId(p)));
|
|
14151
|
+
const summaryIdsSet = new Set(summaries.map((s) => summaryId(s)));
|
|
14092
14152
|
for (const sid of summaryIdsSet) if (!planIdsSet.has(sid)) warnings.push(`Summary ${sid}-SUMMARY.md in ${dir} has no matching PLAN.md`);
|
|
14093
14153
|
}
|
|
14094
14154
|
} catch (e) {
|
|
14095
|
-
|
|
14155
|
+
debugLog(e);
|
|
14096
14156
|
}
|
|
14097
14157
|
try {
|
|
14098
|
-
const dirs =
|
|
14158
|
+
const dirs = listSubDirs(phasesDir);
|
|
14099
14159
|
for (const dir of dirs) {
|
|
14100
|
-
const plans = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).filter((f) => f
|
|
14160
|
+
const plans = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).filter((f) => isPlanFile(f));
|
|
14101
14161
|
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
14162
|
}
|
|
14103
14163
|
} catch (e) {
|
|
14104
|
-
|
|
14164
|
+
debugLog(e);
|
|
14105
14165
|
}
|
|
14106
14166
|
const passed = errors.length === 0;
|
|
14107
14167
|
output({
|
|
@@ -14112,12 +14172,12 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
14112
14172
|
}, raw, passed ? "passed" : "failed");
|
|
14113
14173
|
}
|
|
14114
14174
|
function cmdValidateHealth(cwd, options, raw) {
|
|
14115
|
-
const planningDir =
|
|
14116
|
-
const projectPath =
|
|
14117
|
-
const
|
|
14118
|
-
const
|
|
14119
|
-
const
|
|
14120
|
-
const phasesDir =
|
|
14175
|
+
const planningDir = planningPath(cwd);
|
|
14176
|
+
const projectPath = planningPath(cwd, "PROJECT.md");
|
|
14177
|
+
const rmPath = roadmapPath(cwd);
|
|
14178
|
+
const stPath = statePath(cwd);
|
|
14179
|
+
const cfgPath = configPath(cwd);
|
|
14180
|
+
const phasesDir = phasesPath(cwd);
|
|
14121
14181
|
const errors = [];
|
|
14122
14182
|
const warnings = [];
|
|
14123
14183
|
const info = [];
|
|
@@ -14153,21 +14213,20 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14153
14213
|
"## Requirements"
|
|
14154
14214
|
]) if (!content.includes(section)) addIssue("warning", "W001", `PROJECT.md missing section: ${section}`, "Add section manually");
|
|
14155
14215
|
}
|
|
14156
|
-
if (!node_fs.default.existsSync(
|
|
14157
|
-
if (!node_fs.default.existsSync(
|
|
14216
|
+
if (!node_fs.default.existsSync(rmPath)) addIssue("error", "E003", "ROADMAP.md not found", "Run /maxsim:new-milestone to create roadmap");
|
|
14217
|
+
if (!node_fs.default.existsSync(stPath)) {
|
|
14158
14218
|
addIssue("error", "E004", "STATE.md not found", "Run /maxsim:health --repair to regenerate", true);
|
|
14159
14219
|
repairs.push("regenerateState");
|
|
14160
14220
|
} else {
|
|
14161
|
-
const phaseRefs = [...node_fs.default.readFileSync(
|
|
14221
|
+
const phaseRefs = [...node_fs.default.readFileSync(stPath, "utf-8").matchAll(/[Pp]hase\s+(\d+(?:\.\d+)?)/g)].map((m) => m[1]);
|
|
14162
14222
|
const diskPhases = /* @__PURE__ */ new Set();
|
|
14163
14223
|
try {
|
|
14164
|
-
const
|
|
14165
|
-
|
|
14166
|
-
const dm = e.name.match(/^(\d+(?:\.\d+)?)/);
|
|
14224
|
+
for (const dir of listSubDirs(phasesDir)) {
|
|
14225
|
+
const dm = dir.match(/^(\d+(?:\.\d+)?)/);
|
|
14167
14226
|
if (dm) diskPhases.add(dm[1]);
|
|
14168
14227
|
}
|
|
14169
14228
|
} catch (e) {
|
|
14170
|
-
|
|
14229
|
+
debugLog(e);
|
|
14171
14230
|
}
|
|
14172
14231
|
for (const ref of phaseRefs) {
|
|
14173
14232
|
const normalizedRef = String(parseInt(ref, 10)).padStart(2, "0");
|
|
@@ -14179,11 +14238,11 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14179
14238
|
}
|
|
14180
14239
|
}
|
|
14181
14240
|
}
|
|
14182
|
-
if (!node_fs.default.existsSync(
|
|
14241
|
+
if (!node_fs.default.existsSync(cfgPath)) {
|
|
14183
14242
|
addIssue("warning", "W003", "config.json not found", "Run /maxsim:health --repair to create with defaults", true);
|
|
14184
14243
|
repairs.push("createConfig");
|
|
14185
14244
|
} else try {
|
|
14186
|
-
const rawContent = node_fs.default.readFileSync(
|
|
14245
|
+
const rawContent = node_fs.default.readFileSync(cfgPath, "utf-8");
|
|
14187
14246
|
const parsed = JSON.parse(rawContent);
|
|
14188
14247
|
const validProfiles = [
|
|
14189
14248
|
"quality",
|
|
@@ -14197,42 +14256,39 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14197
14256
|
repairs.push("resetConfig");
|
|
14198
14257
|
}
|
|
14199
14258
|
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)");
|
|
14259
|
+
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
14260
|
} catch (e) {
|
|
14203
|
-
|
|
14261
|
+
debugLog(e);
|
|
14204
14262
|
}
|
|
14205
14263
|
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", "")));
|
|
14264
|
+
const orphanDirs = listSubDirs(phasesDir);
|
|
14265
|
+
for (const dirName of orphanDirs) {
|
|
14266
|
+
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dirName));
|
|
14267
|
+
const plans = phaseFiles.filter((f) => isPlanFile(f));
|
|
14268
|
+
const summaries = phaseFiles.filter((f) => isSummaryFile(f));
|
|
14269
|
+
const summaryBases = new Set(summaries.map((s) => summaryId(s)));
|
|
14213
14270
|
for (const plan of plans) {
|
|
14214
|
-
const planBase = plan
|
|
14215
|
-
if (!summaryBases.has(planBase)) addIssue("info", "I001", `${
|
|
14271
|
+
const planBase = planId(plan);
|
|
14272
|
+
if (!summaryBases.has(planBase)) addIssue("info", "I001", `${dirName}/${plan} has no SUMMARY.md`, "May be in progress");
|
|
14216
14273
|
}
|
|
14217
14274
|
}
|
|
14218
14275
|
} catch (e) {
|
|
14219
|
-
|
|
14276
|
+
debugLog(e);
|
|
14220
14277
|
}
|
|
14221
|
-
if (node_fs.default.existsSync(
|
|
14222
|
-
const roadmapContent = node_fs.default.readFileSync(
|
|
14278
|
+
if (node_fs.default.existsSync(rmPath)) {
|
|
14279
|
+
const roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14223
14280
|
const roadmapPhases = /* @__PURE__ */ new Set();
|
|
14224
14281
|
const phasePattern = getPhasePattern();
|
|
14225
14282
|
let m;
|
|
14226
14283
|
while ((m = phasePattern.exec(roadmapContent)) !== null) roadmapPhases.add(m[1]);
|
|
14227
14284
|
const diskPhases = /* @__PURE__ */ new Set();
|
|
14228
14285
|
try {
|
|
14229
|
-
const
|
|
14230
|
-
|
|
14231
|
-
const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
|
|
14286
|
+
for (const dir of listSubDirs(phasesDir)) {
|
|
14287
|
+
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
|
|
14232
14288
|
if (dm) diskPhases.add(dm[1]);
|
|
14233
14289
|
}
|
|
14234
14290
|
} catch (e) {
|
|
14235
|
-
|
|
14291
|
+
debugLog(e);
|
|
14236
14292
|
}
|
|
14237
14293
|
for (const p of roadmapPhases) {
|
|
14238
14294
|
const padded = String(parseInt(p, 10)).padStart(2, "0");
|
|
@@ -14248,7 +14304,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14248
14304
|
switch (repair) {
|
|
14249
14305
|
case "createConfig":
|
|
14250
14306
|
case "resetConfig":
|
|
14251
|
-
node_fs.default.writeFileSync(
|
|
14307
|
+
node_fs.default.writeFileSync(cfgPath, JSON.stringify({
|
|
14252
14308
|
model_profile: "balanced",
|
|
14253
14309
|
commit_docs: true,
|
|
14254
14310
|
search_gitignored: false,
|
|
@@ -14265,9 +14321,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14265
14321
|
});
|
|
14266
14322
|
break;
|
|
14267
14323
|
case "regenerateState": {
|
|
14268
|
-
if (node_fs.default.existsSync(
|
|
14269
|
-
const backupPath = `${
|
|
14270
|
-
node_fs.default.copyFileSync(
|
|
14324
|
+
if (node_fs.default.existsSync(stPath)) {
|
|
14325
|
+
const backupPath = `${stPath}.bak-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
|
|
14326
|
+
node_fs.default.copyFileSync(stPath, backupPath);
|
|
14271
14327
|
repairActions.push({
|
|
14272
14328
|
action: "backupState",
|
|
14273
14329
|
success: true,
|
|
@@ -14283,8 +14339,8 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14283
14339
|
stateContent += `**Current phase:** (determining...)\n`;
|
|
14284
14340
|
stateContent += `**Status:** Resuming\n\n`;
|
|
14285
14341
|
stateContent += `## Session Log\n\n`;
|
|
14286
|
-
stateContent += `- ${(
|
|
14287
|
-
node_fs.default.writeFileSync(
|
|
14342
|
+
stateContent += `- ${todayISO()}: STATE.md regenerated by /maxsim:health --repair\n`;
|
|
14343
|
+
node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
|
|
14288
14344
|
repairActions.push({
|
|
14289
14345
|
action: repair,
|
|
14290
14346
|
success: true,
|
|
@@ -14323,10 +14379,170 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
14323
14379
|
*
|
|
14324
14380
|
* Ported from maxsim/bin/lib/phase.cjs
|
|
14325
14381
|
*/
|
|
14382
|
+
function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
14383
|
+
const today = todayISO();
|
|
14384
|
+
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`);
|
|
14385
|
+
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`);
|
|
14386
|
+
}
|
|
14387
|
+
function phaseAddCore(cwd, description, options) {
|
|
14388
|
+
const rmPath = roadmapPath(cwd);
|
|
14389
|
+
if (!node_fs.default.existsSync(rmPath)) throw new Error("ROADMAP.md not found");
|
|
14390
|
+
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14391
|
+
const slug = generateSlugInternal(description);
|
|
14392
|
+
const phasePattern = getPhasePattern();
|
|
14393
|
+
let maxPhase = 0;
|
|
14394
|
+
let m;
|
|
14395
|
+
while ((m = phasePattern.exec(content)) !== null) {
|
|
14396
|
+
const num = parseInt(m[1], 10);
|
|
14397
|
+
if (num > maxPhase) maxPhase = num;
|
|
14398
|
+
}
|
|
14399
|
+
const newPhaseNum = maxPhase + 1;
|
|
14400
|
+
const paddedNum = String(newPhaseNum).padStart(2, "0");
|
|
14401
|
+
const dirName = `${paddedNum}-${slug}`;
|
|
14402
|
+
const dirPath = planningPath(cwd, "phases", dirName);
|
|
14403
|
+
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
14404
|
+
node_fs.default.writeFileSync(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14405
|
+
if (options?.includeStubs) scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
14406
|
+
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`;
|
|
14407
|
+
let updatedContent;
|
|
14408
|
+
const lastSeparator = content.lastIndexOf("\n---");
|
|
14409
|
+
if (lastSeparator > 0) updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
|
|
14410
|
+
else updatedContent = content + phaseEntry;
|
|
14411
|
+
node_fs.default.writeFileSync(rmPath, updatedContent, "utf-8");
|
|
14412
|
+
return {
|
|
14413
|
+
phase_number: newPhaseNum,
|
|
14414
|
+
padded: paddedNum,
|
|
14415
|
+
slug,
|
|
14416
|
+
directory: `.planning/phases/${dirName}`,
|
|
14417
|
+
description
|
|
14418
|
+
};
|
|
14419
|
+
}
|
|
14420
|
+
function phaseInsertCore(cwd, afterPhase, 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 afterPhaseEscaped = "0*" + normalizePhaseName(afterPhase).replace(/^0+/, "").replace(/\./g, "\\.");
|
|
14426
|
+
if (!getPhasePattern(afterPhaseEscaped, "i").test(content)) throw new Error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
14427
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14428
|
+
const normalizedBase = normalizePhaseName(afterPhase);
|
|
14429
|
+
const existingDecimals = [];
|
|
14430
|
+
try {
|
|
14431
|
+
const dirs = listSubDirs(phasesDirPath);
|
|
14432
|
+
const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
|
|
14433
|
+
for (const dir of dirs) {
|
|
14434
|
+
const dm = dir.match(decimalPattern);
|
|
14435
|
+
if (dm) existingDecimals.push(parseInt(dm[1], 10));
|
|
14436
|
+
}
|
|
14437
|
+
} catch (e) {
|
|
14438
|
+
debugLog(e);
|
|
14439
|
+
}
|
|
14440
|
+
const decimalPhase = `${normalizedBase}.${existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1}`;
|
|
14441
|
+
const dirName = `${decimalPhase}-${slug}`;
|
|
14442
|
+
const dirPath = planningPath(cwd, "phases", dirName);
|
|
14443
|
+
node_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
14444
|
+
node_fs.default.writeFileSync(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14445
|
+
if (options?.includeStubs) scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
14446
|
+
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`;
|
|
14447
|
+
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, "i");
|
|
14448
|
+
const headerMatch = content.match(headerPattern);
|
|
14449
|
+
if (!headerMatch) throw new Error(`Could not find Phase ${afterPhase} header`);
|
|
14450
|
+
const headerIdx = content.indexOf(headerMatch[0]);
|
|
14451
|
+
const nextPhaseMatch = content.slice(headerIdx + headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
14452
|
+
let insertIdx;
|
|
14453
|
+
if (nextPhaseMatch) insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
14454
|
+
else insertIdx = content.length;
|
|
14455
|
+
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
14456
|
+
node_fs.default.writeFileSync(rmPath, updatedContent, "utf-8");
|
|
14457
|
+
return {
|
|
14458
|
+
phase_number: decimalPhase,
|
|
14459
|
+
after_phase: afterPhase,
|
|
14460
|
+
slug,
|
|
14461
|
+
directory: `.planning/phases/${dirName}`,
|
|
14462
|
+
description
|
|
14463
|
+
};
|
|
14464
|
+
}
|
|
14465
|
+
function phaseCompleteCore(cwd, phaseNum) {
|
|
14466
|
+
const rmPath = roadmapPath(cwd);
|
|
14467
|
+
const stPath = statePath(cwd);
|
|
14468
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14469
|
+
const today = todayISO();
|
|
14470
|
+
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
14471
|
+
if (!phaseInfo) throw new Error(`Phase ${phaseNum} not found`);
|
|
14472
|
+
const planCount = phaseInfo.plans.length;
|
|
14473
|
+
const summaryCount = phaseInfo.summaries.length;
|
|
14474
|
+
let requirementsUpdated = false;
|
|
14475
|
+
if (node_fs.default.existsSync(rmPath)) {
|
|
14476
|
+
let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14477
|
+
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${escapePhaseNum(phaseNum)}[:\\s][^\\n]*)`, "i");
|
|
14478
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
14479
|
+
const phaseEscaped = escapePhaseNum(phaseNum);
|
|
14480
|
+
const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i");
|
|
14481
|
+
roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
|
|
14482
|
+
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i");
|
|
14483
|
+
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
14484
|
+
node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
|
|
14485
|
+
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
14486
|
+
if (node_fs.default.existsSync(reqPath)) {
|
|
14487
|
+
const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${escapePhaseNum(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, "i"));
|
|
14488
|
+
if (reqMatch) {
|
|
14489
|
+
const reqIds = reqMatch[1].replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
|
|
14490
|
+
let reqContent = node_fs.default.readFileSync(reqPath, "utf-8");
|
|
14491
|
+
for (const reqId of reqIds) {
|
|
14492
|
+
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, "gi"), "$1x$2");
|
|
14493
|
+
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, "gi"), "$1 Complete $2");
|
|
14494
|
+
}
|
|
14495
|
+
node_fs.default.writeFileSync(reqPath, reqContent, "utf-8");
|
|
14496
|
+
requirementsUpdated = true;
|
|
14497
|
+
}
|
|
14498
|
+
}
|
|
14499
|
+
}
|
|
14500
|
+
let nextPhaseNum = null;
|
|
14501
|
+
let nextPhaseName = null;
|
|
14502
|
+
let isLastPhase = true;
|
|
14503
|
+
try {
|
|
14504
|
+
const dirs = listSubDirs(phasesDirPath, true);
|
|
14505
|
+
for (const dir of dirs) {
|
|
14506
|
+
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
14507
|
+
if (dm) {
|
|
14508
|
+
if (comparePhaseNum(dm[1], phaseNum) > 0) {
|
|
14509
|
+
nextPhaseNum = dm[1];
|
|
14510
|
+
nextPhaseName = dm[2] || null;
|
|
14511
|
+
isLastPhase = false;
|
|
14512
|
+
break;
|
|
14513
|
+
}
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
} catch (e) {
|
|
14517
|
+
debugLog(e);
|
|
14518
|
+
}
|
|
14519
|
+
if (node_fs.default.existsSync(stPath)) {
|
|
14520
|
+
let stateContent = node_fs.default.readFileSync(stPath, "utf-8");
|
|
14521
|
+
stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
|
|
14522
|
+
if (nextPhaseName) stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, " ")}`);
|
|
14523
|
+
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? "Milestone complete" : "Ready to plan"}`);
|
|
14524
|
+
stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
|
|
14525
|
+
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
14526
|
+
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ""}`);
|
|
14527
|
+
node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
|
|
14528
|
+
}
|
|
14529
|
+
return {
|
|
14530
|
+
completed_phase: phaseNum,
|
|
14531
|
+
phase_name: phaseInfo.phase_name,
|
|
14532
|
+
plans_executed: `${summaryCount}/${planCount}`,
|
|
14533
|
+
next_phase: nextPhaseNum,
|
|
14534
|
+
next_phase_name: nextPhaseName,
|
|
14535
|
+
is_last_phase: isLastPhase,
|
|
14536
|
+
date: today,
|
|
14537
|
+
roadmap_updated: node_fs.default.existsSync(rmPath),
|
|
14538
|
+
state_updated: node_fs.default.existsSync(stPath),
|
|
14539
|
+
requirements_updated: requirementsUpdated
|
|
14540
|
+
};
|
|
14541
|
+
}
|
|
14326
14542
|
function cmdPhasesList(cwd, options, raw) {
|
|
14327
|
-
const
|
|
14543
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14328
14544
|
const { type, phase, includeArchived } = options;
|
|
14329
|
-
if (!node_fs.default.existsSync(
|
|
14545
|
+
if (!node_fs.default.existsSync(phasesDirPath)) {
|
|
14330
14546
|
if (type) output({
|
|
14331
14547
|
files: [],
|
|
14332
14548
|
count: 0
|
|
@@ -14338,7 +14554,7 @@ function cmdPhasesList(cwd, options, raw) {
|
|
|
14338
14554
|
return;
|
|
14339
14555
|
}
|
|
14340
14556
|
try {
|
|
14341
|
-
let dirs =
|
|
14557
|
+
let dirs = listSubDirs(phasesDirPath);
|
|
14342
14558
|
if (includeArchived) {
|
|
14343
14559
|
const archived = getArchivedPhaseDirs(cwd);
|
|
14344
14560
|
for (const a of archived) dirs.push(`${a.name} [${a.milestone}]`);
|
|
@@ -14361,11 +14577,11 @@ function cmdPhasesList(cwd, options, raw) {
|
|
|
14361
14577
|
if (type) {
|
|
14362
14578
|
const files = [];
|
|
14363
14579
|
for (const dir of dirs) {
|
|
14364
|
-
const dirPath = node_path.default.join(
|
|
14580
|
+
const dirPath = node_path.default.join(phasesDirPath, dir);
|
|
14365
14581
|
const dirFiles = node_fs.default.readdirSync(dirPath);
|
|
14366
14582
|
let filtered;
|
|
14367
|
-
if (type === "plans") filtered = dirFiles.filter(
|
|
14368
|
-
else if (type === "summaries") filtered = dirFiles.filter(
|
|
14583
|
+
if (type === "plans") filtered = dirFiles.filter(isPlanFile);
|
|
14584
|
+
else if (type === "summaries") filtered = dirFiles.filter(isSummaryFile);
|
|
14369
14585
|
else filtered = dirFiles;
|
|
14370
14586
|
files.push(...filtered.sort());
|
|
14371
14587
|
}
|
|
@@ -14385,9 +14601,9 @@ function cmdPhasesList(cwd, options, raw) {
|
|
|
14385
14601
|
}
|
|
14386
14602
|
}
|
|
14387
14603
|
function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
14388
|
-
const
|
|
14604
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14389
14605
|
const normalized = normalizePhaseName(basePhase);
|
|
14390
|
-
if (!node_fs.default.existsSync(
|
|
14606
|
+
if (!node_fs.default.existsSync(phasesDirPath)) {
|
|
14391
14607
|
output({
|
|
14392
14608
|
found: false,
|
|
14393
14609
|
base_phase: normalized,
|
|
@@ -14397,7 +14613,7 @@ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
|
14397
14613
|
return;
|
|
14398
14614
|
}
|
|
14399
14615
|
try {
|
|
14400
|
-
const dirs =
|
|
14616
|
+
const dirs = listSubDirs(phasesDirPath);
|
|
14401
14617
|
const baseExists = dirs.some((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
14402
14618
|
const decimalPattern = new RegExp(`^${normalized}\\.(\\d+)`);
|
|
14403
14619
|
const existingDecimals = [];
|
|
@@ -14426,7 +14642,7 @@ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
|
14426
14642
|
}
|
|
14427
14643
|
function cmdFindPhase(cwd, phase, raw) {
|
|
14428
14644
|
if (!phase) error("phase identifier required");
|
|
14429
|
-
const
|
|
14645
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14430
14646
|
const normalized = normalizePhaseName(phase);
|
|
14431
14647
|
const notFound = {
|
|
14432
14648
|
found: false,
|
|
@@ -14437,7 +14653,7 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
14437
14653
|
summaries: []
|
|
14438
14654
|
};
|
|
14439
14655
|
try {
|
|
14440
|
-
const match =
|
|
14656
|
+
const match = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized));
|
|
14441
14657
|
if (!match) {
|
|
14442
14658
|
output(notFound, raw, "");
|
|
14443
14659
|
return;
|
|
@@ -14445,10 +14661,10 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
14445
14661
|
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
14446
14662
|
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
14447
14663
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
14448
|
-
const phaseDir = node_path.default.join(
|
|
14664
|
+
const phaseDir = node_path.default.join(phasesDirPath, match);
|
|
14449
14665
|
const phaseFiles = node_fs.default.readdirSync(phaseDir);
|
|
14450
|
-
const plans = phaseFiles.filter(
|
|
14451
|
-
const summaries = phaseFiles.filter(
|
|
14666
|
+
const plans = phaseFiles.filter(isPlanFile).sort();
|
|
14667
|
+
const summaries = phaseFiles.filter(isSummaryFile).sort();
|
|
14452
14668
|
const result = {
|
|
14453
14669
|
found: true,
|
|
14454
14670
|
directory: node_path.default.join(".planning", "phases", match),
|
|
@@ -14464,14 +14680,14 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
14464
14680
|
}
|
|
14465
14681
|
function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
14466
14682
|
if (!phase) error("phase required for phase-plan-index");
|
|
14467
|
-
const
|
|
14683
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14468
14684
|
const normalized = normalizePhaseName(phase);
|
|
14469
14685
|
let phaseDir = null;
|
|
14470
14686
|
try {
|
|
14471
|
-
const match =
|
|
14472
|
-
if (match) phaseDir = node_path.default.join(
|
|
14687
|
+
const match = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized));
|
|
14688
|
+
if (match) phaseDir = node_path.default.join(phasesDirPath, match);
|
|
14473
14689
|
} catch (e) {
|
|
14474
|
-
|
|
14690
|
+
debugLog(e);
|
|
14475
14691
|
}
|
|
14476
14692
|
if (!phaseDir) {
|
|
14477
14693
|
output({
|
|
@@ -14485,15 +14701,15 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14485
14701
|
return;
|
|
14486
14702
|
}
|
|
14487
14703
|
const phaseFiles = node_fs.default.readdirSync(phaseDir);
|
|
14488
|
-
const planFiles = phaseFiles.filter(
|
|
14489
|
-
const summaryFiles = phaseFiles.filter(
|
|
14490
|
-
const completedPlanIds = new Set(summaryFiles.map(
|
|
14704
|
+
const planFiles = phaseFiles.filter(isPlanFile).sort();
|
|
14705
|
+
const summaryFiles = phaseFiles.filter(isSummaryFile);
|
|
14706
|
+
const completedPlanIds = new Set(summaryFiles.map(summaryId));
|
|
14491
14707
|
const plans = [];
|
|
14492
14708
|
const waves = {};
|
|
14493
14709
|
const incomplete = [];
|
|
14494
14710
|
let hasCheckpoints = false;
|
|
14495
14711
|
for (const planFile of planFiles) {
|
|
14496
|
-
const
|
|
14712
|
+
const id = planId(planFile);
|
|
14497
14713
|
const planPath = node_path.default.join(phaseDir, planFile);
|
|
14498
14714
|
const content = node_fs.default.readFileSync(planPath, "utf-8");
|
|
14499
14715
|
const fm = extractFrontmatter(content);
|
|
@@ -14504,10 +14720,10 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14504
14720
|
if (!autonomous) hasCheckpoints = true;
|
|
14505
14721
|
let filesModified = [];
|
|
14506
14722
|
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(
|
|
14723
|
+
const hasSummary = completedPlanIds.has(id);
|
|
14724
|
+
if (!hasSummary) incomplete.push(id);
|
|
14509
14725
|
const plan = {
|
|
14510
|
-
id
|
|
14726
|
+
id,
|
|
14511
14727
|
wave,
|
|
14512
14728
|
autonomous,
|
|
14513
14729
|
objective: fm.objective || null,
|
|
@@ -14518,7 +14734,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14518
14734
|
plans.push(plan);
|
|
14519
14735
|
const waveKey = String(wave);
|
|
14520
14736
|
if (!waves[waveKey]) waves[waveKey] = [];
|
|
14521
|
-
waves[waveKey].push(
|
|
14737
|
+
waves[waveKey].push(id);
|
|
14522
14738
|
}
|
|
14523
14739
|
output({
|
|
14524
14740
|
phase: normalized,
|
|
@@ -14530,102 +14746,54 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
14530
14746
|
}
|
|
14531
14747
|
function cmdPhaseAdd(cwd, description, raw) {
|
|
14532
14748
|
if (!description) error("description required for phase add");
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14749
|
+
try {
|
|
14750
|
+
const result = phaseAddCore(cwd, description, { includeStubs: false });
|
|
14751
|
+
output({
|
|
14752
|
+
phase_number: result.phase_number,
|
|
14753
|
+
padded: result.padded,
|
|
14754
|
+
name: result.description,
|
|
14755
|
+
slug: result.slug,
|
|
14756
|
+
directory: result.directory
|
|
14757
|
+
}, raw, result.padded);
|
|
14758
|
+
} catch (e) {
|
|
14759
|
+
error(e.message);
|
|
14543
14760
|
}
|
|
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
14761
|
}
|
|
14564
14762
|
function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
14565
14763
|
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
14764
|
try {
|
|
14576
|
-
const
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14765
|
+
const result = phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
14766
|
+
output({
|
|
14767
|
+
phase_number: result.phase_number,
|
|
14768
|
+
after_phase: result.after_phase,
|
|
14769
|
+
name: result.description,
|
|
14770
|
+
slug: result.slug,
|
|
14771
|
+
directory: result.directory
|
|
14772
|
+
}, raw, result.phase_number);
|
|
14582
14773
|
} catch (e) {
|
|
14583
|
-
|
|
14774
|
+
error(e.message);
|
|
14584
14775
|
}
|
|
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
14776
|
}
|
|
14609
14777
|
function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
14610
14778
|
if (!targetPhase) error("phase number required for phase remove");
|
|
14611
|
-
const
|
|
14612
|
-
const
|
|
14779
|
+
const rmPath = roadmapPath(cwd);
|
|
14780
|
+
const phasesDirPath = phasesPath(cwd);
|
|
14613
14781
|
const force = options.force || false;
|
|
14614
|
-
if (!node_fs.default.existsSync(
|
|
14782
|
+
if (!node_fs.default.existsSync(rmPath)) error("ROADMAP.md not found");
|
|
14615
14783
|
const normalized = normalizePhaseName(targetPhase);
|
|
14616
14784
|
const isDecimal = targetPhase.includes(".");
|
|
14617
14785
|
let targetDir = null;
|
|
14618
14786
|
try {
|
|
14619
|
-
targetDir =
|
|
14787
|
+
targetDir = listSubDirs(phasesDirPath, true).find((d) => d.startsWith(normalized + "-") || d === normalized) || null;
|
|
14620
14788
|
} catch (e) {
|
|
14621
|
-
|
|
14789
|
+
debugLog(e);
|
|
14622
14790
|
}
|
|
14623
14791
|
if (targetDir && !force) {
|
|
14624
|
-
const targetPath = node_path.default.join(
|
|
14625
|
-
const summaries = node_fs.default.readdirSync(targetPath).filter(
|
|
14792
|
+
const targetPath = node_path.default.join(phasesDirPath, targetDir);
|
|
14793
|
+
const summaries = node_fs.default.readdirSync(targetPath).filter(isSummaryFile);
|
|
14626
14794
|
if (summaries.length > 0) error(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
|
|
14627
14795
|
}
|
|
14628
|
-
if (targetDir) node_fs.default.rmSync(node_path.default.join(
|
|
14796
|
+
if (targetDir) node_fs.default.rmSync(node_path.default.join(phasesDirPath, targetDir), {
|
|
14629
14797
|
recursive: true,
|
|
14630
14798
|
force: true
|
|
14631
14799
|
});
|
|
@@ -14636,7 +14804,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14636
14804
|
const baseInt = baseParts[0];
|
|
14637
14805
|
const removedDecimal = parseInt(baseParts[1], 10);
|
|
14638
14806
|
try {
|
|
14639
|
-
const dirs =
|
|
14807
|
+
const dirs = listSubDirs(phasesDirPath, true);
|
|
14640
14808
|
const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
|
|
14641
14809
|
const toRename = [];
|
|
14642
14810
|
for (const dir of dirs) {
|
|
@@ -14653,15 +14821,15 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14653
14821
|
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
14654
14822
|
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
14655
14823
|
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
14656
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14824
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, item.dir), node_path.default.join(phasesDirPath, newDirName));
|
|
14657
14825
|
renamedDirs.push({
|
|
14658
14826
|
from: item.dir,
|
|
14659
14827
|
to: newDirName
|
|
14660
14828
|
});
|
|
14661
|
-
const dirFiles = node_fs.default.readdirSync(node_path.default.join(
|
|
14829
|
+
const dirFiles = node_fs.default.readdirSync(node_path.default.join(phasesDirPath, newDirName));
|
|
14662
14830
|
for (const f of dirFiles) if (f.includes(oldPhaseId)) {
|
|
14663
14831
|
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
14664
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14832
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, newDirName, f), node_path.default.join(phasesDirPath, newDirName, newFileName));
|
|
14665
14833
|
renamedFiles.push({
|
|
14666
14834
|
from: f,
|
|
14667
14835
|
to: newFileName
|
|
@@ -14669,12 +14837,12 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14669
14837
|
}
|
|
14670
14838
|
}
|
|
14671
14839
|
} catch (e) {
|
|
14672
|
-
|
|
14840
|
+
debugLog(e);
|
|
14673
14841
|
}
|
|
14674
14842
|
} else {
|
|
14675
14843
|
const removedInt = parseInt(normalized, 10);
|
|
14676
14844
|
try {
|
|
14677
|
-
const dirs =
|
|
14845
|
+
const dirs = listSubDirs(phasesDirPath, true);
|
|
14678
14846
|
const toRename = [];
|
|
14679
14847
|
for (const dir of dirs) {
|
|
14680
14848
|
const dm = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
|
@@ -14701,15 +14869,15 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14701
14869
|
const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
|
|
14702
14870
|
const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
|
|
14703
14871
|
const newDirName = `${newPrefix}-${item.slug}`;
|
|
14704
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14872
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, item.dir), node_path.default.join(phasesDirPath, newDirName));
|
|
14705
14873
|
renamedDirs.push({
|
|
14706
14874
|
from: item.dir,
|
|
14707
14875
|
to: newDirName
|
|
14708
14876
|
});
|
|
14709
|
-
const dirFiles = node_fs.default.readdirSync(node_path.default.join(
|
|
14877
|
+
const dirFiles = node_fs.default.readdirSync(node_path.default.join(phasesDirPath, newDirName));
|
|
14710
14878
|
for (const f of dirFiles) if (f.startsWith(oldPrefix)) {
|
|
14711
14879
|
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
14712
|
-
node_fs.default.renameSync(node_path.default.join(
|
|
14880
|
+
node_fs.default.renameSync(node_path.default.join(phasesDirPath, newDirName, f), node_path.default.join(phasesDirPath, newDirName, newFileName));
|
|
14713
14881
|
renamedFiles.push({
|
|
14714
14882
|
from: f,
|
|
14715
14883
|
to: newFileName
|
|
@@ -14717,11 +14885,11 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14717
14885
|
}
|
|
14718
14886
|
}
|
|
14719
14887
|
} catch (e) {
|
|
14720
|
-
|
|
14888
|
+
debugLog(e);
|
|
14721
14889
|
}
|
|
14722
14890
|
}
|
|
14723
|
-
let roadmapContent = node_fs.default.readFileSync(
|
|
14724
|
-
const targetEscaped = targetPhase
|
|
14891
|
+
let roadmapContent = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
14892
|
+
const targetEscaped = escapePhaseNum(targetPhase);
|
|
14725
14893
|
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`, "i");
|
|
14726
14894
|
roadmapContent = roadmapContent.replace(sectionPattern, "");
|
|
14727
14895
|
const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`, "gi");
|
|
@@ -14743,10 +14911,10 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14743
14911
|
roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, "gi"), `$1${newStr}`);
|
|
14744
14912
|
}
|
|
14745
14913
|
}
|
|
14746
|
-
node_fs.default.writeFileSync(
|
|
14747
|
-
const
|
|
14748
|
-
if (node_fs.default.existsSync(
|
|
14749
|
-
let stateContent = node_fs.default.readFileSync(
|
|
14914
|
+
node_fs.default.writeFileSync(rmPath, roadmapContent, "utf-8");
|
|
14915
|
+
const stPath = statePath(cwd);
|
|
14916
|
+
if (node_fs.default.existsSync(stPath)) {
|
|
14917
|
+
let stateContent = node_fs.default.readFileSync(stPath, "utf-8");
|
|
14750
14918
|
const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
|
|
14751
14919
|
const totalMatch = stateContent.match(totalPattern);
|
|
14752
14920
|
if (totalMatch) {
|
|
@@ -14759,7 +14927,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14759
14927
|
const oldTotal = parseInt(ofMatch[2], 10);
|
|
14760
14928
|
stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
|
|
14761
14929
|
}
|
|
14762
|
-
node_fs.default.writeFileSync(
|
|
14930
|
+
node_fs.default.writeFileSync(stPath, stateContent, "utf-8");
|
|
14763
14931
|
}
|
|
14764
14932
|
output({
|
|
14765
14933
|
removed: targetPhase,
|
|
@@ -14767,84 +14935,27 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
14767
14935
|
renamed_directories: renamedDirs,
|
|
14768
14936
|
renamed_files: renamedFiles,
|
|
14769
14937
|
roadmap_updated: true,
|
|
14770
|
-
state_updated: node_fs.default.existsSync(
|
|
14938
|
+
state_updated: node_fs.default.existsSync(stPath)
|
|
14771
14939
|
}, raw);
|
|
14772
14940
|
}
|
|
14773
14941
|
function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
14774
14942
|
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
14943
|
try {
|
|
14812
|
-
const
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
}
|
|
14944
|
+
const result = phaseCompleteCore(cwd, phaseNum);
|
|
14945
|
+
output({
|
|
14946
|
+
completed_phase: result.completed_phase,
|
|
14947
|
+
phase_name: result.phase_name,
|
|
14948
|
+
plans_executed: result.plans_executed,
|
|
14949
|
+
next_phase: result.next_phase,
|
|
14950
|
+
next_phase_name: result.next_phase_name,
|
|
14951
|
+
is_last_phase: result.is_last_phase,
|
|
14952
|
+
date: result.date,
|
|
14953
|
+
roadmap_updated: result.roadmap_updated,
|
|
14954
|
+
state_updated: result.state_updated
|
|
14955
|
+
}, raw);
|
|
14824
14956
|
} 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");
|
|
14957
|
+
error(e.message);
|
|
14836
14958
|
}
|
|
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
14959
|
}
|
|
14849
14960
|
|
|
14850
14961
|
//#endregion
|
|
@@ -14902,7 +15013,7 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
|
14902
15013
|
return;
|
|
14903
15014
|
}
|
|
14904
15015
|
const padded = normalizePhaseName(options.phase);
|
|
14905
|
-
const today = (
|
|
15016
|
+
const today = todayISO();
|
|
14906
15017
|
const phaseName = options.name || phaseInfo.phase_name || "Unnamed";
|
|
14907
15018
|
const phaseId = `${padded}-${phaseInfo.phase_slug || generateSlugInternal(phaseName)}`;
|
|
14908
15019
|
const planNum = (options.plan || "01").padStart(2, "0");
|
|
@@ -15094,7 +15205,7 @@ function scanPhaseArtifacts(cwd, phaseDirectory) {
|
|
|
15094
15205
|
const uatFile = files.find((f) => f.endsWith("-UAT.md") || f === "UAT.md");
|
|
15095
15206
|
if (uatFile) result.uat_path = node_path.default.join(phaseDirectory, uatFile);
|
|
15096
15207
|
} catch (e) {
|
|
15097
|
-
|
|
15208
|
+
debugLog(e);
|
|
15098
15209
|
}
|
|
15099
15210
|
return result;
|
|
15100
15211
|
}
|
|
@@ -15193,7 +15304,7 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
15193
15304
|
]
|
|
15194
15305
|
}).trim().length > 0;
|
|
15195
15306
|
} catch (e) {
|
|
15196
|
-
|
|
15307
|
+
debugLog(e);
|
|
15197
15308
|
}
|
|
15198
15309
|
hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
|
|
15199
15310
|
output({
|
|
@@ -15236,13 +15347,13 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
15236
15347
|
const config = loadConfig(cwd);
|
|
15237
15348
|
const now = /* @__PURE__ */ new Date();
|
|
15238
15349
|
const slug = description ? generateSlugInternal(description)?.substring(0, 40) ?? null : null;
|
|
15239
|
-
const quickDir =
|
|
15350
|
+
const quickDir = planningPath(cwd, "quick");
|
|
15240
15351
|
let nextNum = 1;
|
|
15241
15352
|
try {
|
|
15242
15353
|
const existing = node_fs.default.readdirSync(quickDir).filter((f) => /^\d+-/.test(f)).map((f) => parseInt(f.split("-")[0], 10)).filter((n) => !isNaN(n));
|
|
15243
15354
|
if (existing.length > 0) nextNum = Math.max(...existing) + 1;
|
|
15244
15355
|
} catch (e) {
|
|
15245
|
-
|
|
15356
|
+
debugLog(e);
|
|
15246
15357
|
}
|
|
15247
15358
|
output({
|
|
15248
15359
|
planner_model: resolveModelInternal(cwd, "maxsim-planner"),
|
|
@@ -15253,7 +15364,7 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
15253
15364
|
next_num: nextNum,
|
|
15254
15365
|
slug,
|
|
15255
15366
|
description: description ?? null,
|
|
15256
|
-
date:
|
|
15367
|
+
date: todayISO(),
|
|
15257
15368
|
timestamp: now.toISOString(),
|
|
15258
15369
|
quick_dir: ".planning/quick",
|
|
15259
15370
|
task_dir: slug ? `.planning/quick/${nextNum}-${slug}` : null,
|
|
@@ -15265,9 +15376,9 @@ function cmdInitResume(cwd, raw) {
|
|
|
15265
15376
|
const config = loadConfig(cwd);
|
|
15266
15377
|
let interruptedAgentId = null;
|
|
15267
15378
|
try {
|
|
15268
|
-
interruptedAgentId = node_fs.default.readFileSync(
|
|
15379
|
+
interruptedAgentId = node_fs.default.readFileSync(planningPath(cwd, "current-agent-id.txt"), "utf-8").trim();
|
|
15269
15380
|
} catch (e) {
|
|
15270
|
-
|
|
15381
|
+
debugLog(e);
|
|
15271
15382
|
}
|
|
15272
15383
|
output({
|
|
15273
15384
|
state_exists: pathExistsInternal(cwd, ".planning/STATE.md"),
|
|
@@ -15351,7 +15462,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
15351
15462
|
function cmdInitTodos(cwd, area, raw) {
|
|
15352
15463
|
const config = loadConfig(cwd);
|
|
15353
15464
|
const now = /* @__PURE__ */ new Date();
|
|
15354
|
-
const pendingDir =
|
|
15465
|
+
const pendingDir = planningPath(cwd, "todos", "pending");
|
|
15355
15466
|
let count = 0;
|
|
15356
15467
|
const todos = [];
|
|
15357
15468
|
try {
|
|
@@ -15372,14 +15483,14 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
15372
15483
|
path: node_path.default.join(".planning", "todos", "pending", file)
|
|
15373
15484
|
});
|
|
15374
15485
|
} catch (e) {
|
|
15375
|
-
|
|
15486
|
+
debugLog(e);
|
|
15376
15487
|
}
|
|
15377
15488
|
} catch (e) {
|
|
15378
|
-
|
|
15489
|
+
debugLog(e);
|
|
15379
15490
|
}
|
|
15380
15491
|
output({
|
|
15381
15492
|
commit_docs: config.commit_docs,
|
|
15382
|
-
date:
|
|
15493
|
+
date: todayISO(),
|
|
15383
15494
|
timestamp: now.toISOString(),
|
|
15384
15495
|
todo_count: count,
|
|
15385
15496
|
todos,
|
|
@@ -15396,24 +15507,24 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
15396
15507
|
const milestone = getMilestoneInfo(cwd);
|
|
15397
15508
|
let phaseCount = 0;
|
|
15398
15509
|
let completedPhases = 0;
|
|
15399
|
-
const phasesDir =
|
|
15510
|
+
const phasesDir = phasesPath(cwd);
|
|
15400
15511
|
try {
|
|
15401
|
-
const dirs =
|
|
15512
|
+
const dirs = listSubDirs(phasesDir);
|
|
15402
15513
|
phaseCount = dirs.length;
|
|
15403
15514
|
for (const dir of dirs) try {
|
|
15404
|
-
if (node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).some((f) =>
|
|
15515
|
+
if (node_fs.default.readdirSync(node_path.default.join(phasesDir, dir)).some((f) => isSummaryFile(f))) completedPhases++;
|
|
15405
15516
|
} catch (e) {
|
|
15406
|
-
|
|
15517
|
+
debugLog(e);
|
|
15407
15518
|
}
|
|
15408
15519
|
} catch (e) {
|
|
15409
|
-
|
|
15520
|
+
debugLog(e);
|
|
15410
15521
|
}
|
|
15411
|
-
const archiveDir =
|
|
15522
|
+
const archiveDir = planningPath(cwd, "archive");
|
|
15412
15523
|
let archivedMilestones = [];
|
|
15413
15524
|
try {
|
|
15414
|
-
archivedMilestones =
|
|
15525
|
+
archivedMilestones = listSubDirs(archiveDir);
|
|
15415
15526
|
} catch (e) {
|
|
15416
|
-
|
|
15527
|
+
debugLog(e);
|
|
15417
15528
|
}
|
|
15418
15529
|
output({
|
|
15419
15530
|
commit_docs: config.commit_docs,
|
|
@@ -15434,12 +15545,12 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
15434
15545
|
}
|
|
15435
15546
|
function cmdInitMapCodebase(cwd, raw) {
|
|
15436
15547
|
const config = loadConfig(cwd);
|
|
15437
|
-
const codebaseDir =
|
|
15548
|
+
const codebaseDir = planningPath(cwd, "codebase");
|
|
15438
15549
|
let existingMaps = [];
|
|
15439
15550
|
try {
|
|
15440
15551
|
existingMaps = node_fs.default.readdirSync(codebaseDir).filter((f) => f.endsWith(".md"));
|
|
15441
15552
|
} catch (e) {
|
|
15442
|
-
|
|
15553
|
+
debugLog(e);
|
|
15443
15554
|
}
|
|
15444
15555
|
output({
|
|
15445
15556
|
mapper_model: resolveModelInternal(cwd, "maxsim-codebase-mapper"),
|
|
@@ -15471,15 +15582,15 @@ function cmdInitExisting(cwd, raw) {
|
|
|
15471
15582
|
]
|
|
15472
15583
|
}).trim().length > 0;
|
|
15473
15584
|
} catch (e) {
|
|
15474
|
-
|
|
15585
|
+
debugLog(e);
|
|
15475
15586
|
}
|
|
15476
15587
|
hasPackageFile = pathExistsInternal(cwd, "package.json") || pathExistsInternal(cwd, "requirements.txt") || pathExistsInternal(cwd, "Cargo.toml") || pathExistsInternal(cwd, "go.mod") || pathExistsInternal(cwd, "Package.swift");
|
|
15477
15588
|
let planningFiles = [];
|
|
15478
15589
|
try {
|
|
15479
|
-
const planDir =
|
|
15590
|
+
const planDir = planningPath(cwd);
|
|
15480
15591
|
if (node_fs.default.existsSync(planDir)) planningFiles = node_fs.default.readdirSync(planDir, { recursive: true }).map((f) => String(f)).filter((f) => !f.startsWith("."));
|
|
15481
15592
|
} catch (e) {
|
|
15482
|
-
|
|
15593
|
+
debugLog(e);
|
|
15483
15594
|
}
|
|
15484
15595
|
output({
|
|
15485
15596
|
researcher_model: resolveModelInternal(cwd, "maxsim-project-researcher"),
|
|
@@ -15506,20 +15617,20 @@ function cmdInitExisting(cwd, raw) {
|
|
|
15506
15617
|
function cmdInitProgress(cwd, raw) {
|
|
15507
15618
|
const config = loadConfig(cwd);
|
|
15508
15619
|
const milestone = getMilestoneInfo(cwd);
|
|
15509
|
-
const
|
|
15620
|
+
const progressPhasesDir = phasesPath(cwd);
|
|
15510
15621
|
const phases = [];
|
|
15511
15622
|
let currentPhase = null;
|
|
15512
15623
|
let nextPhase = null;
|
|
15513
15624
|
try {
|
|
15514
|
-
const dirs =
|
|
15625
|
+
const dirs = listSubDirs(progressPhasesDir, true);
|
|
15515
15626
|
for (const dir of dirs) {
|
|
15516
15627
|
const match = dir.match(/^(\d+(?:\.\d+)?)-?(.*)/);
|
|
15517
15628
|
const phaseNumber = match ? match[1] : dir;
|
|
15518
15629
|
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) =>
|
|
15630
|
+
const phaseDirPath = node_path.default.join(progressPhasesDir, dir);
|
|
15631
|
+
const phaseFiles = node_fs.default.readdirSync(phaseDirPath);
|
|
15632
|
+
const plans = phaseFiles.filter((f) => isPlanFile(f));
|
|
15633
|
+
const summaries = phaseFiles.filter((f) => isSummaryFile(f));
|
|
15523
15634
|
const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
15524
15635
|
const status = summaries.length >= plans.length && plans.length > 0 ? "complete" : plans.length > 0 ? "in_progress" : hasResearch ? "researched" : "pending";
|
|
15525
15636
|
const phaseInfo = {
|
|
@@ -15536,14 +15647,14 @@ function cmdInitProgress(cwd, raw) {
|
|
|
15536
15647
|
if (!nextPhase && status === "pending") nextPhase = phaseInfo;
|
|
15537
15648
|
}
|
|
15538
15649
|
} catch (e) {
|
|
15539
|
-
|
|
15650
|
+
debugLog(e);
|
|
15540
15651
|
}
|
|
15541
15652
|
let pausedAt = null;
|
|
15542
15653
|
try {
|
|
15543
|
-
const pauseMatch = node_fs.default.readFileSync(
|
|
15654
|
+
const pauseMatch = node_fs.default.readFileSync(planningPath(cwd, "STATE.md"), "utf-8").match(/\*\*Paused At:\*\*\s*(.+)/);
|
|
15544
15655
|
if (pauseMatch) pausedAt = pauseMatch[1].trim();
|
|
15545
15656
|
} catch (e) {
|
|
15546
|
-
|
|
15657
|
+
debugLog(e);
|
|
15547
15658
|
}
|
|
15548
15659
|
output({
|
|
15549
15660
|
executor_model: resolveModelInternal(cwd, "maxsim-executor"),
|
|
@@ -15828,7 +15939,11 @@ const COMMANDS = {
|
|
|
15828
15939
|
freshness: f.freshness ?? void 0
|
|
15829
15940
|
}, raw);
|
|
15830
15941
|
},
|
|
15831
|
-
"dashboard": (args) => handleDashboard(args.slice(1))
|
|
15942
|
+
"dashboard": (args) => handleDashboard(args.slice(1)),
|
|
15943
|
+
"start-server": async () => {
|
|
15944
|
+
const serverPath = node_path.join(__dirname, "mcp-server.cjs");
|
|
15945
|
+
(0, node_child_process.spawn)(process.execPath, [serverPath], { stdio: "inherit" }).on("exit", (code) => process.exit(code ?? 0));
|
|
15946
|
+
}
|
|
15832
15947
|
};
|
|
15833
15948
|
async function main() {
|
|
15834
15949
|
const args = process.argv.slice(2);
|