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.
Files changed (78) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/assets/CHANGELOG.md +7 -0
  3. package/dist/assets/dashboard/server.js +5 -1
  4. package/dist/assets/templates/workflows/execute-plan.md +10 -0
  5. package/dist/cli.cjs +554 -439
  6. package/dist/cli.cjs.map +1 -1
  7. package/dist/cli.js +5 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/core/commands.d.ts +7 -0
  10. package/dist/core/commands.d.ts.map +1 -1
  11. package/dist/core/commands.js +36 -34
  12. package/dist/core/commands.js.map +1 -1
  13. package/dist/core/core.d.ts +21 -1
  14. package/dist/core/core.d.ts.map +1 -1
  15. package/dist/core/core.js +81 -36
  16. package/dist/core/core.js.map +1 -1
  17. package/dist/core/index.d.ts +1 -1
  18. package/dist/core/index.d.ts.map +1 -1
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/init.d.ts +2 -2
  21. package/dist/core/init.d.ts.map +1 -1
  22. package/dist/core/init.js +33 -51
  23. package/dist/core/init.js.map +1 -1
  24. package/dist/core/milestone.d.ts.map +1 -1
  25. package/dist/core/milestone.js +15 -20
  26. package/dist/core/milestone.js.map +1 -1
  27. package/dist/core/phase.d.ts +33 -0
  28. package/dist/core/phase.d.ts.map +1 -1
  29. package/dist/core/phase.js +275 -224
  30. package/dist/core/phase.js.map +1 -1
  31. package/dist/core/roadmap.d.ts.map +1 -1
  32. package/dist/core/roadmap.js +16 -18
  33. package/dist/core/roadmap.js.map +1 -1
  34. package/dist/core/state.d.ts +5 -0
  35. package/dist/core/state.d.ts.map +1 -1
  36. package/dist/core/state.js +44 -37
  37. package/dist/core/state.js.map +1 -1
  38. package/dist/core/template.d.ts.map +1 -1
  39. package/dist/core/template.js +1 -1
  40. package/dist/core/template.js.map +1 -1
  41. package/dist/core/types.d.ts +3 -2
  42. package/dist/core/types.d.ts.map +1 -1
  43. package/dist/core/types.js.map +1 -1
  44. package/dist/core/verify.d.ts.map +1 -1
  45. package/dist/core/verify.js +61 -80
  46. package/dist/core/verify.js.map +1 -1
  47. package/dist/install.cjs +23 -0
  48. package/dist/install.cjs.map +1 -1
  49. package/dist/install.js +37 -0
  50. package/dist/install.js.map +1 -1
  51. package/dist/mcp/index.d.ts +12 -0
  52. package/dist/mcp/index.d.ts.map +1 -0
  53. package/dist/mcp/index.js +21 -0
  54. package/dist/mcp/index.js.map +1 -0
  55. package/dist/mcp/phase-tools.d.ts +13 -0
  56. package/dist/mcp/phase-tools.d.ts.map +1 -0
  57. package/dist/mcp/phase-tools.js +164 -0
  58. package/dist/mcp/phase-tools.js.map +1 -0
  59. package/dist/mcp/state-tools.d.ts +13 -0
  60. package/dist/mcp/state-tools.d.ts.map +1 -0
  61. package/dist/mcp/state-tools.js +185 -0
  62. package/dist/mcp/state-tools.js.map +1 -0
  63. package/dist/mcp/todo-tools.d.ts +13 -0
  64. package/dist/mcp/todo-tools.d.ts.map +1 -0
  65. package/dist/mcp/todo-tools.js +143 -0
  66. package/dist/mcp/todo-tools.js.map +1 -0
  67. package/dist/mcp/utils.d.ts +27 -0
  68. package/dist/mcp/utils.d.ts.map +1 -0
  69. package/dist/mcp/utils.js +82 -0
  70. package/dist/mcp/utils.js.map +1 -0
  71. package/dist/mcp-server.cjs +11806 -0
  72. package/dist/mcp-server.cjs.map +1 -0
  73. package/dist/mcp-server.d.cts +2 -0
  74. package/dist/mcp-server.d.ts +12 -0
  75. package/dist/mcp-server.d.ts.map +1 -0
  76. package/dist/mcp-server.js +31 -0
  77. package/dist/mcp-server.js.map +1 -0
  78. package/package.json +5 -3
@@ -8,6 +8,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
9
  };
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.scaffoldPhaseStubs = scaffoldPhaseStubs;
12
+ exports.phaseAddCore = phaseAddCore;
13
+ exports.phaseInsertCore = phaseInsertCore;
14
+ exports.phaseCompleteCore = phaseCompleteCore;
11
15
  exports.cmdPhasesList = cmdPhasesList;
12
16
  exports.cmdPhaseNextDecimal = cmdPhaseNextDecimal;
13
17
  exports.cmdFindPhase = cmdFindPhase;
@@ -20,11 +24,209 @@ const node_fs_1 = __importDefault(require("node:fs"));
20
24
  const node_path_1 = __importDefault(require("node:path"));
