maxsimcli 4.1.0 → 4.2.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 +8 -0
- package/dist/assets/dashboard/client/assets/{index-C_eAetZJ.js → index-BcRHShXD.js} +59 -59
- package/dist/assets/dashboard/client/assets/index-C199D4Eb.css +32 -0
- package/dist/assets/dashboard/client/index.html +2 -2
- package/dist/assets/dashboard/server.js +26 -11
- package/dist/assets/templates/agents/AGENTS.md +18 -69
- package/dist/assets/templates/agents/maxsim-code-reviewer.md +17 -92
- package/dist/assets/templates/agents/maxsim-codebase-mapper.md +57 -694
- package/dist/assets/templates/agents/maxsim-debugger.md +80 -925
- package/dist/assets/templates/agents/maxsim-executor.md +94 -431
- package/dist/assets/templates/agents/maxsim-integration-checker.md +51 -319
- package/dist/assets/templates/agents/maxsim-phase-researcher.md +63 -429
- package/dist/assets/templates/agents/maxsim-plan-checker.md +79 -568
- package/dist/assets/templates/agents/maxsim-planner.md +125 -855
- package/dist/assets/templates/agents/maxsim-project-researcher.md +32 -472
- package/dist/assets/templates/agents/maxsim-research-synthesizer.md +25 -134
- package/dist/assets/templates/agents/maxsim-roadmapper.md +66 -480
- package/dist/assets/templates/agents/maxsim-spec-reviewer.md +13 -55
- package/dist/assets/templates/agents/maxsim-verifier.md +95 -450
- package/dist/assets/templates/commands/maxsim/artefakte.md +122 -0
- package/dist/assets/templates/commands/maxsim/batch.md +42 -0
- package/dist/assets/templates/commands/maxsim/check-todos.md +1 -0
- package/dist/assets/templates/commands/maxsim/sdd.md +39 -0
- package/dist/assets/templates/references/thinking-partner.md +33 -0
- package/dist/assets/templates/workflows/batch.md +420 -0
- package/dist/assets/templates/workflows/check-todos.md +85 -1
- package/dist/assets/templates/workflows/discuss-phase.md +31 -0
- package/dist/assets/templates/workflows/execute-plan.md +96 -27
- package/dist/assets/templates/workflows/help.md +47 -0
- package/dist/assets/templates/workflows/sdd.md +426 -0
- package/dist/backend-server.cjs +174 -51
- package/dist/backend-server.cjs.map +1 -1
- package/dist/cli.cjs +310 -146
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/core/artefakte.d.ts.map +1 -1
- package/dist/core/artefakte.js +16 -0
- package/dist/core/artefakte.js.map +1 -1
- package/dist/core/context-loader.d.ts +1 -0
- package/dist/core/context-loader.d.ts.map +1 -1
- package/dist/core/context-loader.js +58 -0
- package/dist/core/context-loader.js.map +1 -1
- package/dist/core/core.d.ts +6 -0
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.js +238 -0
- 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 +5 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/phase.d.ts +11 -11
- package/dist/core/phase.d.ts.map +1 -1
- package/dist/core/phase.js +88 -73
- package/dist/core/phase.js.map +1 -1
- package/dist/core/roadmap.d.ts +2 -2
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/roadmap.js +11 -10
- package/dist/core/roadmap.js.map +1 -1
- package/dist/core/state.d.ts +11 -11
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +60 -54
- package/dist/core/state.js.map +1 -1
- package/dist/core-RRjCSt0G.cjs.map +1 -1
- package/dist/{lifecycle-D4E9yP6E.cjs → lifecycle-0M4VqOMm.cjs} +2 -2
- package/dist/{lifecycle-D4E9yP6E.cjs.map → lifecycle-0M4VqOMm.cjs.map} +1 -1
- package/dist/mcp/context-tools.d.ts.map +1 -1
- package/dist/mcp/context-tools.js +7 -3
- package/dist/mcp/context-tools.js.map +1 -1
- package/dist/mcp/phase-tools.js +3 -3
- package/dist/mcp/phase-tools.js.map +1 -1
- package/dist/mcp-server.cjs +163 -40
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/{server-pvY2WbKj.cjs → server-G1MIg_Oe.cjs} +7 -7
- package/dist/server-G1MIg_Oe.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/assets/dashboard/client/assets/index-CmiJKqOU.css +0 -32
- package/dist/server-pvY2WbKj.cjs.map +0 -1
package/dist/core/phase.js
CHANGED
|
@@ -20,24 +20,29 @@ exports.cmdPhaseAdd = cmdPhaseAdd;
|
|
|
20
20
|
exports.cmdPhaseInsert = cmdPhaseInsert;
|
|
21
21
|
exports.cmdPhaseRemove = cmdPhaseRemove;
|
|
22
22
|
exports.cmdPhaseComplete = cmdPhaseComplete;
|
|
23
|
-
const node_fs_1 =
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
24
|
const node_path_1 = __importDefault(require("node:path"));
|
|
25
25
|
const core_js_1 = require("./core.js");
|
|
26
26
|
const frontmatter_js_1 = require("./frontmatter.js");
|
|
27
27
|
const types_js_1 = require("./types.js");
|
|
28
28
|
// ─── Stub scaffolding ───────────────────────────────────────────────────────
|
|
29
|
-
function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
29
|
+
async function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
30
30
|
const today = (0, core_js_1.todayISO)();
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
await Promise.all([
|
|
32
|
+
node_fs_1.promises.writeFile(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`),
|
|
33
|
+
node_fs_1.promises.writeFile(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`),
|
|
34
|
+
]);
|
|
33
35
|
}
|
|
34
36
|
// ─── Core functions ─────────────────────────────────────────────────────────
|
|
35
|
-
function phaseAddCore(cwd, description, options) {
|
|
37
|
+
async function phaseAddCore(cwd, description, options) {
|
|
36
38
|
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
37
|
-
|
|
39
|
+
let content;
|
|
40
|
+
try {
|
|
41
|
+
content = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
38
44
|
throw new Error('ROADMAP.md not found');
|
|
39
45
|
}
|
|
40
|
-
const content = node_fs_1.default.readFileSync(rmPath, 'utf-8');
|
|
41
46
|
const slug = (0, core_js_1.generateSlugInternal)(description);
|
|
42
47
|
const phasePattern = (0, core_js_1.getPhasePattern)();
|
|
43
48
|
let maxPhase = 0;
|
|
@@ -51,10 +56,10 @@ function phaseAddCore(cwd, description, options) {
|
|
|
51
56
|
const paddedNum = String(newPhaseNum).padStart(2, '0');
|
|
52
57
|
const dirName = `${paddedNum}-${slug}`;
|
|
53
58
|
const dirPath = (0, core_js_1.planningPath)(cwd, 'phases', dirName);
|
|
54
|
-
node_fs_1.
|
|
55
|
-
node_fs_1.
|
|
59
|
+
await node_fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
60
|
+
await node_fs_1.promises.writeFile(node_path_1.default.join(dirPath, '.gitkeep'), '');
|
|
56
61
|
if (options?.includeStubs) {
|
|
57
|
-
scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
62
|
+
await scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
58
63
|
}
|
|
59
64
|
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`;
|
|
60
65
|
let updatedContent;
|
|
@@ -65,7 +70,7 @@ function phaseAddCore(cwd, description, options) {
|
|
|
65
70
|
else {
|
|
66
71
|
updatedContent = content + phaseEntry;
|
|
67
72
|
}
|
|
68
|
-
node_fs_1.
|
|
73
|
+
await node_fs_1.promises.writeFile(rmPath, updatedContent, 'utf-8');
|
|
69
74
|
return {
|
|
70
75
|
phase_number: newPhaseNum,
|
|
71
76
|
padded: paddedNum,
|
|
@@ -74,12 +79,15 @@ function phaseAddCore(cwd, description, options) {
|
|
|
74
79
|
description,
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
|
-
function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
82
|
+
async function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
78
83
|
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
79
|
-
|
|
84
|
+
let content;
|
|
85
|
+
try {
|
|
86
|
+
content = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
80
89
|
throw new Error('ROADMAP.md not found');
|
|
81
90
|
}
|
|
82
|
-
const content = node_fs_1.default.readFileSync(rmPath, 'utf-8');
|
|
83
91
|
const slug = (0, core_js_1.generateSlugInternal)(description);
|
|
84
92
|
const normalizedAfter = (0, core_js_1.normalizePhaseName)(afterPhase);
|
|
85
93
|
const unpadded = normalizedAfter.replace(/^0+/, '');
|
|
@@ -92,7 +100,7 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
92
100
|
const normalizedBase = (0, core_js_1.normalizePhaseName)(afterPhase);
|
|
93
101
|
const existingDecimals = [];
|
|
94
102
|
try {
|
|
95
|
-
const dirs = (0, core_js_1.
|
|
103
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath);
|
|
96
104
|
const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
|
|
97
105
|
for (const dir of dirs) {
|
|
98
106
|
const dm = dir.match(decimalPattern);
|
|
@@ -107,10 +115,10 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
107
115
|
const decimalPhase = `${normalizedBase}.${nextDecimal}`;
|
|
108
116
|
const dirName = `${decimalPhase}-${slug}`;
|
|
109
117
|
const dirPath = (0, core_js_1.planningPath)(cwd, 'phases', dirName);
|
|
110
|
-
node_fs_1.
|
|
111
|
-
node_fs_1.
|
|
118
|
+
await node_fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
119
|
+
await node_fs_1.promises.writeFile(node_path_1.default.join(dirPath, '.gitkeep'), '');
|
|
112
120
|
if (options?.includeStubs) {
|
|
113
|
-
scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
121
|
+
await scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
114
122
|
}
|
|
115
123
|
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`;
|
|
116
124
|
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
|
@@ -129,7 +137,7 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
129
137
|
insertIdx = content.length;
|
|
130
138
|
}
|
|
131
139
|
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
132
|
-
node_fs_1.
|
|
140
|
+
await node_fs_1.promises.writeFile(rmPath, updatedContent, 'utf-8');
|
|
133
141
|
return {
|
|
134
142
|
phase_number: decimalPhase,
|
|
135
143
|
after_phase: afterPhase,
|
|
@@ -138,20 +146,21 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
138
146
|
description,
|
|
139
147
|
};
|
|
140
148
|
}
|
|
141
|
-
function phaseCompleteCore(cwd, phaseNum) {
|
|
149
|
+
async function phaseCompleteCore(cwd, phaseNum) {
|
|
142
150
|
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
143
151
|
const stPath = (0, core_js_1.statePath)(cwd);
|
|
144
152
|
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
145
153
|
const today = (0, core_js_1.todayISO)();
|
|
146
|
-
const phaseInfo = (0, core_js_1.
|
|
154
|
+
const phaseInfo = await (0, core_js_1.findPhaseInternalAsync)(cwd, phaseNum);
|
|
147
155
|
if (!phaseInfo) {
|
|
148
156
|
throw new Error(`Phase ${phaseNum} not found`);
|
|
149
157
|
}
|
|
150
158
|
const planCount = phaseInfo.plans.length;
|
|
151
159
|
const summaryCount = phaseInfo.summaries.length;
|
|
152
160
|
let requirementsUpdated = false;
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
const rmExists = await (0, core_js_1.pathExistsAsync)(rmPath);
|
|
162
|
+
if (rmExists) {
|
|
163
|
+
let roadmapContent = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
155
164
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${(0, core_js_1.escapePhaseNum)(phaseNum)}[:\\s][^\\n]*)`, 'i');
|
|
156
165
|
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
157
166
|
const phaseEscaped = (0, core_js_1.escapePhaseNum)(phaseNum);
|
|
@@ -160,21 +169,21 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
160
169
|
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
|
|
161
170
|
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
162
171
|
(0, core_js_1.debugLog)('phase-complete-write', `writing ROADMAP.md for phase ${phaseNum}`);
|
|
163
|
-
node_fs_1.
|
|
172
|
+
await node_fs_1.promises.writeFile(rmPath, roadmapContent, 'utf-8');
|
|
164
173
|
(0, core_js_1.debugLog)('phase-complete-write', `ROADMAP.md updated for phase ${phaseNum}`);
|
|
165
174
|
// Update REQUIREMENTS.md
|
|
166
175
|
const reqPath = (0, core_js_1.planningPath)(cwd, 'REQUIREMENTS.md');
|
|
167
|
-
if (
|
|
176
|
+
if (await (0, core_js_1.pathExistsAsync)(reqPath)) {
|
|
168
177
|
const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${(0, core_js_1.escapePhaseNum)(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, 'i'));
|
|
169
178
|
if (reqMatch) {
|
|
170
179
|
const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
|
|
171
|
-
let reqContent = node_fs_1.
|
|
180
|
+
let reqContent = await node_fs_1.promises.readFile(reqPath, 'utf-8');
|
|
172
181
|
for (const reqId of reqIds) {
|
|
173
182
|
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, 'gi'), '$1x$2');
|
|
174
183
|
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'), '$1 Complete $2');
|
|
175
184
|
}
|
|
176
185
|
(0, core_js_1.debugLog)('phase-complete-write', `writing REQUIREMENTS.md for phase ${phaseNum}`);
|
|
177
|
-
node_fs_1.
|
|
186
|
+
await node_fs_1.promises.writeFile(reqPath, reqContent, 'utf-8');
|
|
178
187
|
(0, core_js_1.debugLog)('phase-complete-write', `REQUIREMENTS.md updated for phase ${phaseNum}`);
|
|
179
188
|
requirementsUpdated = true;
|
|
180
189
|
}
|
|
@@ -185,7 +194,7 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
185
194
|
let nextPhaseName = null;
|
|
186
195
|
let isLastPhase = true;
|
|
187
196
|
try {
|
|
188
|
-
const dirs = (0, core_js_1.
|
|
197
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
189
198
|
for (const dir of dirs) {
|
|
190
199
|
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
191
200
|
if (dm) {
|
|
@@ -202,8 +211,9 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
202
211
|
(0, core_js_1.debugLog)('phase-complete-next-phase-scan-failed', e);
|
|
203
212
|
}
|
|
204
213
|
// Update STATE.md
|
|
205
|
-
|
|
206
|
-
|
|
214
|
+
const stExists = await (0, core_js_1.pathExistsAsync)(stPath);
|
|
215
|
+
if (stExists) {
|
|
216
|
+
let stateContent = await node_fs_1.promises.readFile(stPath, 'utf-8');
|
|
207
217
|
stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
|
|
208
218
|
if (nextPhaseName) {
|
|
209
219
|
stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, ' ')}`);
|
|
@@ -213,7 +223,7 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
213
223
|
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
214
224
|
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`);
|
|
215
225
|
(0, core_js_1.debugLog)('phase-complete-write', `writing STATE.md for phase ${phaseNum}`);
|
|
216
|
-
node_fs_1.
|
|
226
|
+
await node_fs_1.promises.writeFile(stPath, stateContent, 'utf-8');
|
|
217
227
|
(0, core_js_1.debugLog)('phase-complete-write', `STATE.md updated for phase ${phaseNum}`);
|
|
218
228
|
}
|
|
219
229
|
return {
|
|
@@ -224,8 +234,8 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
224
234
|
next_phase_name: nextPhaseName,
|
|
225
235
|
is_last_phase: isLastPhase,
|
|
226
236
|
date: today,
|
|
227
|
-
roadmap_updated:
|
|
228
|
-
state_updated:
|
|
237
|
+
roadmap_updated: rmExists,
|
|
238
|
+
state_updated: stExists,
|
|
229
239
|
requirements_updated: requirementsUpdated,
|
|
230
240
|
};
|
|
231
241
|
}
|
|
@@ -233,7 +243,7 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
233
243
|
async function cmdPhasesList(cwd, options) {
|
|
234
244
|
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
235
245
|
const { type, phase, includeArchived, offset, limit } = options;
|
|
236
|
-
if (!
|
|
246
|
+
if (!(await (0, core_js_1.pathExistsAsync)(phasesDirPath))) {
|
|
237
247
|
if (type) {
|
|
238
248
|
return (0, types_js_1.cmdOk)({ files: [], count: 0, total: 0 }, '');
|
|
239
249
|
}
|
|
@@ -244,7 +254,7 @@ async function cmdPhasesList(cwd, options) {
|
|
|
244
254
|
try {
|
|
245
255
|
let dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath);
|
|
246
256
|
if (includeArchived) {
|
|
247
|
-
const archived = (0, core_js_1.
|
|
257
|
+
const archived = await (0, core_js_1.getArchivedPhaseDirsAsync)(cwd);
|
|
248
258
|
for (const a of archived) {
|
|
249
259
|
dirs.push(`${a.name} [${a.milestone}]`);
|
|
250
260
|
}
|
|
@@ -261,7 +271,7 @@ async function cmdPhasesList(cwd, options) {
|
|
|
261
271
|
if (type) {
|
|
262
272
|
const fileResults = await Promise.all(dirs.map(async (dir) => {
|
|
263
273
|
const dirPath = node_path_1.default.join(phasesDirPath, dir);
|
|
264
|
-
const dirFiles = await node_fs_1.
|
|
274
|
+
const dirFiles = await node_fs_1.promises.readdir(dirPath);
|
|
265
275
|
let filtered;
|
|
266
276
|
if (type === 'plans') {
|
|
267
277
|
filtered = dirFiles.filter(core_js_1.isPlanFile);
|
|
@@ -294,14 +304,14 @@ async function cmdPhasesList(cwd, options) {
|
|
|
294
304
|
}
|
|
295
305
|
}
|
|
296
306
|
// ─── Next decimal ───────────────────────────────────────────────────────────
|
|
297
|
-
function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
307
|
+
async function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
298
308
|
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
299
309
|
const normalized = (0, core_js_1.normalizePhaseName)(basePhase);
|
|
300
|
-
if (!
|
|
310
|
+
if (!(await (0, core_js_1.pathExistsAsync)(phasesDirPath))) {
|
|
301
311
|
return (0, types_js_1.cmdOk)({ found: false, base_phase: normalized, next: `${normalized}.1`, existing: [] }, `${normalized}.1`);
|
|
302
312
|
}
|
|
303
313
|
try {
|
|
304
|
-
const dirs = (0, core_js_1.
|
|
314
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath);
|
|
305
315
|
const baseExists = dirs.some(d => d.startsWith(normalized + '-') || d === normalized);
|
|
306
316
|
const decimalPattern = new RegExp(`^${normalized}\\.(\\d+)`);
|
|
307
317
|
const existingDecimals = [];
|
|
@@ -332,7 +342,7 @@ function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
|
332
342
|
}
|
|
333
343
|
}
|
|
334
344
|
// ─── Find phase ─────────────────────────────────────────────────────────────
|
|
335
|
-
function cmdFindPhase(cwd, phase) {
|
|
345
|
+
async function cmdFindPhase(cwd, phase) {
|
|
336
346
|
if (!phase) {
|
|
337
347
|
return (0, types_js_1.cmdErr)('phase identifier required');
|
|
338
348
|
}
|
|
@@ -340,7 +350,7 @@ function cmdFindPhase(cwd, phase) {
|
|
|
340
350
|
const normalized = (0, core_js_1.normalizePhaseName)(phase);
|
|
341
351
|
const notFound = { found: false, directory: null, phase_number: null, phase_name: null, plans: [], summaries: [] };
|
|
342
352
|
try {
|
|
343
|
-
const dirs = (0, core_js_1.
|
|
353
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
344
354
|
const match = dirs.find(d => d.startsWith(normalized));
|
|
345
355
|
if (!match) {
|
|
346
356
|
return (0, types_js_1.cmdOk)(notFound, '');
|
|
@@ -349,7 +359,7 @@ function cmdFindPhase(cwd, phase) {
|
|
|
349
359
|
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
350
360
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
351
361
|
const phaseDir = node_path_1.default.join(phasesDirPath, match);
|
|
352
|
-
const phaseFiles = node_fs_1.
|
|
362
|
+
const phaseFiles = await node_fs_1.promises.readdir(phaseDir);
|
|
353
363
|
const plans = phaseFiles.filter(core_js_1.isPlanFile).sort();
|
|
354
364
|
const summaries = phaseFiles.filter(core_js_1.isSummaryFile).sort();
|
|
355
365
|
const result = {
|
|
@@ -367,7 +377,7 @@ function cmdFindPhase(cwd, phase) {
|
|
|
367
377
|
}
|
|
368
378
|
}
|
|
369
379
|
// ─── Phase plan index ───────────────────────────────────────────────────────
|
|
370
|
-
function cmdPhasePlanIndex(cwd, phase) {
|
|
380
|
+
async function cmdPhasePlanIndex(cwd, phase) {
|
|
371
381
|
if (!phase) {
|
|
372
382
|
return (0, types_js_1.cmdErr)('phase required for phase-plan-index');
|
|
373
383
|
}
|
|
@@ -376,7 +386,7 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
376
386
|
let phaseDir = null;
|
|
377
387
|
let phaseDirName = null;
|
|
378
388
|
try {
|
|
379
|
-
const dirs = (0, core_js_1.
|
|
389
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
380
390
|
const match = dirs.find(d => d.startsWith(normalized));
|
|
381
391
|
if (match) {
|
|
382
392
|
phaseDir = node_path_1.default.join(phasesDirPath, match);
|
|
@@ -389,7 +399,7 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
389
399
|
if (!phaseDir) {
|
|
390
400
|
return (0, types_js_1.cmdOk)({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], has_checkpoints: false });
|
|
391
401
|
}
|
|
392
|
-
const phaseFiles = node_fs_1.
|
|
402
|
+
const phaseFiles = await node_fs_1.promises.readdir(phaseDir);
|
|
393
403
|
const planFiles = phaseFiles.filter(core_js_1.isPlanFile).sort();
|
|
394
404
|
const summaryFiles = phaseFiles.filter(core_js_1.isSummaryFile);
|
|
395
405
|
const completedPlanIds = new Set(summaryFiles.map(core_js_1.summaryId));
|
|
@@ -397,10 +407,12 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
397
407
|
const waves = {};
|
|
398
408
|
const incomplete = [];
|
|
399
409
|
let hasCheckpoints = false;
|
|
400
|
-
|
|
410
|
+
// Read all plan files in parallel since each read is independent
|
|
411
|
+
const planContents = await Promise.all(planFiles.map(planFile => node_fs_1.promises.readFile(node_path_1.default.join(phaseDir, planFile), 'utf-8')));
|
|
412
|
+
for (let i = 0; i < planFiles.length; i++) {
|
|
413
|
+
const planFile = planFiles[i];
|
|
401
414
|
const id = (0, core_js_1.planId)(planFile);
|
|
402
|
-
const
|
|
403
|
-
const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
|
|
415
|
+
const content = planContents[i];
|
|
404
416
|
const fm = (0, frontmatter_js_1.extractFrontmatter)(content);
|
|
405
417
|
const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
406
418
|
const taskCount = taskMatches.length;
|
|
@@ -439,12 +451,12 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
439
451
|
return (0, types_js_1.cmdOk)({ phase: normalized, plans, waves, incomplete, has_checkpoints: hasCheckpoints });
|
|
440
452
|
}
|
|
441
453
|
// ─── Phase add ──────────────────────────────────────────────────────────────
|
|
442
|
-
function cmdPhaseAdd(cwd, description) {
|
|
454
|
+
async function cmdPhaseAdd(cwd, description) {
|
|
443
455
|
if (!description) {
|
|
444
456
|
return (0, types_js_1.cmdErr)('description required for phase add');
|
|
445
457
|
}
|
|
446
458
|
try {
|
|
447
|
-
const result = phaseAddCore(cwd, description, { includeStubs: false });
|
|
459
|
+
const result = await phaseAddCore(cwd, description, { includeStubs: false });
|
|
448
460
|
return (0, types_js_1.cmdOk)({ phase_number: result.phase_number, padded: result.padded, name: result.description, slug: result.slug, directory: result.directory }, result.padded);
|
|
449
461
|
}
|
|
450
462
|
catch (e) {
|
|
@@ -452,12 +464,12 @@ function cmdPhaseAdd(cwd, description) {
|
|
|
452
464
|
}
|
|
453
465
|
}
|
|
454
466
|
// ─── Phase insert ───────────────────────────────────────────────────────────
|
|
455
|
-
function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
467
|
+
async function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
456
468
|
if (!afterPhase || !description) {
|
|
457
469
|
return (0, types_js_1.cmdErr)('after-phase and description required for phase insert');
|
|
458
470
|
}
|
|
459
471
|
try {
|
|
460
|
-
const result = phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
472
|
+
const result = await phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
461
473
|
return (0, types_js_1.cmdOk)({ phase_number: result.phase_number, after_phase: result.after_phase, name: result.description, slug: result.slug, directory: result.directory }, result.phase_number);
|
|
462
474
|
}
|
|
463
475
|
catch (e) {
|
|
@@ -465,21 +477,21 @@ function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
|
465
477
|
}
|
|
466
478
|
}
|
|
467
479
|
// ─── Phase remove ───────────────────────────────────────────────────────────
|
|
468
|
-
function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
480
|
+
async function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
469
481
|
if (!targetPhase) {
|
|
470
482
|
return (0, types_js_1.cmdErr)('phase number required for phase remove');
|
|
471
483
|
}
|
|
472
484
|
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
473
485
|
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
474
486
|
const force = options.force || false;
|
|
475
|
-
if (!
|
|
487
|
+
if (!(await (0, core_js_1.pathExistsAsync)(rmPath))) {
|
|
476
488
|
return (0, types_js_1.cmdErr)('ROADMAP.md not found');
|
|
477
489
|
}
|
|
478
490
|
const normalized = (0, core_js_1.normalizePhaseName)(targetPhase);
|
|
479
491
|
const isDecimal = targetPhase.includes('.');
|
|
480
492
|
let targetDir = null;
|
|
481
493
|
try {
|
|
482
|
-
const dirs = (0, core_js_1.
|
|
494
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
483
495
|
targetDir = dirs.find(d => d.startsWith(normalized + '-') || d === normalized) || null;
|
|
484
496
|
}
|
|
485
497
|
catch (e) {
|
|
@@ -487,14 +499,14 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
487
499
|
}
|
|
488
500
|
if (targetDir && !force) {
|
|
489
501
|
const targetPath = node_path_1.default.join(phasesDirPath, targetDir);
|
|
490
|
-
const files = node_fs_1.
|
|
502
|
+
const files = await node_fs_1.promises.readdir(targetPath);
|
|
491
503
|
const summaries = files.filter(core_js_1.isSummaryFile);
|
|
492
504
|
if (summaries.length > 0) {
|
|
493
505
|
return (0, types_js_1.cmdErr)(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
|
|
494
506
|
}
|
|
495
507
|
}
|
|
496
508
|
if (targetDir) {
|
|
497
|
-
node_fs_1.
|
|
509
|
+
await node_fs_1.promises.rm(node_path_1.default.join(phasesDirPath, targetDir), { recursive: true, force: true });
|
|
498
510
|
}
|
|
499
511
|
const renamedDirs = [];
|
|
500
512
|
const renamedFiles = [];
|
|
@@ -503,7 +515,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
503
515
|
const baseInt = baseParts[0];
|
|
504
516
|
const removedDecimal = parseInt(baseParts[1], 10);
|
|
505
517
|
try {
|
|
506
|
-
const dirs = (0, core_js_1.
|
|
518
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
507
519
|
const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
|
|
508
520
|
const toRename = [];
|
|
509
521
|
for (const dir of dirs) {
|
|
@@ -513,18 +525,19 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
513
525
|
}
|
|
514
526
|
}
|
|
515
527
|
toRename.sort((a, b) => b.oldDecimal - a.oldDecimal);
|
|
528
|
+
// Sequential renames — order matters
|
|
516
529
|
for (const item of toRename) {
|
|
517
530
|
const newDecimal = item.oldDecimal - 1;
|
|
518
531
|
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
519
532
|
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
520
533
|
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
521
|
-
node_fs_1.
|
|
534
|
+
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, item.dir), node_path_1.default.join(phasesDirPath, newDirName));
|
|
522
535
|
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
523
|
-
const dirFiles = node_fs_1.
|
|
536
|
+
const dirFiles = await node_fs_1.promises.readdir(node_path_1.default.join(phasesDirPath, newDirName));
|
|
524
537
|
for (const f of dirFiles) {
|
|
525
538
|
if (f.includes(oldPhaseId)) {
|
|
526
539
|
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
527
|
-
node_fs_1.
|
|
540
|
+
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, newDirName, f), node_path_1.default.join(phasesDirPath, newDirName, newFileName));
|
|
528
541
|
renamedFiles.push({ from: f, to: newFileName });
|
|
529
542
|
}
|
|
530
543
|
}
|
|
@@ -537,7 +550,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
537
550
|
else {
|
|
538
551
|
const removedInt = parseInt(normalized, 10);
|
|
539
552
|
try {
|
|
540
|
-
const dirs = (0, core_js_1.
|
|
553
|
+
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
541
554
|
const toRename = [];
|
|
542
555
|
for (const dir of dirs) {
|
|
543
556
|
const dm = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
|
@@ -559,6 +572,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
559
572
|
return b.oldInt - a.oldInt;
|
|
560
573
|
return (b.decimal || 0) - (a.decimal || 0);
|
|
561
574
|
});
|
|
575
|
+
// Sequential renames — order matters
|
|
562
576
|
for (const item of toRename) {
|
|
563
577
|
const newInt = item.oldInt - 1;
|
|
564
578
|
const newPadded = String(newInt).padStart(2, '0');
|
|
@@ -568,13 +582,13 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
568
582
|
const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
|
|
569
583
|
const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
|
|
570
584
|
const newDirName = `${newPrefix}-${item.slug}`;
|
|
571
|
-
node_fs_1.
|
|
585
|
+
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, item.dir), node_path_1.default.join(phasesDirPath, newDirName));
|
|
572
586
|
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
573
|
-
const dirFiles = node_fs_1.
|
|
587
|
+
const dirFiles = await node_fs_1.promises.readdir(node_path_1.default.join(phasesDirPath, newDirName));
|
|
574
588
|
for (const f of dirFiles) {
|
|
575
589
|
if (f.startsWith(oldPrefix)) {
|
|
576
590
|
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
577
|
-
node_fs_1.
|
|
591
|
+
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, newDirName, f), node_path_1.default.join(phasesDirPath, newDirName, newFileName));
|
|
578
592
|
renamedFiles.push({ from: f, to: newFileName });
|
|
579
593
|
}
|
|
580
594
|
}
|
|
@@ -585,7 +599,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
585
599
|
}
|
|
586
600
|
}
|
|
587
601
|
// Update ROADMAP.md
|
|
588
|
-
let roadmapContent = node_fs_1.
|
|
602
|
+
let roadmapContent = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
589
603
|
const targetEscaped = (0, core_js_1.escapePhaseNum)(targetPhase);
|
|
590
604
|
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`, 'i');
|
|
591
605
|
roadmapContent = roadmapContent.replace(sectionPattern, '');
|
|
@@ -609,11 +623,12 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
609
623
|
roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, 'gi'), `$1${newStr}`);
|
|
610
624
|
}
|
|
611
625
|
}
|
|
612
|
-
node_fs_1.
|
|
626
|
+
await node_fs_1.promises.writeFile(rmPath, roadmapContent, 'utf-8');
|
|
613
627
|
// Update STATE.md phase count
|
|
614
628
|
const stPath = (0, core_js_1.statePath)(cwd);
|
|
615
|
-
|
|
616
|
-
|
|
629
|
+
const stExists = await (0, core_js_1.pathExistsAsync)(stPath);
|
|
630
|
+
if (stExists) {
|
|
631
|
+
let stateContent = await node_fs_1.promises.readFile(stPath, 'utf-8');
|
|
617
632
|
const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
|
|
618
633
|
const totalMatch = stateContent.match(totalPattern);
|
|
619
634
|
if (totalMatch) {
|
|
@@ -626,7 +641,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
626
641
|
const oldTotal = parseInt(ofMatch[2], 10);
|
|
627
642
|
stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
|
|
628
643
|
}
|
|
629
|
-
node_fs_1.
|
|
644
|
+
await node_fs_1.promises.writeFile(stPath, stateContent, 'utf-8');
|
|
630
645
|
}
|
|
631
646
|
return (0, types_js_1.cmdOk)({
|
|
632
647
|
removed: targetPhase,
|
|
@@ -634,16 +649,16 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
634
649
|
renamed_directories: renamedDirs,
|
|
635
650
|
renamed_files: renamedFiles,
|
|
636
651
|
roadmap_updated: true,
|
|
637
|
-
state_updated:
|
|
652
|
+
state_updated: stExists,
|
|
638
653
|
});
|
|
639
654
|
}
|
|
640
655
|
// ─── Phase complete ─────────────────────────────────────────────────────────
|
|
641
|
-
function cmdPhaseComplete(cwd, phaseNum) {
|
|
656
|
+
async function cmdPhaseComplete(cwd, phaseNum) {
|
|
642
657
|
if (!phaseNum) {
|
|
643
658
|
return (0, types_js_1.cmdErr)('phase number required for phase complete');
|
|
644
659
|
}
|
|
645
660
|
try {
|
|
646
|
-
const result = phaseCompleteCore(cwd, phaseNum);
|
|
661
|
+
const result = await phaseCompleteCore(cwd, phaseNum);
|
|
647
662
|
return (0, types_js_1.cmdOk)({
|
|
648
663
|
completed_phase: result.completed_phase,
|
|
649
664
|
phase_name: result.phase_name,
|