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/cli.cjs
CHANGED
|
@@ -607,7 +607,7 @@ var require_has_flag = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
607
607
|
//#endregion
|
|
608
608
|
//#region ../../node_modules/supports-color/index.js
|
|
609
609
|
var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
610
|
-
const os$
|
|
610
|
+
const os$6 = require("os");
|
|
611
611
|
const tty$2 = require("tty");
|
|
612
612
|
const hasFlag = require_has_flag();
|
|
613
613
|
const { env } = process;
|
|
@@ -634,7 +634,7 @@ var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
634
634
|
const min = forceColor || 0;
|
|
635
635
|
if (env.TERM === "dumb") return min;
|
|
636
636
|
if (process.platform === "win32") {
|
|
637
|
-
const osRelease = os$
|
|
637
|
+
const osRelease = os$6.release().split(".");
|
|
638
638
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
639
639
|
return 1;
|
|
640
640
|
}
|
|
@@ -5153,6 +5153,100 @@ function getMilestoneInfo(cwd) {
|
|
|
5153
5153
|
};
|
|
5154
5154
|
}
|
|
5155
5155
|
}
|
|
5156
|
+
async function pathExistsAsync(p) {
|
|
5157
|
+
try {
|
|
5158
|
+
await node_fs.promises.access(p);
|
|
5159
|
+
return true;
|
|
5160
|
+
} catch {
|
|
5161
|
+
return false;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
async function searchPhaseInDirAsync(baseDir, relBase, normalized) {
|
|
5165
|
+
try {
|
|
5166
|
+
const match = (await listSubDirsAsync(baseDir, true)).find((d) => d.startsWith(normalized));
|
|
5167
|
+
if (!match) return null;
|
|
5168
|
+
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
5169
|
+
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
5170
|
+
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
5171
|
+
const phaseDir = node_path.default.join(baseDir, match);
|
|
5172
|
+
const phaseFiles = await node_fs.promises.readdir(phaseDir);
|
|
5173
|
+
const plans = phaseFiles.filter(isPlanFile).sort();
|
|
5174
|
+
const summaries = phaseFiles.filter(isSummaryFile).sort();
|
|
5175
|
+
const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
5176
|
+
const hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
5177
|
+
const hasVerification = phaseFiles.some((f) => f.endsWith("-VERIFICATION.md") || f === "VERIFICATION.md");
|
|
5178
|
+
const completedPlanIds = new Set(summaries.map(summaryId));
|
|
5179
|
+
const incompletePlans = plans.filter((p) => !completedPlanIds.has(planId(p)));
|
|
5180
|
+
return {
|
|
5181
|
+
found: true,
|
|
5182
|
+
directory: node_path.default.join(relBase, match),
|
|
5183
|
+
phase_number: phaseNumber,
|
|
5184
|
+
phase_name: phaseName,
|
|
5185
|
+
phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") : null,
|
|
5186
|
+
plans,
|
|
5187
|
+
summaries,
|
|
5188
|
+
incomplete_plans: incompletePlans,
|
|
5189
|
+
has_research: hasResearch,
|
|
5190
|
+
has_context: hasContext,
|
|
5191
|
+
has_verification: hasVerification
|
|
5192
|
+
};
|
|
5193
|
+
} catch (e) {
|
|
5194
|
+
debugLog("search-phase-in-dir-async-failed", {
|
|
5195
|
+
dir: baseDir,
|
|
5196
|
+
phase: normalized,
|
|
5197
|
+
error: errorMsg(e)
|
|
5198
|
+
});
|
|
5199
|
+
return null;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
async function findPhaseInternalAsync(cwd, phase) {
|
|
5203
|
+
if (!phase) return null;
|
|
5204
|
+
const pd = phasesPath(cwd);
|
|
5205
|
+
const normalized = normalizePhaseName(phase);
|
|
5206
|
+
const current = await searchPhaseInDirAsync(pd, node_path.default.join(".planning", "phases"), normalized);
|
|
5207
|
+
if (current) return current;
|
|
5208
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
5209
|
+
if (!await pathExistsAsync(milestonesDir)) return null;
|
|
5210
|
+
try {
|
|
5211
|
+
const archiveDirs = (await node_fs.promises.readdir(milestonesDir, { withFileTypes: true })).filter((e) => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name)).map((e) => e.name).sort().reverse();
|
|
5212
|
+
for (const archiveName of archiveDirs) {
|
|
5213
|
+
const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
|
|
5214
|
+
if (!versionMatch) continue;
|
|
5215
|
+
const version = versionMatch[1];
|
|
5216
|
+
const result = await searchPhaseInDirAsync(node_path.default.join(milestonesDir, archiveName), node_path.default.join(".planning", "milestones", archiveName), normalized);
|
|
5217
|
+
if (result) {
|
|
5218
|
+
result.archived = version;
|
|
5219
|
+
return result;
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
} catch (e) {
|
|
5223
|
+
debugLog("find-phase-async-milestone-search-failed", e);
|
|
5224
|
+
}
|
|
5225
|
+
return null;
|
|
5226
|
+
}
|
|
5227
|
+
async function getArchivedPhaseDirsAsync(cwd) {
|
|
5228
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
5229
|
+
const results = [];
|
|
5230
|
+
try {
|
|
5231
|
+
const phaseDirs = (await node_fs.promises.readdir(milestonesDir, { withFileTypes: true })).filter((e) => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name)).map((e) => e.name).sort().reverse();
|
|
5232
|
+
for (const archiveName of phaseDirs) {
|
|
5233
|
+
const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
|
|
5234
|
+
if (!versionMatch) continue;
|
|
5235
|
+
const version = versionMatch[1];
|
|
5236
|
+
const archivePath = node_path.default.join(milestonesDir, archiveName);
|
|
5237
|
+
const dirs = await listSubDirsAsync(archivePath, true);
|
|
5238
|
+
for (const dir of dirs) results.push({
|
|
5239
|
+
name: dir,
|
|
5240
|
+
milestone: version,
|
|
5241
|
+
basePath: node_path.default.join(".planning", "milestones", archiveName),
|
|
5242
|
+
fullPath: node_path.default.join(archivePath, dir)
|
|
5243
|
+
});
|
|
5244
|
+
}
|
|
5245
|
+
} catch (e) {
|
|
5246
|
+
debugLog("get-archived-phase-dirs-async-failed", e);
|
|
5247
|
+
}
|
|
5248
|
+
return results;
|
|
5249
|
+
}
|
|
5156
5250
|
|
|
5157
5251
|
//#endregion
|
|
5158
5252
|
//#region ../../node_modules/yaml/dist/nodes/identity.js
|
|
@@ -12108,11 +12202,11 @@ function stateReplaceField(content, fieldName, newValue) {
|
|
|
12108
12202
|
replaced = content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
|
|
12109
12203
|
return replaced !== content ? replaced : null;
|
|
12110
12204
|
}
|
|
12111
|
-
function readTextArgOrFile(cwd, value, filePath, label) {
|
|
12205
|
+
async function readTextArgOrFile(cwd, value, filePath, label) {
|
|
12112
12206
|
if (!filePath) return value;
|
|
12113
12207
|
const resolvedPath = node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath);
|
|
12114
12208
|
try {
|
|
12115
|
-
return node_fs.
|
|
12209
|
+
return (await node_fs.promises.readFile(resolvedPath, "utf-8")).trimEnd();
|
|
12116
12210
|
} catch {
|
|
12117
12211
|
throw new Error(`${label} file not found: ${filePath}`);
|
|
12118
12212
|
}
|
|
@@ -12137,8 +12231,8 @@ async function cmdStateLoad(cwd, raw) {
|
|
|
12137
12231
|
const config = loadConfig(cwd);
|
|
12138
12232
|
const [stateContent, configExists, roadmapExists] = await Promise.all([
|
|
12139
12233
|
safeReadFileAsync(statePath(cwd)),
|
|
12140
|
-
|
|
12141
|
-
|
|
12234
|
+
pathExistsAsync(configPath(cwd)),
|
|
12235
|
+
pathExistsAsync(roadmapPath(cwd))
|
|
12142
12236
|
]);
|
|
12143
12237
|
const stateRaw = stateContent ?? "";
|
|
12144
12238
|
const stateExists = stateRaw.length > 0;
|
|
@@ -12168,10 +12262,10 @@ async function cmdStateLoad(cwd, raw) {
|
|
|
12168
12262
|
}
|
|
12169
12263
|
return cmdOk(result);
|
|
12170
12264
|
}
|
|
12171
|
-
function cmdStateGet(cwd, section, raw) {
|
|
12265
|
+
async function cmdStateGet(cwd, section, raw) {
|
|
12172
12266
|
const statePath$2 = statePath(cwd);
|
|
12173
12267
|
try {
|
|
12174
|
-
const content = node_fs.
|
|
12268
|
+
const content = await node_fs.promises.readFile(statePath$2, "utf-8");
|
|
12175
12269
|
if (!section) return cmdOk({ content }, raw ? content : void 0);
|
|
12176
12270
|
const fieldValue = stateExtractField(content, section);
|
|
12177
12271
|
if (fieldValue !== null) return cmdOk({ [section]: fieldValue }, raw ? fieldValue : void 0);
|
|
@@ -12185,10 +12279,10 @@ function cmdStateGet(cwd, section, raw) {
|
|
|
12185
12279
|
return cmdErr("STATE.md not found");
|
|
12186
12280
|
}
|
|
12187
12281
|
}
|
|
12188
|
-
function cmdStatePatch(cwd, patches, raw) {
|
|
12282
|
+
async function cmdStatePatch(cwd, patches, raw) {
|
|
12189
12283
|
const statePath$3 = statePath(cwd);
|
|
12190
12284
|
try {
|
|
12191
|
-
let content = node_fs.
|
|
12285
|
+
let content = await node_fs.promises.readFile(statePath$3, "utf-8");
|
|
12192
12286
|
const results = {
|
|
12193
12287
|
updated: [],
|
|
12194
12288
|
failed: []
|
|
@@ -12200,20 +12294,20 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
12200
12294
|
results.updated.push(field);
|
|
12201
12295
|
} else results.failed.push(field);
|
|
12202
12296
|
}
|
|
12203
|
-
if (results.updated.length > 0) node_fs.
|
|
12297
|
+
if (results.updated.length > 0) await node_fs.promises.writeFile(statePath$3, content, "utf-8");
|
|
12204
12298
|
return cmdOk(results, raw ? results.updated.length > 0 ? "true" : "false" : void 0);
|
|
12205
12299
|
} catch (e) {
|
|
12206
12300
|
rethrowCliSignals(e);
|
|
12207
12301
|
return cmdErr("STATE.md not found");
|
|
12208
12302
|
}
|
|
12209
12303
|
}
|
|
12210
|
-
function cmdStateUpdate(cwd, field, value) {
|
|
12304
|
+
async function cmdStateUpdate(cwd, field, value) {
|
|
12211
12305
|
if (!field || value === void 0) return cmdErr("field and value required for state update");
|
|
12212
12306
|
const statePath$4 = statePath(cwd);
|
|
12213
12307
|
try {
|
|
12214
|
-
const result = stateReplaceField(node_fs.
|
|
12308
|
+
const result = stateReplaceField(await node_fs.promises.readFile(statePath$4, "utf-8"), field, value);
|
|
12215
12309
|
if (result) {
|
|
12216
|
-
node_fs.
|
|
12310
|
+
await node_fs.promises.writeFile(statePath$4, result, "utf-8");
|
|
12217
12311
|
return cmdOk({ updated: true });
|
|
12218
12312
|
} else return cmdOk({
|
|
12219
12313
|
updated: false,
|
|
@@ -12227,10 +12321,10 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
12227
12321
|
});
|
|
12228
12322
|
}
|
|
12229
12323
|
}
|
|
12230
|
-
function cmdStateAdvancePlan(cwd, raw) {
|
|
12324
|
+
async function cmdStateAdvancePlan(cwd, raw) {
|
|
12231
12325
|
const statePath$5 = statePath(cwd);
|
|
12232
|
-
if (!
|
|
12233
|
-
let content = node_fs.
|
|
12326
|
+
if (!await pathExistsAsync(statePath$5)) return cmdOk({ error: "STATE.md not found" });
|
|
12327
|
+
let content = await node_fs.promises.readFile(statePath$5, "utf-8");
|
|
12234
12328
|
const currentPlan = parseInt(stateExtractField(content, "Current Plan") ?? "", 10);
|
|
12235
12329
|
const totalPlans = parseInt(stateExtractField(content, "Total Plans in Phase") ?? "", 10);
|
|
12236
12330
|
const today = todayISO();
|
|
@@ -12238,7 +12332,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12238
12332
|
if (currentPlan >= totalPlans) {
|
|
12239
12333
|
content = stateReplaceField(content, "Status", "Phase complete — ready for verification") || content;
|
|
12240
12334
|
content = stateReplaceField(content, "Last Activity", today) || content;
|
|
12241
|
-
node_fs.
|
|
12335
|
+
await node_fs.promises.writeFile(statePath$5, content, "utf-8");
|
|
12242
12336
|
return cmdOk({
|
|
12243
12337
|
advanced: false,
|
|
12244
12338
|
reason: "last_plan",
|
|
@@ -12251,7 +12345,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12251
12345
|
content = stateReplaceField(content, "Current Plan", String(newPlan)) || content;
|
|
12252
12346
|
content = stateReplaceField(content, "Status", "Ready to execute") || content;
|
|
12253
12347
|
content = stateReplaceField(content, "Last Activity", today) || content;
|
|
12254
|
-
node_fs.
|
|
12348
|
+
await node_fs.promises.writeFile(statePath$5, content, "utf-8");
|
|
12255
12349
|
return cmdOk({
|
|
12256
12350
|
advanced: true,
|
|
12257
12351
|
previous_plan: currentPlan,
|
|
@@ -12260,10 +12354,10 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
12260
12354
|
}, raw ? "true" : void 0);
|
|
12261
12355
|
}
|
|
12262
12356
|
}
|
|
12263
|
-
function cmdStateRecordMetric(cwd, options, raw) {
|
|
12357
|
+
async function cmdStateRecordMetric(cwd, options, raw) {
|
|
12264
12358
|
const statePath$6 = statePath(cwd);
|
|
12265
|
-
if (!
|
|
12266
|
-
let content = node_fs.
|
|
12359
|
+
if (!await pathExistsAsync(statePath$6)) return cmdOk({ error: "STATE.md not found" });
|
|
12360
|
+
let content = await node_fs.promises.readFile(statePath$6, "utf-8");
|
|
12267
12361
|
const { phase, plan, duration, tasks, files } = options;
|
|
12268
12362
|
if (!phase || !plan || !duration) return cmdOk({ error: "phase, plan, and duration required" });
|
|
12269
12363
|
const metricsPattern = /(#{2,3}\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[\s:|\-]+\n)([\s\S]*?)(?=\n#{2,3}\s|\n$|$)/i;
|
|
@@ -12274,7 +12368,7 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
12274
12368
|
if (tableBody.trim() === "" || tableBody.includes("None yet")) tableBody = newRow;
|
|
12275
12369
|
else tableBody = tableBody + "\n" + newRow;
|
|
12276
12370
|
content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
|
|
12277
|
-
node_fs.
|
|
12371
|
+
await node_fs.promises.writeFile(statePath$6, content, "utf-8");
|
|
12278
12372
|
return cmdOk({
|
|
12279
12373
|
recorded: true,
|
|
12280
12374
|
phase,
|
|
@@ -12286,19 +12380,25 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
12286
12380
|
reason: "Performance Metrics section not found in STATE.md"
|
|
12287
12381
|
}, raw ? "false" : void 0);
|
|
12288
12382
|
}
|
|
12289
|
-
function cmdStateUpdateProgress(cwd, raw) {
|
|
12383
|
+
async function cmdStateUpdateProgress(cwd, raw) {
|
|
12290
12384
|
const statePath$7 = statePath(cwd);
|
|
12291
|
-
if (!
|
|
12292
|
-
let content = node_fs.
|
|
12385
|
+
if (!await pathExistsAsync(statePath$7)) return cmdOk({ error: "STATE.md not found" });
|
|
12386
|
+
let content = await node_fs.promises.readFile(statePath$7, "utf-8");
|
|
12293
12387
|
const phasesDir = phasesPath(cwd);
|
|
12294
12388
|
let totalPlans = 0;
|
|
12295
12389
|
let totalSummaries = 0;
|
|
12296
|
-
if (
|
|
12297
|
-
const phaseDirs = node_fs.
|
|
12298
|
-
|
|
12299
|
-
const files = node_fs.
|
|
12300
|
-
|
|
12301
|
-
|
|
12390
|
+
if (await pathExistsAsync(phasesDir)) {
|
|
12391
|
+
const phaseDirs = (await node_fs.promises.readdir(phasesDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
12392
|
+
const counts = await Promise.all(phaseDirs.map(async (dir) => {
|
|
12393
|
+
const files = await node_fs.promises.readdir(node_path.default.join(phasesDir, dir));
|
|
12394
|
+
return {
|
|
12395
|
+
plans: files.filter((f) => isPlanFile(f)).length,
|
|
12396
|
+
summaries: files.filter((f) => isSummaryFile(f)).length
|
|
12397
|
+
};
|
|
12398
|
+
}));
|
|
12399
|
+
for (const c of counts) {
|
|
12400
|
+
totalPlans += c.plans;
|
|
12401
|
+
totalSummaries += c.summaries;
|
|
12302
12402
|
}
|
|
12303
12403
|
}
|
|
12304
12404
|
const percent = totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0;
|
|
@@ -12307,7 +12407,7 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
12307
12407
|
const progressStr = `[${"█".repeat(filled) + "░".repeat(barWidth - filled)}] ${percent}%`;
|
|
12308
12408
|
const result = stateReplaceField(content, "Progress", progressStr);
|
|
12309
12409
|
if (result) {
|
|
12310
|
-
node_fs.
|
|
12410
|
+
await node_fs.promises.writeFile(statePath$7, result, "utf-8");
|
|
12311
12411
|
return cmdOk({
|
|
12312
12412
|
updated: true,
|
|
12313
12413
|
percent,
|
|
@@ -12320,15 +12420,15 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
12320
12420
|
reason: "Progress field not found in STATE.md"
|
|
12321
12421
|
}, raw ? "false" : void 0);
|
|
12322
12422
|
}
|
|
12323
|
-
function cmdStateAddDecision(cwd, options, raw) {
|
|
12423
|
+
async function cmdStateAddDecision(cwd, options, raw) {
|
|
12324
12424
|
const statePath$8 = statePath(cwd);
|
|
12325
|
-
if (!
|
|
12425
|
+
if (!await pathExistsAsync(statePath$8)) return cmdOk({ error: "STATE.md not found" });
|
|
12326
12426
|
const { phase, summary, summary_file, rationale, rationale_file } = options;
|
|
12327
12427
|
let summaryText;
|
|
12328
12428
|
let rationaleText = "";
|
|
12329
12429
|
try {
|
|
12330
|
-
summaryText = readTextArgOrFile(cwd, summary, summary_file, "summary");
|
|
12331
|
-
rationaleText = readTextArgOrFile(cwd, rationale || "", rationale_file, "rationale") || "";
|
|
12430
|
+
summaryText = await readTextArgOrFile(cwd, summary, summary_file, "summary");
|
|
12431
|
+
rationaleText = await readTextArgOrFile(cwd, rationale || "", rationale_file, "rationale") || "";
|
|
12332
12432
|
} catch (thrown) {
|
|
12333
12433
|
return cmdOk({
|
|
12334
12434
|
added: false,
|
|
@@ -12336,11 +12436,11 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
12336
12436
|
}, raw ? "false" : void 0);
|
|
12337
12437
|
}
|
|
12338
12438
|
if (!summaryText) return cmdOk({ error: "summary required" });
|
|
12339
|
-
const content = node_fs.
|
|
12439
|
+
const content = await node_fs.promises.readFile(statePath$8, "utf-8");
|
|
12340
12440
|
const entry = `- [Phase ${phase || "?"}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ""}`;
|
|
12341
12441
|
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]);
|
|
12342
12442
|
if (updated) {
|
|
12343
|
-
node_fs.
|
|
12443
|
+
await node_fs.promises.writeFile(statePath$8, updated, "utf-8");
|
|
12344
12444
|
return cmdOk({
|
|
12345
12445
|
added: true,
|
|
12346
12446
|
decision: entry
|
|
@@ -12350,13 +12450,13 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
12350
12450
|
reason: "Decisions section not found in STATE.md"
|
|
12351
12451
|
}, raw ? "false" : void 0);
|
|
12352
12452
|
}
|
|
12353
|
-
function cmdStateAddBlocker(cwd, text, raw) {
|
|
12453
|
+
async function cmdStateAddBlocker(cwd, text, raw) {
|
|
12354
12454
|
const statePath$9 = statePath(cwd);
|
|
12355
|
-
if (!
|
|
12455
|
+
if (!await pathExistsAsync(statePath$9)) return cmdOk({ error: "STATE.md not found" });
|
|
12356
12456
|
const blockerOptions = typeof text === "object" && text !== null ? text : { text };
|
|
12357
12457
|
let blockerText;
|
|
12358
12458
|
try {
|
|
12359
|
-
blockerText = readTextArgOrFile(cwd, blockerOptions.text, blockerOptions.text_file, "blocker");
|
|
12459
|
+
blockerText = await readTextArgOrFile(cwd, blockerOptions.text, blockerOptions.text_file, "blocker");
|
|
12360
12460
|
} catch (thrown) {
|
|
12361
12461
|
return cmdOk({
|
|
12362
12462
|
added: false,
|
|
@@ -12364,9 +12464,9 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
12364
12464
|
}, raw ? "false" : void 0);
|
|
12365
12465
|
}
|
|
12366
12466
|
if (!blockerText) return cmdOk({ error: "text required" });
|
|
12367
|
-
const updated = appendToStateSection(node_fs.
|
|
12467
|
+
const updated = appendToStateSection(await node_fs.promises.readFile(statePath$9, "utf-8"), /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, `- ${blockerText}`, [/None\.?\s*\n?/gi, /None yet\.?\s*\n?/gi]);
|
|
12368
12468
|
if (updated) {
|
|
12369
|
-
node_fs.
|
|
12469
|
+
await node_fs.promises.writeFile(statePath$9, updated, "utf-8");
|
|
12370
12470
|
return cmdOk({
|
|
12371
12471
|
added: true,
|
|
12372
12472
|
blocker: blockerText
|
|
@@ -12376,11 +12476,11 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
12376
12476
|
reason: "Blockers section not found in STATE.md"
|
|
12377
12477
|
}, raw ? "false" : void 0);
|
|
12378
12478
|
}
|
|
12379
|
-
function cmdStateResolveBlocker(cwd, text, raw) {
|
|
12479
|
+
async function cmdStateResolveBlocker(cwd, text, raw) {
|
|
12380
12480
|
const statePath$10 = statePath(cwd);
|
|
12381
|
-
if (!
|
|
12481
|
+
if (!await pathExistsAsync(statePath$10)) return cmdOk({ error: "STATE.md not found" });
|
|
12382
12482
|
if (!text) return cmdOk({ error: "text required" });
|
|
12383
|
-
let content = node_fs.
|
|
12483
|
+
let content = await node_fs.promises.readFile(statePath$10, "utf-8");
|
|
12384
12484
|
const sectionPattern = /(#{2,3}\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n\s*\n?)([\s\S]*?)(?=\n#{2,3}\s|$)/i;
|
|
12385
12485
|
const match = content.match(sectionPattern);
|
|
12386
12486
|
if (match) {
|
|
@@ -12390,7 +12490,7 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12390
12490
|
}).join("\n");
|
|
12391
12491
|
if (!newBody.trim() || !/^\s*[-*]\s+/m.test(newBody)) newBody = "None\n";
|
|
12392
12492
|
content = content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
|
|
12393
|
-
node_fs.
|
|
12493
|
+
await node_fs.promises.writeFile(statePath$10, content, "utf-8");
|
|
12394
12494
|
return cmdOk({
|
|
12395
12495
|
resolved: true,
|
|
12396
12496
|
blocker: text
|
|
@@ -12400,10 +12500,10 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
12400
12500
|
reason: "Blockers section not found in STATE.md"
|
|
12401
12501
|
}, raw ? "false" : void 0);
|
|
12402
12502
|
}
|
|
12403
|
-
function cmdStateRecordSession(cwd, options, raw) {
|
|
12503
|
+
async function cmdStateRecordSession(cwd, options, raw) {
|
|
12404
12504
|
const statePath$11 = statePath(cwd);
|
|
12405
|
-
if (!
|
|
12406
|
-
let content = node_fs.
|
|
12505
|
+
if (!await pathExistsAsync(statePath$11)) return cmdOk({ error: "STATE.md not found" });
|
|
12506
|
+
let content = await node_fs.promises.readFile(statePath$11, "utf-8");
|
|
12407
12507
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12408
12508
|
const updated = [];
|
|
12409
12509
|
let result = stateReplaceField(content, "Last session", now);
|
|
@@ -12432,7 +12532,7 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
12432
12532
|
updated.push("Resume File");
|
|
12433
12533
|
}
|
|
12434
12534
|
if (updated.length > 0) {
|
|
12435
|
-
node_fs.
|
|
12535
|
+
await node_fs.promises.writeFile(statePath$11, content, "utf-8");
|
|
12436
12536
|
return cmdOk({
|
|
12437
12537
|
recorded: true,
|
|
12438
12538
|
updated
|
|
@@ -12442,10 +12542,10 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
12442
12542
|
reason: "No session fields found in STATE.md"
|
|
12443
12543
|
}, raw ? "false" : void 0);
|
|
12444
12544
|
}
|
|
12445
|
-
function cmdStateSnapshot(cwd, raw) {
|
|
12545
|
+
async function cmdStateSnapshot(cwd, raw) {
|
|
12446
12546
|
const statePath$12 = statePath(cwd);
|
|
12447
|
-
if (!
|
|
12448
|
-
const content = node_fs.
|
|
12547
|
+
if (!await pathExistsAsync(statePath$12)) return cmdOk({ error: "STATE.md not found" });
|
|
12548
|
+
const content = await node_fs.promises.readFile(statePath$12, "utf-8");
|
|
12449
12549
|
const extractField = (fieldName) => stateExtractField(content, fieldName);
|
|
12450
12550
|
const currentPhase = extractField("Current Phase");
|
|
12451
12551
|
const currentPhaseName = extractField("Current Phase Name");
|
|
@@ -12516,14 +12616,13 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
12516
12616
|
*
|
|
12517
12617
|
* Ported from maxsim/bin/lib/roadmap.cjs
|
|
12518
12618
|
*/
|
|
12519
|
-
function cmdRoadmapGetPhase(cwd, phaseNum) {
|
|
12520
|
-
const
|
|
12521
|
-
if (!
|
|
12619
|
+
async function cmdRoadmapGetPhase(cwd, phaseNum) {
|
|
12620
|
+
const content = await safeReadFileAsync(roadmapPath(cwd));
|
|
12621
|
+
if (!content) return cmdOk({
|
|
12522
12622
|
found: false,
|
|
12523
12623
|
error: "ROADMAP.md not found"
|
|
12524
12624
|
}, "");
|
|
12525
12625
|
try {
|
|
12526
|
-
const content = node_fs.default.readFileSync(rmPath, "utf-8");
|
|
12527
12626
|
const escapedPhase = phaseNum.replace(/\./g, "\\.");
|
|
12528
12627
|
const phasePattern = getPhasePattern(escapedPhase, "i");
|
|
12529
12628
|
const headerMatch = content.match(phasePattern);
|
|
@@ -12607,7 +12706,7 @@ async function cmdRoadmapAnalyze(cwd) {
|
|
|
12607
12706
|
try {
|
|
12608
12707
|
const dirMatch = allDirs.find((d) => d.startsWith(p.normalized + "-") || d === p.normalized);
|
|
12609
12708
|
if (dirMatch) {
|
|
12610
|
-
const phaseFiles = await node_fs.
|
|
12709
|
+
const phaseFiles = await node_fs.promises.readdir(node_path.default.join(phasesDir, dirMatch));
|
|
12611
12710
|
planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
|
|
12612
12711
|
summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
|
|
12613
12712
|
hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
@@ -12668,10 +12767,10 @@ async function cmdRoadmapAnalyze(cwd) {
|
|
|
12668
12767
|
missing_phase_details: missingDetails.length > 0 ? missingDetails : null
|
|
12669
12768
|
});
|
|
12670
12769
|
}
|
|
12671
|
-
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum) {
|
|
12770
|
+
async function cmdRoadmapUpdatePlanProgress(cwd, phaseNum) {
|
|
12672
12771
|
if (!phaseNum) return cmdErr("phase number required for roadmap update-plan-progress");
|
|
12673
12772
|
const rmPath = roadmapPath(cwd);
|
|
12674
|
-
const phaseInfo =
|
|
12773
|
+
const phaseInfo = await findPhaseInternalAsync(cwd, phaseNum);
|
|
12675
12774
|
if (!phaseInfo) return cmdErr(`Phase ${phaseNum} not found`);
|
|
12676
12775
|
const planCount = phaseInfo.plans.length;
|
|
12677
12776
|
const summaryCount = phaseInfo.summaries.length;
|
|
@@ -12684,20 +12783,21 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum) {
|
|
|
12684
12783
|
const isComplete = summaryCount >= planCount;
|
|
12685
12784
|
const status = isComplete ? "Complete" : summaryCount > 0 ? "In Progress" : "Planned";
|
|
12686
12785
|
const today = todayISO();
|
|
12687
|
-
|
|
12786
|
+
const rawContent = await safeReadFileAsync(rmPath);
|
|
12787
|
+
if (!rawContent) return cmdOk({
|
|
12688
12788
|
updated: false,
|
|
12689
12789
|
reason: "ROADMAP.md not found",
|
|
12690
12790
|
plan_count: planCount,
|
|
12691
12791
|
summary_count: summaryCount
|
|
12692
12792
|
}, "no roadmap");
|
|
12693
|
-
let roadmapContent =
|
|
12793
|
+
let roadmapContent = rawContent;
|
|
12694
12794
|
const phaseEscaped = phaseNum.replace(".", "\\.");
|
|
12695
12795
|
const dateField = isComplete ? ` ${today} ` : " ";
|
|
12696
12796
|
roadmapContent = roadmapContent.replace(new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, "i"), `$1 ${summaryCount}/${planCount} $2 ${status.padEnd(11)}$3${dateField}$4`);
|
|
12697
12797
|
const planCountText = isComplete ? `${summaryCount}/${planCount} plans complete` : `${summaryCount}/${planCount} plans executed`;
|
|
12698
12798
|
roadmapContent = roadmapContent.replace(new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i"), `$1${planCountText}`);
|
|
12699
12799
|
if (isComplete) roadmapContent = roadmapContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, "i"), `$1x$2 (completed ${today})`);
|
|
12700
|
-
node_fs.
|
|
12800
|
+
await node_fs.promises.writeFile(rmPath, roadmapContent, "utf-8");
|
|
12701
12801
|
return cmdOk({
|
|
12702
12802
|
updated: true,
|
|
12703
12803
|
phase: phaseNum,
|
|
@@ -14318,15 +14418,18 @@ function cmdValidateHealth(cwd, options) {
|
|
|
14318
14418
|
*
|
|
14319
14419
|
* Ported from maxsim/bin/lib/phase.cjs
|
|
14320
14420
|
*/
|
|
14321
|
-
function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
14421
|
+
async function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
14322
14422
|
const today = todayISO();
|
|
14323
|
-
node_fs.
|
|
14324
|
-
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`);
|
|
14423
|
+
await Promise.all([node_fs.promises.writeFile(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`), node_fs.promises.writeFile(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`)]);
|
|
14325
14424
|
}
|
|
14326
|
-
function phaseAddCore(cwd, description, options) {
|
|
14425
|
+
async function phaseAddCore(cwd, description, options) {
|
|
14327
14426
|
const rmPath = roadmapPath(cwd);
|
|
14328
|
-
|
|
14329
|
-
|
|
14427
|
+
let content;
|
|
14428
|
+
try {
|
|
14429
|
+
content = await node_fs.promises.readFile(rmPath, "utf-8");
|
|
14430
|
+
} catch {
|
|
14431
|
+
throw new Error("ROADMAP.md not found");
|
|
14432
|
+
}
|
|
14330
14433
|
const slug = generateSlugInternal(description);
|
|
14331
14434
|
const phasePattern = getPhasePattern();
|
|
14332
14435
|
let maxPhase = 0;
|
|
@@ -14339,15 +14442,15 @@ function phaseAddCore(cwd, description, options) {
|
|
|
14339
14442
|
const paddedNum = String(newPhaseNum).padStart(2, "0");
|
|
14340
14443
|
const dirName = `${paddedNum}-${slug}`;
|
|
14341
14444
|
const dirPath = planningPath(cwd, "phases", dirName);
|
|
14342
|
-
node_fs.
|
|
14343
|
-
node_fs.
|
|
14344
|
-
if (options?.includeStubs) scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
14445
|
+
await node_fs.promises.mkdir(dirPath, { recursive: true });
|
|
14446
|
+
await node_fs.promises.writeFile(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14447
|
+
if (options?.includeStubs) await scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
14345
14448
|
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`;
|
|
14346
14449
|
let updatedContent;
|
|
14347
14450
|
const lastSeparator = content.lastIndexOf("\n---");
|
|
14348
14451
|
if (lastSeparator > 0) updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
|
|
14349
14452
|
else updatedContent = content + phaseEntry;
|
|
14350
|
-
node_fs.
|
|
14453
|
+
await node_fs.promises.writeFile(rmPath, updatedContent, "utf-8");
|
|
14351
14454
|
return {
|
|
14352
14455
|
phase_number: newPhaseNum,
|
|
14353
14456
|
padded: paddedNum,
|
|
@@ -14356,10 +14459,14 @@ function phaseAddCore(cwd, description, options) {
|
|
|
14356
14459
|
description
|
|
14357
14460
|
};
|
|
14358
14461
|
}
|
|
14359
|
-
function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
14462
|
+
async function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
14360
14463
|
const rmPath = roadmapPath(cwd);
|
|
14361
|
-
|
|
14362
|
-
|
|
14464
|
+
let content;
|
|
14465
|
+
try {
|
|
14466
|
+
content = await node_fs.promises.readFile(rmPath, "utf-8");
|
|
14467
|
+
} catch {
|
|
14468
|
+
throw new Error("ROADMAP.md not found");
|
|
14469
|
+
}
|
|
14363
14470
|
const slug = generateSlugInternal(description);
|
|
14364
14471
|
const afterPhaseEscaped = "0*" + normalizePhaseName(afterPhase).replace(/^0+/, "").replace(/\./g, "\\.");
|
|
14365
14472
|
if (!getPhasePattern(afterPhaseEscaped, "i").test(content)) throw new Error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
@@ -14367,7 +14474,7 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
14367
14474
|
const normalizedBase = normalizePhaseName(afterPhase);
|
|
14368
14475
|
const existingDecimals = [];
|
|
14369
14476
|
try {
|
|
14370
|
-
const dirs =
|
|
14477
|
+
const dirs = await listSubDirsAsync(phasesDirPath);
|
|
14371
14478
|
const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
|
|
14372
14479
|
for (const dir of dirs) {
|
|
14373
14480
|
const dm = dir.match(decimalPattern);
|
|
@@ -14379,9 +14486,9 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
14379
14486
|
const decimalPhase = `${normalizedBase}.${existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1}`;
|
|
14380
14487
|
const dirName = `${decimalPhase}-${slug}`;
|
|
14381
14488
|
const dirPath = planningPath(cwd, "phases", dirName);
|
|
14382
|
-
node_fs.
|
|
14383
|
-
node_fs.
|
|
14384
|
-
if (options?.includeStubs) scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
14489
|
+
await node_fs.promises.mkdir(dirPath, { recursive: true });
|
|
14490
|
+
await node_fs.promises.writeFile(node_path.default.join(dirPath, ".gitkeep"), "");
|
|
14491
|
+
if (options?.includeStubs) await scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
14385
14492
|
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`;
|
|
14386
14493
|
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, "i");
|
|
14387
14494
|
const headerMatch = content.match(headerPattern);
|
|
@@ -14392,7 +14499,7 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
14392
14499
|
if (nextPhaseMatch) insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
14393
14500
|
else insertIdx = content.length;
|
|
14394
14501
|
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
14395
|
-
node_fs.
|
|
14502
|
+
await node_fs.promises.writeFile(rmPath, updatedContent, "utf-8");
|
|
14396
14503
|
return {
|
|
14397
14504
|
phase_number: decimalPhase,
|
|
14398
14505
|
after_phase: afterPhase,
|
|
@@ -14401,18 +14508,19 @@ function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
|
14401
14508
|
description
|
|
14402
14509
|
};
|
|
14403
14510
|
}
|
|
14404
|
-
function phaseCompleteCore(cwd, phaseNum) {
|
|
14511
|
+
async function phaseCompleteCore(cwd, phaseNum) {
|
|
14405
14512
|
const rmPath = roadmapPath(cwd);
|
|
14406
14513
|
const stPath = statePath(cwd);
|
|
14407
14514
|
const phasesDirPath = phasesPath(cwd);
|
|
14408
14515
|
const today = todayISO();
|
|
14409
|
-
const phaseInfo =
|
|
14516
|
+
const phaseInfo = await findPhaseInternalAsync(cwd, phaseNum);
|
|
14410
14517
|
if (!phaseInfo) throw new Error(`Phase ${phaseNum} not found`);
|
|
14411
14518
|
const planCount = phaseInfo.plans.length;
|
|
14412
14519
|
const summaryCount = phaseInfo.summaries.length;
|
|
14413
14520
|
let requirementsUpdated = false;
|
|
14414
|
-
|
|
14415
|
-
|
|
14521
|
+
const rmExists = await pathExistsAsync(rmPath);
|
|
14522
|
+
if (rmExists) {
|
|
14523
|
+
let roadmapContent = await node_fs.promises.readFile(rmPath, "utf-8");
|
|
14416
14524
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${escapePhaseNum(phaseNum)}[:\\s][^\\n]*)`, "i");
|
|
14417
14525
|
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
14418
14526
|
const phaseEscaped = escapePhaseNum(phaseNum);
|
|
@@ -14421,20 +14529,20 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
14421
14529
|
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, "i");
|
|
14422
14530
|
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
14423
14531
|
debugLog("phase-complete-write", `writing ROADMAP.md for phase ${phaseNum}`);
|
|
14424
|
-
node_fs.
|
|
14532
|
+
await node_fs.promises.writeFile(rmPath, roadmapContent, "utf-8");
|
|
14425
14533
|
debugLog("phase-complete-write", `ROADMAP.md updated for phase ${phaseNum}`);
|
|
14426
14534
|
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
14427
|
-
if (
|
|
14535
|
+
if (await pathExistsAsync(reqPath)) {
|
|
14428
14536
|
const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${escapePhaseNum(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, "i"));
|
|
14429
14537
|
if (reqMatch) {
|
|
14430
14538
|
const reqIds = reqMatch[1].replace(/[\[\]]/g, "").split(/[,\s]+/).map((r) => r.trim()).filter(Boolean);
|
|
14431
|
-
let reqContent = node_fs.
|
|
14539
|
+
let reqContent = await node_fs.promises.readFile(reqPath, "utf-8");
|
|
14432
14540
|
for (const reqId of reqIds) {
|
|
14433
14541
|
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, "gi"), "$1x$2");
|
|
14434
14542
|
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, "gi"), "$1 Complete $2");
|
|
14435
14543
|
}
|
|
14436
14544
|
debugLog("phase-complete-write", `writing REQUIREMENTS.md for phase ${phaseNum}`);
|
|
14437
|
-
node_fs.
|
|
14545
|
+
await node_fs.promises.writeFile(reqPath, reqContent, "utf-8");
|
|
14438
14546
|
debugLog("phase-complete-write", `REQUIREMENTS.md updated for phase ${phaseNum}`);
|
|
14439
14547
|
requirementsUpdated = true;
|
|
14440
14548
|
}
|
|
@@ -14444,7 +14552,7 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
14444
14552
|
let nextPhaseName = null;
|
|
14445
14553
|
let isLastPhase = true;
|
|
14446
14554
|
try {
|
|
14447
|
-
const dirs =
|
|
14555
|
+
const dirs = await listSubDirsAsync(phasesDirPath, true);
|
|
14448
14556
|
for (const dir of dirs) {
|
|
14449
14557
|
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
14450
14558
|
if (dm) {
|
|
@@ -14459,8 +14567,9 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
14459
14567
|
} catch (e) {
|
|
14460
14568
|
debugLog("phase-complete-next-phase-scan-failed", e);
|
|
14461
14569
|
}
|
|
14462
|
-
|
|
14463
|
-
|
|
14570
|
+
const stExists = await pathExistsAsync(stPath);
|
|
14571
|
+
if (stExists) {
|
|
14572
|
+
let stateContent = await node_fs.promises.readFile(stPath, "utf-8");
|
|
14464
14573
|
stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
|
|
14465
14574
|
if (nextPhaseName) stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, " ")}`);
|
|
14466
14575
|
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? "Milestone complete" : "Ready to plan"}`);
|
|
@@ -14468,7 +14577,7 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
14468
14577
|
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
14469
14578
|
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ""}`);
|
|
14470
14579
|
debugLog("phase-complete-write", `writing STATE.md for phase ${phaseNum}`);
|
|
14471
|
-
node_fs.
|
|
14580
|
+
await node_fs.promises.writeFile(stPath, stateContent, "utf-8");
|
|
14472
14581
|
debugLog("phase-complete-write", `STATE.md updated for phase ${phaseNum}`);
|
|
14473
14582
|
}
|
|
14474
14583
|
return {
|
|
@@ -14479,15 +14588,15 @@ function phaseCompleteCore(cwd, phaseNum) {
|
|
|
14479
14588
|
next_phase_name: nextPhaseName,
|
|
14480
14589
|
is_last_phase: isLastPhase,
|
|
14481
14590
|
date: today,
|
|
14482
|
-
roadmap_updated:
|
|
14483
|
-
state_updated:
|
|
14591
|
+
roadmap_updated: rmExists,
|
|
14592
|
+
state_updated: stExists,
|
|
14484
14593
|
requirements_updated: requirementsUpdated
|
|
14485
14594
|
};
|
|
14486
14595
|
}
|
|
14487
14596
|
async function cmdPhasesList(cwd, options) {
|
|
14488
14597
|
const phasesDirPath = phasesPath(cwd);
|
|
14489
14598
|
const { type, phase, includeArchived, offset, limit } = options;
|
|
14490
|
-
if (!
|
|
14599
|
+
if (!await pathExistsAsync(phasesDirPath)) if (type) return cmdOk({
|
|
14491
14600
|
files: [],
|
|
14492
14601
|
count: 0,
|
|
14493
14602
|
total: 0
|
|
@@ -14500,7 +14609,7 @@ async function cmdPhasesList(cwd, options) {
|
|
|
14500
14609
|
try {
|
|
14501
14610
|
let dirs = await listSubDirsAsync(phasesDirPath);
|
|
14502
14611
|
if (includeArchived) {
|
|
14503
|
-
const archived =
|
|
14612
|
+
const archived = await getArchivedPhaseDirsAsync(cwd);
|
|
14504
14613
|
for (const a of archived) dirs.push(`${a.name} [${a.milestone}]`);
|
|
14505
14614
|
}
|
|
14506
14615
|
dirs.sort((a, b) => comparePhaseNum(a, b));
|
|
@@ -14519,7 +14628,7 @@ async function cmdPhasesList(cwd, options) {
|
|
|
14519
14628
|
if (type) {
|
|
14520
14629
|
const files = (await Promise.all(dirs.map(async (dir) => {
|
|
14521
14630
|
const dirPath = node_path.default.join(phasesDirPath, dir);
|
|
14522
|
-
const dirFiles = await node_fs.
|
|
14631
|
+
const dirFiles = await node_fs.promises.readdir(dirPath);
|
|
14523
14632
|
let filtered;
|
|
14524
14633
|
if (type === "plans") filtered = dirFiles.filter(isPlanFile);
|
|
14525
14634
|
else if (type === "summaries") filtered = dirFiles.filter(isSummaryFile);
|
|
@@ -14545,17 +14654,17 @@ async function cmdPhasesList(cwd, options) {
|
|
|
14545
14654
|
return cmdErr("Failed to list phases: " + e.message);
|
|
14546
14655
|
}
|
|
14547
14656
|
}
|
|
14548
|
-
function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
14657
|
+
async function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
14549
14658
|
const phasesDirPath = phasesPath(cwd);
|
|
14550
14659
|
const normalized = normalizePhaseName(basePhase);
|
|
14551
|
-
if (!
|
|
14660
|
+
if (!await pathExistsAsync(phasesDirPath)) return cmdOk({
|
|
14552
14661
|
found: false,
|
|
14553
14662
|
base_phase: normalized,
|
|
14554
14663
|
next: `${normalized}.1`,
|
|
14555
14664
|
existing: []
|
|
14556
14665
|
}, `${normalized}.1`);
|
|
14557
14666
|
try {
|
|
14558
|
-
const dirs =
|
|
14667
|
+
const dirs = await listSubDirsAsync(phasesDirPath);
|
|
14559
14668
|
const baseExists = dirs.some((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
14560
14669
|
const decimalPattern = new RegExp(`^${normalized}\\.(\\d+)`);
|
|
14561
14670
|
const existingDecimals = [];
|
|
@@ -14582,7 +14691,7 @@ function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
|
14582
14691
|
return cmdErr("Failed to calculate next decimal phase: " + e.message);
|
|
14583
14692
|
}
|
|
14584
14693
|
}
|
|
14585
|
-
function cmdFindPhase(cwd, phase) {
|
|
14694
|
+
async function cmdFindPhase(cwd, phase) {
|
|
14586
14695
|
if (!phase) return cmdErr("phase identifier required");
|
|
14587
14696
|
const phasesDirPath = phasesPath(cwd);
|
|
14588
14697
|
const normalized = normalizePhaseName(phase);
|
|
@@ -14595,13 +14704,13 @@ function cmdFindPhase(cwd, phase) {
|
|
|
14595
14704
|
summaries: []
|
|
14596
14705
|
};
|
|
14597
14706
|
try {
|
|
14598
|
-
const match =
|
|
14707
|
+
const match = (await listSubDirsAsync(phasesDirPath, true)).find((d) => d.startsWith(normalized));
|
|
14599
14708
|
if (!match) return cmdOk(notFound, "");
|
|
14600
14709
|
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
14601
14710
|
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
14602
14711
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
14603
14712
|
const phaseDir = node_path.default.join(phasesDirPath, match);
|
|
14604
|
-
const phaseFiles = node_fs.
|
|
14713
|
+
const phaseFiles = await node_fs.promises.readdir(phaseDir);
|
|
14605
14714
|
const plans = phaseFiles.filter(isPlanFile).sort();
|
|
14606
14715
|
const summaries = phaseFiles.filter(isSummaryFile).sort();
|
|
14607
14716
|
const result = {
|
|
@@ -14617,13 +14726,13 @@ function cmdFindPhase(cwd, phase) {
|
|
|
14617
14726
|
return cmdOk(notFound, "");
|
|
14618
14727
|
}
|
|
14619
14728
|
}
|
|
14620
|
-
function cmdPhasePlanIndex(cwd, phase) {
|
|
14729
|
+
async function cmdPhasePlanIndex(cwd, phase) {
|
|
14621
14730
|
if (!phase) return cmdErr("phase required for phase-plan-index");
|
|
14622
14731
|
const phasesDirPath = phasesPath(cwd);
|
|
14623
14732
|
const normalized = normalizePhaseName(phase);
|
|
14624
14733
|
let phaseDir = null;
|
|
14625
14734
|
try {
|
|
14626
|
-
const match =
|
|
14735
|
+
const match = (await listSubDirsAsync(phasesDirPath, true)).find((d) => d.startsWith(normalized));
|
|
14627
14736
|
if (match) phaseDir = node_path.default.join(phasesDirPath, match);
|
|
14628
14737
|
} catch (e) {
|
|
14629
14738
|
debugLog("phase-plan-index-failed", e);
|
|
@@ -14636,7 +14745,7 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
14636
14745
|
incomplete: [],
|
|
14637
14746
|
has_checkpoints: false
|
|
14638
14747
|
});
|
|
14639
|
-
const phaseFiles = node_fs.
|
|
14748
|
+
const phaseFiles = await node_fs.promises.readdir(phaseDir);
|
|
14640
14749
|
const planFiles = phaseFiles.filter(isPlanFile).sort();
|
|
14641
14750
|
const summaryFiles = phaseFiles.filter(isSummaryFile);
|
|
14642
14751
|
const completedPlanIds = new Set(summaryFiles.map(summaryId));
|
|
@@ -14644,10 +14753,11 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
14644
14753
|
const waves = {};
|
|
14645
14754
|
const incomplete = [];
|
|
14646
14755
|
let hasCheckpoints = false;
|
|
14647
|
-
|
|
14756
|
+
const planContents = await Promise.all(planFiles.map((planFile) => node_fs.promises.readFile(node_path.default.join(phaseDir, planFile), "utf-8")));
|
|
14757
|
+
for (let i = 0; i < planFiles.length; i++) {
|
|
14758
|
+
const planFile = planFiles[i];
|
|
14648
14759
|
const id = planId(planFile);
|
|
14649
|
-
const
|
|
14650
|
-
const content = node_fs.default.readFileSync(planPath, "utf-8");
|
|
14760
|
+
const content = planContents[i];
|
|
14651
14761
|
const fm = extractFrontmatter(content);
|
|
14652
14762
|
const taskCount = (content.match(/##\s*Task\s*\d+/gi) || []).length;
|
|
14653
14763
|
const wave = parseInt(fm.wave, 10) || 1;
|
|
@@ -14680,10 +14790,10 @@ function cmdPhasePlanIndex(cwd, phase) {
|
|
|
14680
14790
|
has_checkpoints: hasCheckpoints
|
|
14681
14791
|
});
|
|
14682
14792
|
}
|
|
14683
|
-
function cmdPhaseAdd(cwd, description) {
|
|
14793
|
+
async function cmdPhaseAdd(cwd, description) {
|
|
14684
14794
|
if (!description) return cmdErr("description required for phase add");
|
|
14685
14795
|
try {
|
|
14686
|
-
const result = phaseAddCore(cwd, description, { includeStubs: false });
|
|
14796
|
+
const result = await phaseAddCore(cwd, description, { includeStubs: false });
|
|
14687
14797
|
return cmdOk({
|
|
14688
14798
|
phase_number: result.phase_number,
|
|
14689
14799
|
padded: result.padded,
|
|
@@ -14695,10 +14805,10 @@ function cmdPhaseAdd(cwd, description) {
|
|
|
14695
14805
|
return cmdErr(e.message);
|
|
14696
14806
|
}
|
|
14697
14807
|
}
|
|
14698
|
-
function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
14808
|
+
async function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
14699
14809
|
if (!afterPhase || !description) return cmdErr("after-phase and description required for phase insert");
|
|
14700
14810
|
try {
|
|
14701
|
-
const result = phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
14811
|
+
const result = await phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
14702
14812
|
return cmdOk({
|
|
14703
14813
|
phase_number: result.phase_number,
|
|
14704
14814
|
after_phase: result.after_phase,
|
|
@@ -14710,26 +14820,26 @@ function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
|
14710
14820
|
return cmdErr(e.message);
|
|
14711
14821
|
}
|
|
14712
14822
|
}
|
|
14713
|
-
function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
14823
|
+
async function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
14714
14824
|
if (!targetPhase) return cmdErr("phase number required for phase remove");
|
|
14715
14825
|
const rmPath = roadmapPath(cwd);
|
|
14716
14826
|
const phasesDirPath = phasesPath(cwd);
|
|
14717
14827
|
const force = options.force || false;
|
|
14718
|
-
if (!
|
|
14828
|
+
if (!await pathExistsAsync(rmPath)) return cmdErr("ROADMAP.md not found");
|
|
14719
14829
|
const normalized = normalizePhaseName(targetPhase);
|
|
14720
14830
|
const isDecimal = targetPhase.includes(".");
|
|
14721
14831
|
let targetDir = null;
|
|
14722
14832
|
try {
|
|
14723
|
-
targetDir =
|
|
14833
|
+
targetDir = (await listSubDirsAsync(phasesDirPath, true)).find((d) => d.startsWith(normalized + "-") || d === normalized) || null;
|
|
14724
14834
|
} catch (e) {
|
|
14725
14835
|
debugLog("phase-remove-find-target-failed", e);
|
|
14726
14836
|
}
|
|
14727
14837
|
if (targetDir && !force) {
|
|
14728
14838
|
const targetPath = node_path.default.join(phasesDirPath, targetDir);
|
|
14729
|
-
const summaries = node_fs.
|
|
14839
|
+
const summaries = (await node_fs.promises.readdir(targetPath)).filter(isSummaryFile);
|
|
14730
14840
|
if (summaries.length > 0) return cmdErr(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
|
|
14731
14841
|
}
|
|
14732
|
-
if (targetDir) node_fs.
|
|
14842
|
+
if (targetDir) await node_fs.promises.rm(node_path.default.join(phasesDirPath, targetDir), {
|
|
14733
14843
|
recursive: true,
|
|
14734
14844
|
force: true
|
|
14735
14845
|
});
|
|
@@ -14740,7 +14850,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14740
14850
|
const baseInt = baseParts[0];
|
|
14741
14851
|
const removedDecimal = parseInt(baseParts[1], 10);
|
|
14742
14852
|
try {
|
|
14743
|
-
const dirs =
|
|
14853
|
+
const dirs = await listSubDirsAsync(phasesDirPath, true);
|
|
14744
14854
|
const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
|
|
14745
14855
|
const toRename = [];
|
|
14746
14856
|
for (const dir of dirs) {
|
|
@@ -14757,15 +14867,15 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14757
14867
|
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
14758
14868
|
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
14759
14869
|
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
14760
|
-
node_fs.
|
|
14870
|
+
await node_fs.promises.rename(node_path.default.join(phasesDirPath, item.dir), node_path.default.join(phasesDirPath, newDirName));
|
|
14761
14871
|
renamedDirs.push({
|
|
14762
14872
|
from: item.dir,
|
|
14763
14873
|
to: newDirName
|
|
14764
14874
|
});
|
|
14765
|
-
const dirFiles = node_fs.
|
|
14875
|
+
const dirFiles = await node_fs.promises.readdir(node_path.default.join(phasesDirPath, newDirName));
|
|
14766
14876
|
for (const f of dirFiles) if (f.includes(oldPhaseId)) {
|
|
14767
14877
|
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
14768
|
-
node_fs.
|
|
14878
|
+
await node_fs.promises.rename(node_path.default.join(phasesDirPath, newDirName, f), node_path.default.join(phasesDirPath, newDirName, newFileName));
|
|
14769
14879
|
renamedFiles.push({
|
|
14770
14880
|
from: f,
|
|
14771
14881
|
to: newFileName
|
|
@@ -14781,7 +14891,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14781
14891
|
} else {
|
|
14782
14892
|
const removedInt = parseInt(normalized, 10);
|
|
14783
14893
|
try {
|
|
14784
|
-
const dirs =
|
|
14894
|
+
const dirs = await listSubDirsAsync(phasesDirPath, true);
|
|
14785
14895
|
const toRename = [];
|
|
14786
14896
|
for (const dir of dirs) {
|
|
14787
14897
|
const dm = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
|
@@ -14808,15 +14918,15 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14808
14918
|
const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
|
|
14809
14919
|
const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
|
|
14810
14920
|
const newDirName = `${newPrefix}-${item.slug}`;
|
|
14811
|
-
node_fs.
|
|
14921
|
+
await node_fs.promises.rename(node_path.default.join(phasesDirPath, item.dir), node_path.default.join(phasesDirPath, newDirName));
|
|
14812
14922
|
renamedDirs.push({
|
|
14813
14923
|
from: item.dir,
|
|
14814
14924
|
to: newDirName
|
|
14815
14925
|
});
|
|
14816
|
-
const dirFiles = node_fs.
|
|
14926
|
+
const dirFiles = await node_fs.promises.readdir(node_path.default.join(phasesDirPath, newDirName));
|
|
14817
14927
|
for (const f of dirFiles) if (f.startsWith(oldPrefix)) {
|
|
14818
14928
|
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
14819
|
-
node_fs.
|
|
14929
|
+
await node_fs.promises.rename(node_path.default.join(phasesDirPath, newDirName, f), node_path.default.join(phasesDirPath, newDirName, newFileName));
|
|
14820
14930
|
renamedFiles.push({
|
|
14821
14931
|
from: f,
|
|
14822
14932
|
to: newFileName
|
|
@@ -14830,7 +14940,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14830
14940
|
});
|
|
14831
14941
|
}
|
|
14832
14942
|
}
|
|
14833
|
-
let roadmapContent = node_fs.
|
|
14943
|
+
let roadmapContent = await node_fs.promises.readFile(rmPath, "utf-8");
|
|
14834
14944
|
const targetEscaped = escapePhaseNum(targetPhase);
|
|
14835
14945
|
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`, "i");
|
|
14836
14946
|
roadmapContent = roadmapContent.replace(sectionPattern, "");
|
|
@@ -14853,10 +14963,11 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14853
14963
|
roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, "gi"), `$1${newStr}`);
|
|
14854
14964
|
}
|
|
14855
14965
|
}
|
|
14856
|
-
node_fs.
|
|
14966
|
+
await node_fs.promises.writeFile(rmPath, roadmapContent, "utf-8");
|
|
14857
14967
|
const stPath = statePath(cwd);
|
|
14858
|
-
|
|
14859
|
-
|
|
14968
|
+
const stExists = await pathExistsAsync(stPath);
|
|
14969
|
+
if (stExists) {
|
|
14970
|
+
let stateContent = await node_fs.promises.readFile(stPath, "utf-8");
|
|
14860
14971
|
const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
|
|
14861
14972
|
const totalMatch = stateContent.match(totalPattern);
|
|
14862
14973
|
if (totalMatch) {
|
|
@@ -14869,7 +14980,7 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14869
14980
|
const oldTotal = parseInt(ofMatch[2], 10);
|
|
14870
14981
|
stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
|
|
14871
14982
|
}
|
|
14872
|
-
node_fs.
|
|
14983
|
+
await node_fs.promises.writeFile(stPath, stateContent, "utf-8");
|
|
14873
14984
|
}
|
|
14874
14985
|
return cmdOk({
|
|
14875
14986
|
removed: targetPhase,
|
|
@@ -14877,13 +14988,13 @@ function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
|
14877
14988
|
renamed_directories: renamedDirs,
|
|
14878
14989
|
renamed_files: renamedFiles,
|
|
14879
14990
|
roadmap_updated: true,
|
|
14880
|
-
state_updated:
|
|
14991
|
+
state_updated: stExists
|
|
14881
14992
|
});
|
|
14882
14993
|
}
|
|
14883
|
-
function cmdPhaseComplete(cwd, phaseNum) {
|
|
14994
|
+
async function cmdPhaseComplete(cwd, phaseNum) {
|
|
14884
14995
|
if (!phaseNum) return cmdErr("phase number required for phase complete");
|
|
14885
14996
|
try {
|
|
14886
|
-
const result = phaseCompleteCore(cwd, phaseNum);
|
|
14997
|
+
const result = await phaseCompleteCore(cwd, phaseNum);
|
|
14887
14998
|
return cmdOk({
|
|
14888
14999
|
completed_phase: result.completed_phase,
|
|
14889
15000
|
phase_name: result.phase_name,
|
|
@@ -15138,7 +15249,17 @@ function resolveArtefaktPath(cwd, type, phase) {
|
|
|
15138
15249
|
}
|
|
15139
15250
|
return planningPath(cwd, filename);
|
|
15140
15251
|
}
|
|
15252
|
+
const TEMPLATE_FILES = {
|
|
15253
|
+
"decisions": "decisions.md",
|
|
15254
|
+
"acceptance-criteria": "acceptance-criteria.md",
|
|
15255
|
+
"no-gos": "no-gos.md"
|
|
15256
|
+
};
|
|
15141
15257
|
function getTemplate(type) {
|
|
15258
|
+
const content = safeReadFile(node_path.default.join(node_os.default.homedir(), ".claude", "maxsim", "templates", TEMPLATE_FILES[type]));
|
|
15259
|
+
if (content) return content.replace(/\{\{date\}\}/g, todayISO());
|
|
15260
|
+
return getHardcodedTemplate(type);
|
|
15261
|
+
}
|
|
15262
|
+
function getHardcodedTemplate(type) {
|
|
15142
15263
|
const today = todayISO();
|
|
15143
15264
|
switch (type) {
|
|
15144
15265
|
case "decisions": return `# Decisions\n\n> Architectural and design decisions for this project.\n\n**Created:** ${today}\n\n## Decision Log\n\n| # | Decision | Rationale | Date | Phase |\n|---|----------|-----------|------|-------|\n`;
|
|
@@ -15248,6 +15369,36 @@ function addIfExists(files, cwd, relPath, role) {
|
|
|
15248
15369
|
const entry = fileEntry(cwd, relPath, role);
|
|
15249
15370
|
if (entry) files.push(entry);
|
|
15250
15371
|
}
|
|
15372
|
+
const TOPIC_TO_CODEBASE_DOCS = {
|
|
15373
|
+
ui: ["CONVENTIONS.md", "STRUCTURE.md"],
|
|
15374
|
+
frontend: ["CONVENTIONS.md", "STRUCTURE.md"],
|
|
15375
|
+
component: ["CONVENTIONS.md", "STRUCTURE.md"],
|
|
15376
|
+
api: ["ARCHITECTURE.md", "CONVENTIONS.md"],
|
|
15377
|
+
backend: ["ARCHITECTURE.md", "CONVENTIONS.md"],
|
|
15378
|
+
server: ["ARCHITECTURE.md", "CONVENTIONS.md"],
|
|
15379
|
+
database: ["ARCHITECTURE.md", "STACK.md"],
|
|
15380
|
+
schema: ["ARCHITECTURE.md", "STACK.md"],
|
|
15381
|
+
data: ["ARCHITECTURE.md", "STACK.md"],
|
|
15382
|
+
testing: ["TESTING.md", "CONVENTIONS.md"],
|
|
15383
|
+
test: ["TESTING.md", "CONVENTIONS.md"],
|
|
15384
|
+
integration: ["INTEGRATIONS.md", "STACK.md"],
|
|
15385
|
+
deploy: ["INTEGRATIONS.md", "STACK.md"],
|
|
15386
|
+
refactor: ["CONCERNS.md", "ARCHITECTURE.md"],
|
|
15387
|
+
cleanup: ["CONCERNS.md", "ARCHITECTURE.md"],
|
|
15388
|
+
setup: ["STACK.md", "STRUCTURE.md"],
|
|
15389
|
+
config: ["STACK.md", "STRUCTURE.md"],
|
|
15390
|
+
auth: ["ARCHITECTURE.md", "INTEGRATIONS.md"],
|
|
15391
|
+
performance: ["ARCHITECTURE.md", "STACK.md"],
|
|
15392
|
+
install: ["STACK.md", "STRUCTURE.md"]
|
|
15393
|
+
};
|
|
15394
|
+
const DEFAULT_CODEBASE_DOCS = ["STACK.md", "ARCHITECTURE.md"];
|
|
15395
|
+
function selectCodebaseDocs(topic) {
|
|
15396
|
+
if (!topic) return DEFAULT_CODEBASE_DOCS;
|
|
15397
|
+
const topicLower = topic.toLowerCase();
|
|
15398
|
+
const matched = /* @__PURE__ */ new Set();
|
|
15399
|
+
for (const [keyword, docs] of Object.entries(TOPIC_TO_CODEBASE_DOCS)) if (topicLower.includes(keyword)) for (const doc of docs) matched.add(doc);
|
|
15400
|
+
return matched.size > 0 ? Array.from(matched) : DEFAULT_CODEBASE_DOCS;
|
|
15401
|
+
}
|
|
15251
15402
|
function loadProjectContext(cwd) {
|
|
15252
15403
|
const files = [];
|
|
15253
15404
|
addIfExists(files, cwd, ".planning/PROJECT.md", "project-vision");
|
|
@@ -15296,6 +15447,16 @@ function loadArtefakteContext(cwd, phase) {
|
|
|
15296
15447
|
}
|
|
15297
15448
|
return files;
|
|
15298
15449
|
}
|
|
15450
|
+
function loadCodebaseContext(cwd, topic) {
|
|
15451
|
+
const files = [];
|
|
15452
|
+
const codebaseDir = planningPath(cwd, "codebase");
|
|
15453
|
+
try {
|
|
15454
|
+
const existing = node_fs.default.readdirSync(codebaseDir).filter((f) => f.endsWith(".md"));
|
|
15455
|
+
const selected = selectCodebaseDocs(topic);
|
|
15456
|
+
for (const filename of selected) if (existing.includes(filename)) addIfExists(files, cwd, `.planning/codebase/${filename}`, `codebase-${filename.replace(".md", "").toLowerCase()}`);
|
|
15457
|
+
} catch {}
|
|
15458
|
+
return files;
|
|
15459
|
+
}
|
|
15299
15460
|
function loadHistoryContext(cwd, currentPhase) {
|
|
15300
15461
|
const files = [];
|
|
15301
15462
|
const pd = phasesPath(cwd);
|
|
@@ -15319,6 +15480,8 @@ function cmdContextLoad(cwd, phase, topic, includeHistory) {
|
|
|
15319
15480
|
allFiles.push(...loadProjectContext(cwd));
|
|
15320
15481
|
allFiles.push(...loadRoadmapContext(cwd));
|
|
15321
15482
|
allFiles.push(...loadArtefakteContext(cwd, phase));
|
|
15483
|
+
const selectedDocs = selectCodebaseDocs(topic);
|
|
15484
|
+
allFiles.push(...loadCodebaseContext(cwd, topic));
|
|
15322
15485
|
if (phase) allFiles.push(...loadPhaseContext(cwd, phase));
|
|
15323
15486
|
if (includeHistory) allFiles.push(...loadHistoryContext(cwd, phase));
|
|
15324
15487
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -15331,7 +15494,8 @@ function cmdContextLoad(cwd, phase, topic, includeHistory) {
|
|
|
15331
15494
|
files: deduped,
|
|
15332
15495
|
total_size: deduped.reduce((sum, f) => sum + f.size, 0),
|
|
15333
15496
|
phase: phase ?? null,
|
|
15334
|
-
topic: topic ?? null
|
|
15497
|
+
topic: topic ?? null,
|
|
15498
|
+
codebase_docs_selected: selectedDocs
|
|
15335
15499
|
});
|
|
15336
15500
|
}
|
|
15337
15501
|
|
|
@@ -16374,7 +16538,7 @@ const handleRoadmap = async (args, cwd, raw) => {
|
|
|
16374
16538
|
if (handler) return handleResult(await handler(), raw);
|
|
16375
16539
|
error("Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress");
|
|
16376
16540
|
};
|
|
16377
|
-
const handlePhase = (args, cwd, raw) => {
|
|
16541
|
+
const handlePhase = async (args, cwd, raw) => {
|
|
16378
16542
|
const sub = args[1];
|
|
16379
16543
|
const handler = sub ? {
|
|
16380
16544
|
"next-decimal": () => cmdPhaseNextDecimal(cwd, args[2]),
|
|
@@ -16383,7 +16547,7 @@ const handlePhase = (args, cwd, raw) => {
|
|
|
16383
16547
|
"remove": () => cmdPhaseRemove(cwd, args[2], { force: hasFlag(args, "force") }),
|
|
16384
16548
|
"complete": () => cmdPhaseComplete(cwd, args[2])
|
|
16385
16549
|
}[sub] : void 0;
|
|
16386
|
-
if (handler) return handleResult(handler(), raw);
|
|
16550
|
+
if (handler) return handleResult(await handler(), raw);
|
|
16387
16551
|
error("Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete");
|
|
16388
16552
|
};
|
|
16389
16553
|
const handleMilestone = (args, cwd, raw) => {
|
|
@@ -16436,7 +16600,7 @@ const handleInit = (args, cwd, raw) => {
|
|
|
16436
16600
|
const COMMANDS = {
|
|
16437
16601
|
"state": handleState,
|
|
16438
16602
|
"resolve-model": (args, cwd, raw) => handleResult(cmdResolveModel(cwd, args[1], raw), raw),
|
|
16439
|
-
"find-phase": (args, cwd, raw) => handleResult(cmdFindPhase(cwd, args[1]), raw),
|
|
16603
|
+
"find-phase": async (args, cwd, raw) => handleResult(await cmdFindPhase(cwd, args[1]), raw),
|
|
16440
16604
|
"commit": async (args, cwd, raw) => {
|
|
16441
16605
|
const files = args.indexOf("--files") !== -1 ? args.slice(args.indexOf("--files") + 1).filter((a) => !a.startsWith("--")) : [];
|
|
16442
16606
|
handleResult(await cmdCommit(cwd, args[1], files, raw, hasFlag(args, "amend")), raw);
|
|
@@ -16479,8 +16643,8 @@ const COMMANDS = {
|
|
|
16479
16643
|
}, raw), raw);
|
|
16480
16644
|
},
|
|
16481
16645
|
"init": handleInit,
|
|
16482
|
-
"phase-plan-index": (args, cwd, raw) => handleResult(cmdPhasePlanIndex(cwd, args[1]), raw),
|
|
16483
|
-
"state-snapshot": (_args, cwd, raw) => handleResult(cmdStateSnapshot(cwd, raw), raw),
|
|
16646
|
+
"phase-plan-index": async (args, cwd, raw) => handleResult(await cmdPhasePlanIndex(cwd, args[1]), raw),
|
|
16647
|
+
"state-snapshot": async (_args, cwd, raw) => handleResult(await cmdStateSnapshot(cwd, raw), raw),
|
|
16484
16648
|
"summary-extract": (args, cwd, raw) => {
|
|
16485
16649
|
const fieldsIndex = args.indexOf("--fields");
|
|
16486
16650
|
const fields = fieldsIndex !== -1 ? args[fieldsIndex + 1].split(",") : null;
|
|
@@ -16511,7 +16675,7 @@ const COMMANDS = {
|
|
|
16511
16675
|
(0, node_child_process.spawn)(process.execPath, [serverPath], { stdio: "inherit" }).on("exit", (code) => process.exit(code ?? 0));
|
|
16512
16676
|
},
|
|
16513
16677
|
"backend-start": async (args, cwd, raw) => {
|
|
16514
|
-
const { startBackend } = await Promise.resolve().then(() => require("./lifecycle-
|
|
16678
|
+
const { startBackend } = await Promise.resolve().then(() => require("./lifecycle-0M4VqOMm.cjs"));
|
|
16515
16679
|
const portFlag = args.find((a) => a.startsWith("--port="))?.split("=")[1];
|
|
16516
16680
|
const background = !args.includes("--foreground");
|
|
16517
16681
|
output(await startBackend(cwd, {
|
|
@@ -16520,11 +16684,11 @@ const COMMANDS = {
|
|
|
16520
16684
|
}), raw);
|
|
16521
16685
|
},
|
|
16522
16686
|
"backend-stop": async (_args, cwd, raw) => {
|
|
16523
|
-
const { stopBackend } = await Promise.resolve().then(() => require("./lifecycle-
|
|
16687
|
+
const { stopBackend } = await Promise.resolve().then(() => require("./lifecycle-0M4VqOMm.cjs"));
|
|
16524
16688
|
output({ stopped: await stopBackend(cwd) }, raw);
|
|
16525
16689
|
},
|
|
16526
16690
|
"backend-status": async (_args, cwd, raw) => {
|
|
16527
|
-
const { getBackendStatus } = await Promise.resolve().then(() => require("./lifecycle-
|
|
16691
|
+
const { getBackendStatus } = await Promise.resolve().then(() => require("./lifecycle-0M4VqOMm.cjs"));
|
|
16528
16692
|
output(await getBackendStatus(cwd) || { running: false }, raw);
|
|
16529
16693
|
}
|
|
16530
16694
|
};
|