21
25
  const core_js_1 = require("./core.js");
22
26
  const frontmatter_js_1 = require("./frontmatter.js");
27
+ // ─── Stub scaffolding ───────────────────────────────────────────────────────
28
+ function scaffoldPhaseStubs(dirPath, phaseId, name) {
29
+ const today = (0, core_js_1.todayISO)();
30
+ node_fs_1.default.writeFileSync(node_path_1.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`);
31
+ node_fs_1.default.writeFileSync(node_path_1.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`);
32
+ }
33
+ // ─── Core functions ─────────────────────────────────────────────────────────
34
+ function phaseAddCore(cwd, description, options) {
35
+ const rmPath = (0, core_js_1.roadmapPath)(cwd);
36
+ if (!node_fs_1.default.existsSync(rmPath)) {
37
+ throw new Error('ROADMAP.md not found');
38
+ }
39
+ const content = node_fs_1.default.readFileSync(rmPath, 'utf-8');
40
+ const slug = (0, core_js_1.generateSlugInternal)(description);
41
+ const phasePattern = (0, core_js_1.getPhasePattern)();
42
+ let maxPhase = 0;
43
+ let m;
44
+ while ((m = phasePattern.exec(content)) !== null) {
45
+ const num = parseInt(m[1], 10);
46
+ if (num > maxPhase)
47
+ maxPhase = num;
48
+ }
49
+ const newPhaseNum = maxPhase + 1;
50
+ const paddedNum = String(newPhaseNum).padStart(2, '0');
51
+ const dirName = `${paddedNum}-${slug}`;
52
+ const dirPath = (0, core_js_1.planningPath)(cwd, 'phases', dirName);
53
+ node_fs_1.default.mkdirSync(dirPath, { recursive: true });
54
+ node_fs_1.default.writeFileSync(node_path_1.default.join(dirPath, '.gitkeep'), '');
55
+ if (options?.includeStubs) {
56
+ scaffoldPhaseStubs(dirPath, paddedNum, description);
57
+ }
58
+ 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`;
59
+ let updatedContent;
60
+ const lastSeparator = content.lastIndexOf('\n---');
61
+ if (lastSeparator > 0) {
62
+ updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
63
+ }
64
+ else {
65
+ updatedContent = content + phaseEntry;
66
+ }
67
+ node_fs_1.default.writeFileSync(rmPath, updatedContent, 'utf-8');
68
+ return {
69
+ phase_number: newPhaseNum,
70
+ padded: paddedNum,
71
+ slug,
72
+ directory: `.planning/phases/${dirName}`,
73
+ description,
74
+ };
75
+ }
76
+ function phaseInsertCore(cwd, afterPhase, description, options) {
77
+ const rmPath = (0, core_js_1.roadmapPath)(cwd);
78
+ if (!node_fs_1.default.existsSync(rmPath)) {
79
+ throw new Error('ROADMAP.md not found');
80
+ }
81
+ const content = node_fs_1.default.readFileSync(rmPath, 'utf-8');
82
+ const slug = (0, core_js_1.generateSlugInternal)(description);
83
+ const normalizedAfter = (0, core_js_1.normalizePhaseName)(afterPhase);
84
+ const unpadded = normalizedAfter.replace(/^0+/, '');
85
+ const afterPhaseEscaped = '0*' + unpadded.replace(/\./g, '\\.');
86
+ const targetPattern = (0, core_js_1.getPhasePattern)(afterPhaseEscaped, 'i');
87
+ if (!targetPattern.test(content)) {
88
+ throw new Error(`Phase ${afterPhase} not found in ROADMAP.md`);
89
+ }
90
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
91
+ const normalizedBase = (0, core_js_1.normalizePhaseName)(afterPhase);
92
+ const existingDecimals = [];
93
+ try {
94
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath);
95
+ const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
96
+ for (const dir of dirs) {
97
+ const dm = dir.match(decimalPattern);
98
+ if (dm)
99
+ existingDecimals.push(parseInt(dm[1], 10));
100
+ }
101
+ }
102
+ catch (e) {
103
+ (0, core_js_1.debugLog)(e);
104
+ }
105
+ const nextDecimal = existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1;
106
+ const decimalPhase = `${normalizedBase}.${nextDecimal}`;
107
+ const dirName = `${decimalPhase}-${slug}`;
108
+ const dirPath = (0, core_js_1.planningPath)(cwd, 'phases', dirName);
109
+ node_fs_1.default.mkdirSync(dirPath, { recursive: true });
110
+ node_fs_1.default.writeFileSync(node_path_1.default.join(dirPath, '.gitkeep'), '');
111
+ if (options?.includeStubs) {
112
+ scaffoldPhaseStubs(dirPath, decimalPhase, description);
113
+ }
114
+ 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`;
115
+ const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
116
+ const headerMatch = content.match(headerPattern);
117
+ if (!headerMatch) {
118
+ throw new Error(`Could not find Phase ${afterPhase} header`);
119
+ }
120
+ const headerIdx = content.indexOf(headerMatch[0]);
121
+ const afterHeader = content.slice(headerIdx + headerMatch[0].length);
122
+ const nextPhaseMatch = afterHeader.match(/\n#{2,4}\s+Phase\s+\d/i);
123
+ let insertIdx;
124
+ if (nextPhaseMatch) {
125
+ insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
126
+ }
127
+ else {
128
+ insertIdx = content.length;
129
+ }
130
+ const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
131
+ node_fs_1.default.writeFileSync(rmPath, updatedContent, 'utf-8');
132
+ return {
133
+ phase_number: decimalPhase,
134
+ after_phase: afterPhase,
135
+ slug,
136
+ directory: `.planning/phases/${dirName}`,
137
+ description,
138
+ };
139
+ }
140
+ function phaseCompleteCore(cwd, phaseNum) {
141
+ const rmPath = (0, core_js_1.roadmapPath)(cwd);
142
+ const stPath = (0, core_js_1.statePath)(cwd);
143
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
144
+ const today = (0, core_js_1.todayISO)();
145
+ const phaseInfo = (0, core_js_1.findPhaseInternal)(cwd, phaseNum);
146
+ if (!phaseInfo) {
147
+ throw new Error(`Phase ${phaseNum} not found`);
148
+ }
149
+ const planCount = phaseInfo.plans.length;
150
+ const summaryCount = phaseInfo.summaries.length;
151
+ let requirementsUpdated = false;
152
+ if (node_fs_1.default.existsSync(rmPath)) {
153
+ let roadmapContent = node_fs_1.default.readFileSync(rmPath, 'utf-8');
154
+ const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${(0, core_js_1.escapePhaseNum)(phaseNum)}[:\\s][^\\n]*)`, 'i');
155
+ roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
156
+ const phaseEscaped = (0, core_js_1.escapePhaseNum)(phaseNum);
157
+ const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, 'i');
158
+ roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
159
+ const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
160
+ roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
161
+ node_fs_1.default.writeFileSync(rmPath, roadmapContent, 'utf-8');
162
+ // Update REQUIREMENTS.md
163
+ const reqPath = (0, core_js_1.planningPath)(cwd, 'REQUIREMENTS.md');
164
+ if (node_fs_1.default.existsSync(reqPath)) {
165
+ const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${(0, core_js_1.escapePhaseNum)(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, 'i'));
166
+ if (reqMatch) {
167
+ const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
168
+ let reqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
169
+ for (const reqId of reqIds) {
170
+ reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, 'gi'), '$1x$2');
171
+ reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'), '$1 Complete $2');
172
+ }
173
+ node_fs_1.default.writeFileSync(reqPath, reqContent, 'utf-8');
174
+ requirementsUpdated = true;
175
+ }
176
+ }
177
+ }
178
+ // Find next phase
179
+ let nextPhaseNum = null;
180
+ let nextPhaseName = null;
181
+ let isLastPhase = true;
182
+ try {
183
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath, true);
184
+ for (const dir of dirs) {
185
+ const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
186
+ if (dm) {
187
+ if ((0, core_js_1.comparePhaseNum)(dm[1], phaseNum) > 0) {
188
+ nextPhaseNum = dm[1];
189
+ nextPhaseName = dm[2] || null;
190
+ isLastPhase = false;
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ catch (e) {
197
+ (0, core_js_1.debugLog)(e);
198
+ }
199
+ // Update STATE.md
200
+ if (node_fs_1.default.existsSync(stPath)) {
201
+ let stateContent = node_fs_1.default.readFileSync(stPath, 'utf-8');
202
+ stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
203
+ if (nextPhaseName) {
204
+ stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, ' ')}`);
205
+ }
206
+ stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? 'Milestone complete' : 'Ready to plan'}`);
207
+ stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
208
+ stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
209
+ stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`);
210
+ node_fs_1.default.writeFileSync(stPath, stateContent, 'utf-8');
211
+ }
212
+ return {
213
+ completed_phase: phaseNum,
214
+ phase_name: phaseInfo.phase_name,
215
+ plans_executed: `${summaryCount}/${planCount}`,
216
+ next_phase: nextPhaseNum,
217
+ next_phase_name: nextPhaseName,
218
+ is_last_phase: isLastPhase,
219
+ date: today,
220
+ roadmap_updated: node_fs_1.default.existsSync(rmPath),
221
+ state_updated: node_fs_1.default.existsSync(stPath),
222
+ requirements_updated: requirementsUpdated,
223
+ };
224
+ }
23
225
  // ─── Phase list ─────────────────────────────────────────────────────────────
24
226
  function cmdPhasesList(cwd, options, raw) {
25
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
227
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
26
228
  const { type, phase, includeArchived } = options;
27
- if (!node_fs_1.default.existsSync(phasesDir)) {
229
+ if (!node_fs_1.default.existsSync(phasesDirPath)) {
28
230
  if (type) {
29
231
  (0, core_js_1.output)({ files: [], count: 0 }, raw, '');
30
232
  }
@@ -34,8 +236,7 @@ function cmdPhasesList(cwd, options, raw) {
34
236
  return;
35
237
  }
36
238
  try {
37
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
38
- let dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
239
+ let dirs = (0, core_js_1.listSubDirs)(phasesDirPath);
39
240
  if (includeArchived) {
40
241
  const archived = (0, core_js_1.getArchivedPhaseDirs)(cwd);
41
242
  for (const a of archived) {
@@ -55,14 +256,14 @@ function cmdPhasesList(cwd, options, raw) {
55
256
  if (type) {
56
257
  const files = [];
57
258
  for (const dir of dirs) {
58
- const dirPath = node_path_1.default.join(phasesDir, dir);
259
+ const dirPath = node_path_1.default.join(phasesDirPath, dir);
59
260
  const dirFiles = node_fs_1.default.readdirSync(dirPath);
60
261
  let filtered;
61
262
  if (type === 'plans') {
62
- filtered = dirFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
263
+ filtered = dirFiles.filter(core_js_1.isPlanFile);
63
264
  }
64
265
  else if (type === 'summaries') {
65
- filtered = dirFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
266
+ filtered = dirFiles.filter(core_js_1.isSummaryFile);
66
267
  }
67
268
  else {
68
269
  filtered = dirFiles;
@@ -85,15 +286,14 @@ function cmdPhasesList(cwd, options, raw) {
85
286
  }
86
287
  // ─── Next decimal ───────────────────────────────────────────────────────────
87
288
  function cmdPhaseNextDecimal(cwd, basePhase, raw) {
88
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
289
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
89
290
  const normalized = (0, core_js_1.normalizePhaseName)(basePhase);
90
- if (!node_fs_1.default.existsSync(phasesDir)) {
291
+ if (!node_fs_1.default.existsSync(phasesDirPath)) {
91
292
  (0, core_js_1.output)({ found: false, base_phase: normalized, next: `${normalized}.1`, existing: [] }, raw, `${normalized}.1`);
92
293
  return;
93
294
  }
94
295
  try {
95
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
96
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
296
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath);
97
297
  const baseExists = dirs.some(d => d.startsWith(normalized + '-') || d === normalized);
98
298
  const decimalPattern = new RegExp(`^${normalized}\\.(\\d+)`);
99
299
  const existingDecimals = [];
@@ -128,12 +328,11 @@ function cmdFindPhase(cwd, phase, raw) {
128
328
  if (!phase) {
129
329
  (0, core_js_1.error)('phase identifier required');
130
330
  }
131
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
331
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
132
332
  const normalized = (0, core_js_1.normalizePhaseName)(phase);
133
333
  const notFound = { found: false, directory: null, phase_number: null, phase_name: null, plans: [], summaries: [] };
134
334
  try {
135
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
136
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
335
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath, true);
137
336
  const match = dirs.find(d => d.startsWith(normalized));
138
337
  if (!match) {
139
338
  (0, core_js_1.output)(notFound, raw, '');
@@ -142,10 +341,10 @@ function cmdFindPhase(cwd, phase, raw) {
142
341
  const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
143
342
  const phaseNumber = dirMatch ? dirMatch[1] : normalized;
144
343
  const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
145
- const phaseDir = node_path_1.default.join(phasesDir, match);
344
+ const phaseDir = node_path_1.default.join(phasesDirPath, match);
146
345
  const phaseFiles = node_fs_1.default.readdirSync(phaseDir);
147
- const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').sort();
148
- const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').sort();
346
+ const plans = phaseFiles.filter(core_js_1.isPlanFile).sort();
347
+ const summaries = phaseFiles.filter(core_js_1.isSummaryFile).sort();
149
348
  const result = {
150
349
  found: true,
151
350
  directory: node_path_1.default.join('.planning', 'phases', match),
@@ -165,38 +364,36 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
165
364
  if (!phase) {
166
365
  (0, core_js_1.error)('phase required for phase-plan-index');
167
366
  }
168
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
367
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
169
368
  const normalized = (0, core_js_1.normalizePhaseName)(phase);
170
369
  let phaseDir = null;
171
370
  let phaseDirName = null;
172
371
  try {
173
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
174
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
372
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath, true);
175
373
  const match = dirs.find(d => d.startsWith(normalized));
176
374
  if (match) {
177
- phaseDir = node_path_1.default.join(phasesDir, match);
375
+ phaseDir = node_path_1.default.join(phasesDirPath, match);
178
376
  phaseDirName = match;
179
377
  }
180
378
  }
181
379
  catch (e) {
182
380
  /* optional op, ignore */
183
- if (process.env.MAXSIM_DEBUG)
184
- console.error(e);
381
+ (0, core_js_1.debugLog)(e);
185
382
  }
186
383
  if (!phaseDir) {
187
384
  (0, core_js_1.output)({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], has_checkpoints: false }, raw);
188
385
  return;
189
386
  }
190
387
  const phaseFiles = node_fs_1.default.readdirSync(phaseDir);
191
- const planFiles = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').sort();
192
- const summaryFiles = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
193
- const completedPlanIds = new Set(summaryFiles.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '')));
388
+ const planFiles = phaseFiles.filter(core_js_1.isPlanFile).sort();
389
+ const summaryFiles = phaseFiles.filter(core_js_1.isSummaryFile);
390
+ const completedPlanIds = new Set(summaryFiles.map(core_js_1.summaryId));
194
391
  const plans = [];
195
392
  const waves = {};
196
393
  const incomplete = [];
197
394
  let hasCheckpoints = false;
198
395
  for (const planFile of planFiles) {
199
- const planId = planFile.replace('-PLAN.md', '').replace('PLAN.md', '');
396
+ const id = (0, core_js_1.planId)(planFile);
200
397
  const planPath = node_path_1.default.join(phaseDir, planFile);
201
398
  const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
202
399
  const fm = (0, frontmatter_js_1.extractFrontmatter)(content);
@@ -214,12 +411,12 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
214
411
  if (fm['files-modified']) {
215
412
  filesModified = Array.isArray(fm['files-modified']) ? fm['files-modified'] : [fm['files-modified']];
216
413
  }
217
- const hasSummary = completedPlanIds.has(planId);
414
+ const hasSummary = completedPlanIds.has(id);
218
415
  if (!hasSummary) {
219
- incomplete.push(planId);
416
+ incomplete.push(id);
220
417
  }
221
418
  const plan = {
222
- id: planId,
419
+ id,
223
420
  wave,
224
421
  autonomous,
225
422
  objective: fm.objective || null,
@@ -232,7 +429,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
232
429
  if (!waves[waveKey]) {
233
430
  waves[waveKey] = [];
234
431
  }
235
- waves[waveKey].push(planId);
432
+ waves[waveKey].push(id);
236
433
  }
237
434
  (0, core_js_1.output)({ phase: normalized, plans, waves, incomplete, has_checkpoints: hasCheckpoints }, raw);
238
435
  }
@@ -241,134 +438,59 @@ function cmdPhaseAdd(cwd, description, raw) {
241
438
  if (!description) {
242
439
  (0, core_js_1.error)('description required for phase add');
243
440
  }
244
- const roadmapPath = node_path_1.default.join(cwd, '.planning', 'ROADMAP.md');
245
- if (!node_fs_1.default.existsSync(roadmapPath)) {
246
- (0, core_js_1.error)('ROADMAP.md not found');
247
- }
248
- const content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
249
- const slug = (0, core_js_1.generateSlugInternal)(description);
250
- const phasePattern = (0, core_js_1.getPhasePattern)();
251
- let maxPhase = 0;
252
- let m;
253
- while ((m = phasePattern.exec(content)) !== null) {
254
- const num = parseInt(m[1], 10);
255
- if (num > maxPhase)
256
- maxPhase = num;
257
- }
258
- const newPhaseNum = maxPhase + 1;
259
- const paddedNum = String(newPhaseNum).padStart(2, '0');
260
- const dirName = `${paddedNum}-${slug}`;
261
- const dirPath = node_path_1.default.join(cwd, '.planning', 'phases', dirName);
262
- node_fs_1.default.mkdirSync(dirPath, { recursive: true });
263
- node_fs_1.default.writeFileSync(node_path_1.default.join(dirPath, '.gitkeep'), '');
264
- 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`;
265
- let updatedContent;
266
- const lastSeparator = content.lastIndexOf('\n---');
267
- if (lastSeparator > 0) {
268
- updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
441
+ try {
442
+ const result = phaseAddCore(cwd, description, { includeStubs: false });
443
+ (0, core_js_1.output)({ phase_number: result.phase_number, padded: result.padded, name: result.description, slug: result.slug, directory: result.directory }, raw, result.padded);
269
444
  }
270
- else {
271
- updatedContent = content + phaseEntry;
445
+ catch (e) {
446
+ (0, core_js_1.error)(e.message);
272
447
  }
273
- node_fs_1.default.writeFileSync(roadmapPath, updatedContent, 'utf-8');
274
- (0, core_js_1.output)({ phase_number: newPhaseNum, padded: paddedNum, name: description, slug, directory: `.planning/phases/${dirName}` }, raw, paddedNum);
275
448
  }
276
449
  // ─── Phase insert ───────────────────────────────────────────────────────────
277
450
  function cmdPhaseInsert(cwd, afterPhase, description, raw) {
278
451
  if (!afterPhase || !description) {
279
452
  (0, core_js_1.error)('after-phase and description required for phase insert');
280
453
  }
281
- const roadmapPath = node_path_1.default.join(cwd, '.planning', 'ROADMAP.md');
282
- if (!node_fs_1.default.existsSync(roadmapPath)) {
283
- (0, core_js_1.error)('ROADMAP.md not found');
284
- }
285
- const content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
286
- const slug = (0, core_js_1.generateSlugInternal)(description);
287
- const normalizedAfter = (0, core_js_1.normalizePhaseName)(afterPhase);
288
- const unpadded = normalizedAfter.replace(/^0+/, '');
289
- const afterPhaseEscaped = '0*' + unpadded.replace(/\./g, '\\.');
290
- const targetPattern = (0, core_js_1.getPhasePattern)(afterPhaseEscaped, 'i');
291
- if (!targetPattern.test(content)) {
292
- (0, core_js_1.error)(`Phase ${afterPhase} not found in ROADMAP.md`);
293
- }
294
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
295
- const normalizedBase = (0, core_js_1.normalizePhaseName)(afterPhase);
296
- const existingDecimals = [];
297
454
  try {
298
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
299
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
300
- const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
301
- for (const dir of dirs) {
302
- const dm = dir.match(decimalPattern);
303
- if (dm)
304
- existingDecimals.push(parseInt(dm[1], 10));
305
- }
455
+ const result = phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
456
+ (0, core_js_1.output)({ phase_number: result.phase_number, after_phase: result.after_phase, name: result.description, slug: result.slug, directory: result.directory }, raw, result.phase_number);
306
457
  }
307
458
  catch (e) {
308
- /* optional op, ignore */
309
- if (process.env.MAXSIM_DEBUG)
310
- console.error(e);
311
- }
312
- const nextDecimal = existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1;
313
- const decimalPhase = `${normalizedBase}.${nextDecimal}`;
314
- const dirName = `${decimalPhase}-${slug}`;
315
- const dirPath = node_path_1.default.join(cwd, '.planning', 'phases', dirName);
316
- node_fs_1.default.mkdirSync(dirPath, { recursive: true });
317
- node_fs_1.default.writeFileSync(node_path_1.default.join(dirPath, '.gitkeep'), '');
318
- 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`;
319
- const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
320
- const headerMatch = content.match(headerPattern);
321
- if (!headerMatch) {
322
- (0, core_js_1.error)(`Could not find Phase ${afterPhase} header`);
323
- }
324
- const headerIdx = content.indexOf(headerMatch[0]);
325
- const afterHeader = content.slice(headerIdx + headerMatch[0].length);
326
- const nextPhaseMatch = afterHeader.match(/\n#{2,4}\s+Phase\s+\d/i);
327
- let insertIdx;
328
- if (nextPhaseMatch) {
329
- insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
330
- }
331
- else {
332
- insertIdx = content.length;
459
+ (0, core_js_1.error)(e.message);
333
460
  }
334
- const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
335
- node_fs_1.default.writeFileSync(roadmapPath, updatedContent, 'utf-8');
336
- (0, core_js_1.output)({ phase_number: decimalPhase, after_phase: afterPhase, name: description, slug, directory: `.planning/phases/${dirName}` }, raw, decimalPhase);
337
461
  }
338
462
  // ─── Phase remove ───────────────────────────────────────────────────────────
339
463
  function cmdPhaseRemove(cwd, targetPhase, options, raw) {
340
464
  if (!targetPhase) {
341
465
  (0, core_js_1.error)('phase number required for phase remove');
342
466
  }
343
- const roadmapPath = node_path_1.default.join(cwd, '.planning', 'ROADMAP.md');
344
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
467
+ const rmPath = (0, core_js_1.roadmapPath)(cwd);
468
+ const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
345
469
  const force = options.force || false;
346
- if (!node_fs_1.default.existsSync(roadmapPath)) {
470
+ if (!node_fs_1.default.existsSync(rmPath)) {
347
471
  (0, core_js_1.error)('ROADMAP.md not found');
348
472
  }
349
473
  const normalized = (0, core_js_1.normalizePhaseName)(targetPhase);
350
474
  const isDecimal = targetPhase.includes('.');
351
475
  let targetDir = null;
352
476
  try {
353
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
354
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
477
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath, true);
355
478
  targetDir = dirs.find(d => d.startsWith(normalized + '-') || d === normalized) || null;
356
479
  }
357
480
  catch (e) {
358
481
  /* optional op, ignore */
359
- if (process.env.MAXSIM_DEBUG)
360
- console.error(e);
482
+ (0, core_js_1.debugLog)(e);
361
483
  }
362
484
  if (targetDir && !force) {
363
- const targetPath = node_path_1.default.join(phasesDir, targetDir);
485
+ const targetPath = node_path_1.default.join(phasesDirPath, targetDir);
364
486
  const files = node_fs_1.default.readdirSync(targetPath);
365
- const summaries = files.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
487
+ const summaries = files.filter(core_js_1.isSummaryFile);
366
488
  if (summaries.length > 0) {
367
489
  (0, core_js_1.error)(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
368
490
  }
369
491
  }
370
492
  if (targetDir) {
371
- node_fs_1.default.rmSync(node_path_1.default.join(phasesDir, targetDir), { recursive: true, force: true });
493
+ node_fs_1.default.rmSync(node_path_1.default.join(phasesDirPath, targetDir), { recursive: true, force: true });
372
494
  }
373
495
  const renamedDirs = [];
374
496
  const renamedFiles = [];
@@ -377,8 +499,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
377
499
  const baseInt = baseParts[0];
378
500
  const removedDecimal = parseInt(baseParts[1], 10);
379
501
  try {
380
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
381
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
502
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath, true);
382
503
  const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
383
504
  const toRename = [];
384
505
  for (const dir of dirs) {
@@ -393,13 +514,13 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
393
514
  const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
394
515
  const newPhaseId = `${baseInt}.${newDecimal}`;
395
516
  const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
396
- node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, item.dir), node_path_1.default.join(phasesDir, newDirName));
517
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDirPath, item.dir), node_path_1.default.join(phasesDirPath, newDirName));
397
518
  renamedDirs.push({ from: item.dir, to: newDirName });
398
- const dirFiles = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, newDirName));
519
+ const dirFiles = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDirPath, newDirName));
399
520
  for (const f of dirFiles) {
400
521
  if (f.includes(oldPhaseId)) {
401
522
  const newFileName = f.replace(oldPhaseId, newPhaseId);
402
- node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, newDirName, f), node_path_1.default.join(phasesDir, newDirName, newFileName));
523
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDirPath, newDirName, f), node_path_1.default.join(phasesDirPath, newDirName, newFileName));
403
524
  renamedFiles.push({ from: f, to: newFileName });
404
525
  }
405
526
  }
@@ -407,15 +528,13 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
407
528
  }
408
529
  catch (e) {
409
530
  /* optional op, ignore */
410
- if (process.env.MAXSIM_DEBUG)
411
- console.error(e);
531
+ (0, core_js_1.debugLog)(e);
412
532
  }
413
533
  }
414
534
  else {
415
535
  const removedInt = parseInt(normalized, 10);
416
536
  try {
417
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
418
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
537
+ const dirs = (0, core_js_1.listSubDirs)(phasesDirPath, true);
419
538
  const toRename = [];
420
539
  for (const dir of dirs) {
421
540
  const dm = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
@@ -446,13 +565,13 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
446
565
  const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
447
566
  const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
448
567
  const newDirName = `${newPrefix}-${item.slug}`;
449
- node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, item.dir), node_path_1.default.join(phasesDir, newDirName));
568
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDirPath, item.dir), node_path_1.default.join(phasesDirPath, newDirName));
450
569
  renamedDirs.push({ from: item.dir, to: newDirName });
451
- const dirFiles = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, newDirName));
570
+ const dirFiles = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDirPath, newDirName));
452
571
  for (const f of dirFiles) {
453
572
  if (f.startsWith(oldPrefix)) {
454
573
  const newFileName = newPrefix + f.slice(oldPrefix.length);
455
- node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, newDirName, f), node_path_1.default.join(phasesDir, newDirName, newFileName));
574
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDirPath, newDirName, f), node_path_1.default.join(phasesDirPath, newDirName, newFileName));
456
575
  renamedFiles.push({ from: f, to: newFileName });
457
576
  }
458
577
  }
@@ -460,13 +579,12 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
460
579
  }
461
580
  catch (e) {
462
581
  /* optional op, ignore */
463
- if (process.env.MAXSIM_DEBUG)
464
- console.error(e);
582
+ (0, core_js_1.debugLog)(e);
465
583
  }
466
584
  }
467
585
  // Update ROADMAP.md
468
- let roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
469
- const targetEscaped = targetPhase.replace(/\./g, '\\.');
586
+ let roadmapContent = node_fs_1.default.readFileSync(rmPath, 'utf-8');
587
+ const targetEscaped = (0, core_js_1.escapePhaseNum)(targetPhase);
470
588
  const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`, 'i');
471
589
  roadmapContent = roadmapContent.replace(sectionPattern, '');
472
590
  const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`, 'gi');
@@ -489,11 +607,11 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
489
607
  roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, 'gi'), `$1${newStr}`);
490
608
  }
491
609
  }
492
- node_fs_1.default.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
610
+ node_fs_1.default.writeFileSync(rmPath, roadmapContent, 'utf-8');
493
611
  // Update STATE.md phase count
494
- const statePath = node_path_1.default.join(cwd, '.planning', 'STATE.md');
495
- if (node_fs_1.default.existsSync(statePath)) {
496
- let stateContent = node_fs_1.default.readFileSync(statePath, 'utf-8');
612
+ const stPath = (0, core_js_1.statePath)(cwd);
613
+ if (node_fs_1.default.existsSync(stPath)) {
614
+ let stateContent = node_fs_1.default.readFileSync(stPath, 'utf-8');
497
615
  const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
498
616
  const totalMatch = stateContent.match(totalPattern);
499
617
  if (totalMatch) {
@@ -506,7 +624,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
506
624
  const oldTotal = parseInt(ofMatch[2], 10);
507
625
  stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
508
626
  }
509
- node_fs_1.default.writeFileSync(statePath, stateContent, 'utf-8');
627
+ node_fs_1.default.writeFileSync(stPath, stateContent, 'utf-8');
510
628
  }
511
629
  (0, core_js_1.output)({
512
630
  removed: targetPhase,
@@ -514,7 +632,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
514
632
  renamed_directories: renamedDirs,
515
633
  renamed_files: renamedFiles,
516
634
  roadmap_updated: true,
517
- state_updated: node_fs_1.default.existsSync(statePath),
635
+ state_updated: node_fs_1.default.existsSync(stPath),
518
636
  }, raw);
519
637
  }
520
638
  // ─── Phase complete ─────────────────────────────────────────────────────────
@@ -522,89 +640,22 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
522
640
  if (!phaseNum) {
523
641
  (0, core_js_1.error)('phase number required for phase complete');
524
642
  }
525
- const roadmapPath = node_path_1.default.join(cwd, '.planning', 'ROADMAP.md');
526
- const statePath = node_path_1.default.join(cwd, '.planning', 'STATE.md');
527
- const phasesDir = node_path_1.default.join(cwd, '.planning', 'phases');
528
- const normalized = (0, core_js_1.normalizePhaseName)(phaseNum);
529
- const today = new Date().toISOString().split('T')[0];
530
- const phaseInfo = (0, core_js_1.findPhaseInternal)(cwd, phaseNum);
531
- if (!phaseInfo) {
532
- (0, core_js_1.error)(`Phase ${phaseNum} not found`);
533
- }
534
- const planCount = phaseInfo.plans.length;
535
- const summaryCount = phaseInfo.summaries.length;
536
- if (node_fs_1.default.existsSync(roadmapPath)) {
537
- let roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
538
- const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseNum.replace('.', '\\.')}[:\\s][^\\n]*)`, 'i');
539
- roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
540
- const phaseEscaped = phaseNum.replace('.', '\\.');
541
- const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, 'i');
542
- roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
543
- const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
544
- roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
545
- node_fs_1.default.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
546
- // Update REQUIREMENTS.md
547
- const reqPath = node_path_1.default.join(cwd, '.planning', 'REQUIREMENTS.md');
548
- if (node_fs_1.default.existsSync(reqPath)) {
549
- const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${phaseNum.replace('.', '\\.')}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, 'i'));
550
- if (reqMatch) {
551
- const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
552
- let reqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
553
- for (const reqId of reqIds) {
554
- reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, 'gi'), '$1x$2');
555
- reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'), '$1 Complete $2');
556
- }
557
- node_fs_1.default.writeFileSync(reqPath, reqContent, 'utf-8');
558
- }
559
- }
560
- }
561
- // Find next phase
562
- let nextPhaseNum = null;
563
- let nextPhaseName = null;
564
- let isLastPhase = true;
565
643
  try {
566
- const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
567
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
568
- for (const dir of dirs) {
569
- const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
570
- if (dm) {
571
- if ((0, core_js_1.comparePhaseNum)(dm[1], phaseNum) > 0) {
572
- nextPhaseNum = dm[1];
573
- nextPhaseName = dm[2] || null;
574
- isLastPhase = false;
575
- break;
576
- }
577
- }
578
- }
644
+ const result = phaseCompleteCore(cwd, phaseNum);
645
+ (0, core_js_1.output)({
646
+ completed_phase: result.completed_phase,
647
+ phase_name: result.phase_name,
648
+ plans_executed: result.plans_executed,
649
+ next_phase: result.next_phase,
650
+ next_phase_name: result.next_phase_name,
651
+ is_last_phase: result.is_last_phase,
652
+ date: result.date,
653
+ roadmap_updated: result.roadmap_updated,
654
+ state_updated: result.state_updated,
655
+ }, raw);
579
656
  }
580
657
  catch (e) {
581
- /* optional op, ignore */
582
- if (process.env.MAXSIM_DEBUG)
583
- console.error(e);
658
+ (0, core_js_1.error)(e.message);
584
659
  }
585
- // Update STATE.md
586
- if (node_fs_1.default.existsSync(statePath)) {
587
- let stateContent = node_fs_1.default.readFileSync(statePath, 'utf-8');
588
- stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
589
- if (nextPhaseName) {
590
- stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, ' ')}`);
591
- }
592
- stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? 'Milestone complete' : 'Ready to plan'}`);
593
- stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
594
- stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
595
- stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`);
596
- node_fs_1.default.writeFileSync(statePath, stateContent, 'utf-8');
597
- }
598
- (0, core_js_1.output)({
599
- completed_phase: phaseNum,
600
- phase_name: phaseInfo.phase_name,
601
- plans_executed: `${summaryCount}/${planCount}`,
602
- next_phase: nextPhaseNum,
603
- next_phase_name: nextPhaseName,
604
- is_last_phase: isLastPhase,
605
- date: today,
606
- roadmap_updated: node_fs_1.default.existsSync(roadmapPath),
607
- state_updated: node_fs_1.default.existsSync(statePath),
608
- }, raw);
609
660
  }
610
661
  //# sourceMappingURL=phase.js.map