maxsimcli 4.5.0 → 4.7.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/assets/CHANGELOG.md +38 -0
- package/dist/backend-server.cjs +2739 -41
- package/dist/backend-server.cjs.map +1 -1
- package/dist/cli.cjs +3 -3
- package/dist/{lifecycle-DxCru7rk.cjs → lifecycle-D8mcsEjy.cjs} +2 -2
- package/dist/{lifecycle-DxCru7rk.cjs.map → lifecycle-D8mcsEjy.cjs.map} +1 -1
- package/dist/mcp-server.cjs +2715 -16
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/{server-By0TN-nC.cjs → server-BAHfh_vw.cjs} +2716 -17
- package/dist/server-BAHfh_vw.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/.tsbuildinfo +0 -1
- package/dist/backend/index.d.ts +0 -4
- package/dist/backend/index.d.ts.map +0 -1
- package/dist/backend/index.js +0 -12
- package/dist/backend/index.js.map +0 -1
- package/dist/backend/lifecycle.d.ts +0 -13
- package/dist/backend/lifecycle.d.ts.map +0 -1
- package/dist/backend/lifecycle.js +0 -168
- package/dist/backend/lifecycle.js.map +0 -1
- package/dist/backend/server.d.ts +0 -13
- package/dist/backend/server.d.ts.map +0 -1
- package/dist/backend/server.js +0 -1013
- package/dist/backend/server.js.map +0 -1
- package/dist/backend/terminal.d.ts +0 -49
- package/dist/backend/terminal.d.ts.map +0 -1
- package/dist/backend/terminal.js +0 -209
- package/dist/backend/terminal.js.map +0 -1
- package/dist/backend/types.d.ts +0 -77
- package/dist/backend/types.d.ts.map +0 -1
- package/dist/backend/types.js +0 -6
- package/dist/backend/types.js.map +0 -1
- package/dist/backend-server.d.cts +0 -2
- package/dist/backend-server.d.ts +0 -11
- package/dist/backend-server.d.ts.map +0 -1
- package/dist/backend-server.js +0 -43
- package/dist/backend-server.js.map +0 -1
- package/dist/cli.d.cts +0 -2
- package/dist/cli.d.ts +0 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -510
- package/dist/cli.js.map +0 -1
- package/dist/core/artefakte.d.ts +0 -12
- package/dist/core/artefakte.d.ts.map +0 -1
- package/dist/core/artefakte.js +0 -152
- package/dist/core/artefakte.js.map +0 -1
- package/dist/core/commands.d.ts +0 -26
- package/dist/core/commands.d.ts.map +0 -1
- package/dist/core/commands.js +0 -550
- package/dist/core/commands.js.map +0 -1
- package/dist/core/config.d.ts +0 -10
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -143
- package/dist/core/config.js.map +0 -1
- package/dist/core/context-loader.d.ts +0 -21
- package/dist/core/context-loader.d.ts.map +0 -1
- package/dist/core/context-loader.js +0 -212
- package/dist/core/context-loader.js.map +0 -1
- package/dist/core/core.d.ts +0 -91
- package/dist/core/core.d.ts.map +0 -1
- package/dist/core/core.js +0 -823
- package/dist/core/core.js.map +0 -1
- package/dist/core/dashboard-launcher.d.ts +0 -56
- package/dist/core/dashboard-launcher.d.ts.map +0 -1
- package/dist/core/dashboard-launcher.js +0 -246
- package/dist/core/dashboard-launcher.js.map +0 -1
- package/dist/core/drift.d.ts +0 -37
- package/dist/core/drift.d.ts.map +0 -1
- package/dist/core/drift.js +0 -213
- package/dist/core/drift.js.map +0 -1
- package/dist/core/frontmatter.d.ts +0 -33
- package/dist/core/frontmatter.d.ts.map +0 -1
- package/dist/core/frontmatter.js +0 -193
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/index.d.ts +0 -28
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -189
- package/dist/core/index.js.map +0 -1
- package/dist/core/init.d.ts +0 -287
- package/dist/core/init.d.ts.map +0 -1
- package/dist/core/init.js +0 -816
- package/dist/core/init.js.map +0 -1
- package/dist/core/milestone.d.ts +0 -9
- package/dist/core/milestone.d.ts.map +0 -1
- package/dist/core/milestone.js +0 -230
- package/dist/core/milestone.js.map +0 -1
- package/dist/core/phase.d.ts +0 -53
- package/dist/core/phase.d.ts.map +0 -1
- package/dist/core/phase.js +0 -891
- package/dist/core/phase.js.map +0 -1
- package/dist/core/roadmap.d.ts +0 -10
- package/dist/core/roadmap.d.ts.map +0 -1
- package/dist/core/roadmap.js +0 -165
- package/dist/core/roadmap.js.map +0 -1
- package/dist/core/skills.d.ts +0 -20
- package/dist/core/skills.d.ts.map +0 -1
- package/dist/core/skills.js +0 -144
- package/dist/core/skills.js.map +0 -1
- package/dist/core/start.d.ts +0 -15
- package/dist/core/start.d.ts.map +0 -1
- package/dist/core/start.js +0 -80
- package/dist/core/start.js.map +0 -1
- package/dist/core/state.d.ts +0 -32
- package/dist/core/state.d.ts.map +0 -1
- package/dist/core/state.js +0 -582
- package/dist/core/state.js.map +0 -1
- package/dist/core/template.d.ts +0 -30
- package/dist/core/template.d.ts.map +0 -1
- package/dist/core/template.js +0 -223
- package/dist/core/template.js.map +0 -1
- package/dist/core/types.d.ts +0 -519
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -60
- package/dist/core/types.js.map +0 -1
- package/dist/core/verify.d.ts +0 -128
- package/dist/core/verify.d.ts.map +0 -1
- package/dist/core/verify.js +0 -754
- package/dist/core/verify.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -11
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -18
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/maxsim-check-update.d.ts +0 -17
- package/dist/hooks/maxsim-check-update.d.ts.map +0 -1
- package/dist/hooks/maxsim-check-update.js +0 -101
- package/dist/hooks/maxsim-check-update.js.map +0 -1
- package/dist/hooks/maxsim-context-monitor.d.ts +0 -21
- package/dist/hooks/maxsim-context-monitor.d.ts.map +0 -1
- package/dist/hooks/maxsim-context-monitor.js +0 -131
- package/dist/hooks/maxsim-context-monitor.js.map +0 -1
- package/dist/hooks/maxsim-statusline.d.ts +0 -19
- package/dist/hooks/maxsim-statusline.d.ts.map +0 -1
- package/dist/hooks/maxsim-statusline.js +0 -146
- package/dist/hooks/maxsim-statusline.js.map +0 -1
- package/dist/hooks/shared.d.ts +0 -11
- package/dist/hooks/shared.d.ts.map +0 -1
- package/dist/hooks/shared.js +0 -29
- package/dist/hooks/shared.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/dist/install/adapters.d.ts +0 -6
- package/dist/install/adapters.d.ts.map +0 -1
- package/dist/install/adapters.js +0 -65
- package/dist/install/adapters.js.map +0 -1
- package/dist/install/copy.d.ts +0 -6
- package/dist/install/copy.d.ts.map +0 -1
- package/dist/install/copy.js +0 -71
- package/dist/install/copy.js.map +0 -1
- package/dist/install/dashboard.d.ts +0 -16
- package/dist/install/dashboard.d.ts.map +0 -1
- package/dist/install/dashboard.js +0 -273
- package/dist/install/dashboard.js.map +0 -1
- package/dist/install/hooks.d.ts +0 -31
- package/dist/install/hooks.d.ts.map +0 -1
- package/dist/install/hooks.js +0 -260
- package/dist/install/hooks.js.map +0 -1
- package/dist/install/index.d.ts +0 -2
- package/dist/install/index.d.ts.map +0 -1
- package/dist/install/index.js +0 -534
- package/dist/install/index.js.map +0 -1
- package/dist/install/manifest.d.ts +0 -23
- package/dist/install/manifest.d.ts.map +0 -1
- package/dist/install/manifest.js +0 -133
- package/dist/install/manifest.js.map +0 -1
- package/dist/install/patches.d.ts +0 -10
- package/dist/install/patches.d.ts.map +0 -1
- package/dist/install/patches.js +0 -124
- package/dist/install/patches.js.map +0 -1
- package/dist/install/shared.d.ts +0 -56
- package/dist/install/shared.d.ts.map +0 -1
- package/dist/install/shared.js +0 -181
- package/dist/install/shared.js.map +0 -1
- package/dist/install/uninstall.d.ts +0 -5
- package/dist/install/uninstall.d.ts.map +0 -1
- package/dist/install/uninstall.js +0 -222
- package/dist/install/uninstall.js.map +0 -1
- package/dist/install/utils.d.ts +0 -27
- package/dist/install/utils.d.ts.map +0 -1
- package/dist/install/utils.js +0 -99
- package/dist/install/utils.js.map +0 -1
- package/dist/install.d.cts +0 -2
- package/dist/mcp/config-tools.d.ts +0 -13
- package/dist/mcp/config-tools.d.ts.map +0 -1
- package/dist/mcp/config-tools.js +0 -66
- package/dist/mcp/config-tools.js.map +0 -1
- package/dist/mcp/context-tools.d.ts +0 -13
- package/dist/mcp/context-tools.d.ts.map +0 -1
- package/dist/mcp/context-tools.js +0 -176
- package/dist/mcp/context-tools.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -11
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -26
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/phase-tools.d.ts +0 -13
- package/dist/mcp/phase-tools.d.ts.map +0 -1
- package/dist/mcp/phase-tools.js +0 -177
- package/dist/mcp/phase-tools.js.map +0 -1
- package/dist/mcp/roadmap-tools.d.ts +0 -13
- package/dist/mcp/roadmap-tools.d.ts.map +0 -1
- package/dist/mcp/roadmap-tools.js +0 -79
- package/dist/mcp/roadmap-tools.js.map +0 -1
- package/dist/mcp/state-tools.d.ts +0 -13
- package/dist/mcp/state-tools.d.ts.map +0 -1
- package/dist/mcp/state-tools.js +0 -185
- package/dist/mcp/state-tools.js.map +0 -1
- package/dist/mcp/todo-tools.d.ts +0 -13
- package/dist/mcp/todo-tools.d.ts.map +0 -1
- package/dist/mcp/todo-tools.js +0 -143
- package/dist/mcp/todo-tools.js.map +0 -1
- package/dist/mcp/utils.d.ts +0 -27
- package/dist/mcp/utils.d.ts.map +0 -1
- package/dist/mcp/utils.js +0 -82
- package/dist/mcp/utils.js.map +0 -1
- package/dist/mcp-server.d.cts +0 -2
- package/dist/mcp-server.d.ts +0 -12
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/mcp-server.js +0 -31
- package/dist/mcp-server.js.map +0 -1
- package/dist/server-By0TN-nC.cjs.map +0 -1
package/dist/core/phase.js
DELETED
|
@@ -1,891 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Phase — Phase CRUD, query, and lifecycle operations
|
|
4
|
-
*
|
|
5
|
-
* Ported from maxsim/bin/lib/phase.cjs
|
|
6
|
-
*/
|
|
7
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
-
};
|
|
10
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.scaffoldPhaseStubs = scaffoldPhaseStubs;
|
|
12
|
-
exports.phaseAddCore = phaseAddCore;
|
|
13
|
-
exports.phaseInsertCore = phaseInsertCore;
|
|
14
|
-
exports.phaseCompleteCore = phaseCompleteCore;
|
|
15
|
-
exports.cmdPhasesList = cmdPhasesList;
|
|
16
|
-
exports.cmdPhaseNextDecimal = cmdPhaseNextDecimal;
|
|
17
|
-
exports.cmdFindPhase = cmdFindPhase;
|
|
18
|
-
exports.cmdPhasePlanIndex = cmdPhasePlanIndex;
|
|
19
|
-
exports.cmdPhaseAdd = cmdPhaseAdd;
|
|
20
|
-
exports.cmdPhaseInsert = cmdPhaseInsert;
|
|
21
|
-
exports.cmdPhaseRemove = cmdPhaseRemove;
|
|
22
|
-
exports.cmdPhaseComplete = cmdPhaseComplete;
|
|
23
|
-
exports.archivePhasePreview = archivePhasePreview;
|
|
24
|
-
exports.archivePhaseExecute = archivePhaseExecute;
|
|
25
|
-
exports.cmdGetArchivedPhase = cmdGetArchivedPhase;
|
|
26
|
-
const node_fs_1 = require("node:fs");
|
|
27
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
-
const core_js_1 = require("./core.js");
|
|
29
|
-
const frontmatter_js_1 = require("./frontmatter.js");
|
|
30
|
-
const types_js_1 = require("./types.js");
|
|
31
|
-
// ─── Stub scaffolding ───────────────────────────────────────────────────────
|
|
32
|
-
async function scaffoldPhaseStubs(dirPath, phaseId, name) {
|
|
33
|
-
const today = (0, core_js_1.todayISO)();
|
|
34
|
-
await Promise.all([
|
|
35
|
-
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`),
|
|
36
|
-
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`),
|
|
37
|
-
]);
|
|
38
|
-
}
|
|
39
|
-
// ─── Core functions ─────────────────────────────────────────────────────────
|
|
40
|
-
async function phaseAddCore(cwd, description, options) {
|
|
41
|
-
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
42
|
-
let content;
|
|
43
|
-
try {
|
|
44
|
-
content = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
throw new Error('ROADMAP.md not found');
|
|
48
|
-
}
|
|
49
|
-
const slug = (0, core_js_1.generateSlugInternal)(description);
|
|
50
|
-
const phasePattern = (0, core_js_1.getPhasePattern)();
|
|
51
|
-
let maxPhase = 0;
|
|
52
|
-
let m;
|
|
53
|
-
while ((m = phasePattern.exec(content)) !== null) {
|
|
54
|
-
const num = parseInt(m[1], 10);
|
|
55
|
-
if (num > maxPhase)
|
|
56
|
-
maxPhase = num;
|
|
57
|
-
}
|
|
58
|
-
const newPhaseNum = maxPhase + 1;
|
|
59
|
-
const paddedNum = String(newPhaseNum).padStart(2, '0');
|
|
60
|
-
const dirName = `${paddedNum}-${slug}`;
|
|
61
|
-
const dirPath = (0, core_js_1.planningPath)(cwd, 'phases', dirName);
|
|
62
|
-
await node_fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
63
|
-
await node_fs_1.promises.writeFile(node_path_1.default.join(dirPath, '.gitkeep'), '');
|
|
64
|
-
if (options?.includeStubs) {
|
|
65
|
-
await scaffoldPhaseStubs(dirPath, paddedNum, description);
|
|
66
|
-
}
|
|
67
|
-
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`;
|
|
68
|
-
let updatedContent;
|
|
69
|
-
const lastSeparator = content.lastIndexOf('\n---');
|
|
70
|
-
if (lastSeparator > 0) {
|
|
71
|
-
updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
updatedContent = content + phaseEntry;
|
|
75
|
-
}
|
|
76
|
-
await node_fs_1.promises.writeFile(rmPath, updatedContent, 'utf-8');
|
|
77
|
-
return {
|
|
78
|
-
phase_number: newPhaseNum,
|
|
79
|
-
padded: paddedNum,
|
|
80
|
-
slug,
|
|
81
|
-
directory: `.planning/phases/${dirName}`,
|
|
82
|
-
description,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
async function phaseInsertCore(cwd, afterPhase, description, options) {
|
|
86
|
-
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
87
|
-
let content;
|
|
88
|
-
try {
|
|
89
|
-
content = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
throw new Error('ROADMAP.md not found');
|
|
93
|
-
}
|
|
94
|
-
const slug = (0, core_js_1.generateSlugInternal)(description);
|
|
95
|
-
const normalizedAfter = (0, core_js_1.normalizePhaseName)(afterPhase);
|
|
96
|
-
const unpadded = normalizedAfter.replace(/^0+/, '');
|
|
97
|
-
const afterPhaseEscaped = '0*' + unpadded.replace(/\./g, '\\.');
|
|
98
|
-
const targetPattern = (0, core_js_1.getPhasePattern)(afterPhaseEscaped, 'i');
|
|
99
|
-
if (!targetPattern.test(content)) {
|
|
100
|
-
throw new Error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
101
|
-
}
|
|
102
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
103
|
-
const normalizedBase = (0, core_js_1.normalizePhaseName)(afterPhase);
|
|
104
|
-
const existingDecimals = [];
|
|
105
|
-
try {
|
|
106
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath);
|
|
107
|
-
const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
|
|
108
|
-
for (const dir of dirs) {
|
|
109
|
-
const dm = dir.match(decimalPattern);
|
|
110
|
-
if (dm)
|
|
111
|
-
existingDecimals.push(parseInt(dm[1], 10));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
(0, core_js_1.debugLog)('phase-insert-decimal-scan-failed', e);
|
|
116
|
-
}
|
|
117
|
-
const nextDecimal = existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1;
|
|
118
|
-
const decimalPhase = `${normalizedBase}.${nextDecimal}`;
|
|
119
|
-
const dirName = `${decimalPhase}-${slug}`;
|
|
120
|
-
const dirPath = (0, core_js_1.planningPath)(cwd, 'phases', dirName);
|
|
121
|
-
await node_fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
122
|
-
await node_fs_1.promises.writeFile(node_path_1.default.join(dirPath, '.gitkeep'), '');
|
|
123
|
-
if (options?.includeStubs) {
|
|
124
|
-
await scaffoldPhaseStubs(dirPath, decimalPhase, description);
|
|
125
|
-
}
|
|
126
|
-
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`;
|
|
127
|
-
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
|
128
|
-
const headerMatch = content.match(headerPattern);
|
|
129
|
-
if (!headerMatch) {
|
|
130
|
-
throw new Error(`Could not find Phase ${afterPhase} header`);
|
|
131
|
-
}
|
|
132
|
-
const headerIdx = content.indexOf(headerMatch[0]);
|
|
133
|
-
const afterHeader = content.slice(headerIdx + headerMatch[0].length);
|
|
134
|
-
const nextPhaseMatch = afterHeader.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
135
|
-
let insertIdx;
|
|
136
|
-
if (nextPhaseMatch) {
|
|
137
|
-
insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
insertIdx = content.length;
|
|
141
|
-
}
|
|
142
|
-
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
143
|
-
await node_fs_1.promises.writeFile(rmPath, updatedContent, 'utf-8');
|
|
144
|
-
return {
|
|
145
|
-
phase_number: decimalPhase,
|
|
146
|
-
after_phase: afterPhase,
|
|
147
|
-
slug,
|
|
148
|
-
directory: `.planning/phases/${dirName}`,
|
|
149
|
-
description,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
async function phaseCompleteCore(cwd, phaseNum) {
|
|
153
|
-
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
154
|
-
const stPath = (0, core_js_1.statePath)(cwd);
|
|
155
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
156
|
-
const today = (0, core_js_1.todayISO)();
|
|
157
|
-
const phaseInfo = await (0, core_js_1.findPhaseInternalAsync)(cwd, phaseNum);
|
|
158
|
-
if (!phaseInfo) {
|
|
159
|
-
throw new Error(`Phase ${phaseNum} not found`);
|
|
160
|
-
}
|
|
161
|
-
const planCount = phaseInfo.plans.length;
|
|
162
|
-
const summaryCount = phaseInfo.summaries.length;
|
|
163
|
-
let requirementsUpdated = false;
|
|
164
|
-
const rmExists = await (0, core_js_1.pathExistsAsync)(rmPath);
|
|
165
|
-
if (rmExists) {
|
|
166
|
-
let roadmapContent = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
167
|
-
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${(0, core_js_1.escapePhaseNum)(phaseNum)}[:\\s][^\\n]*)`, 'i');
|
|
168
|
-
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
169
|
-
const phaseEscaped = (0, core_js_1.escapePhaseNum)(phaseNum);
|
|
170
|
-
const tablePattern = new RegExp(`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`, 'i');
|
|
171
|
-
roadmapContent = roadmapContent.replace(tablePattern, `$1 Complete $2 ${today} $3`);
|
|
172
|
-
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
|
|
173
|
-
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
174
|
-
(0, core_js_1.debugLog)('phase-complete-write', `writing ROADMAP.md for phase ${phaseNum}`);
|
|
175
|
-
await node_fs_1.promises.writeFile(rmPath, roadmapContent, 'utf-8');
|
|
176
|
-
(0, core_js_1.debugLog)('phase-complete-write', `ROADMAP.md updated for phase ${phaseNum}`);
|
|
177
|
-
// Update REQUIREMENTS.md
|
|
178
|
-
const reqPath = (0, core_js_1.planningPath)(cwd, 'REQUIREMENTS.md');
|
|
179
|
-
if (await (0, core_js_1.pathExistsAsync)(reqPath)) {
|
|
180
|
-
const reqMatch = roadmapContent.match(new RegExp(`Phase\\s+${(0, core_js_1.escapePhaseNum)(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, 'i'));
|
|
181
|
-
if (reqMatch) {
|
|
182
|
-
const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
|
|
183
|
-
let reqContent = await node_fs_1.promises.readFile(reqPath, 'utf-8');
|
|
184
|
-
for (const reqId of reqIds) {
|
|
185
|
-
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, 'gi'), '$1x$2');
|
|
186
|
-
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'), '$1 Complete $2');
|
|
187
|
-
}
|
|
188
|
-
(0, core_js_1.debugLog)('phase-complete-write', `writing REQUIREMENTS.md for phase ${phaseNum}`);
|
|
189
|
-
await node_fs_1.promises.writeFile(reqPath, reqContent, 'utf-8');
|
|
190
|
-
(0, core_js_1.debugLog)('phase-complete-write', `REQUIREMENTS.md updated for phase ${phaseNum}`);
|
|
191
|
-
requirementsUpdated = true;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// Find next phase
|
|
196
|
-
let nextPhaseNum = null;
|
|
197
|
-
let nextPhaseName = null;
|
|
198
|
-
let isLastPhase = true;
|
|
199
|
-
try {
|
|
200
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
201
|
-
for (const dir of dirs) {
|
|
202
|
-
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
203
|
-
if (dm) {
|
|
204
|
-
if ((0, core_js_1.comparePhaseNum)(dm[1], phaseNum) > 0) {
|
|
205
|
-
nextPhaseNum = dm[1];
|
|
206
|
-
nextPhaseName = dm[2] || null;
|
|
207
|
-
isLastPhase = false;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
catch (e) {
|
|
214
|
-
(0, core_js_1.debugLog)('phase-complete-next-phase-scan-failed', e);
|
|
215
|
-
}
|
|
216
|
-
// Update STATE.md
|
|
217
|
-
const stExists = await (0, core_js_1.pathExistsAsync)(stPath);
|
|
218
|
-
if (stExists) {
|
|
219
|
-
let stateContent = await node_fs_1.promises.readFile(stPath, 'utf-8');
|
|
220
|
-
stateContent = stateContent.replace(/(\*\*Current Phase:\*\*\s*).*/, `$1${nextPhaseNum || phaseNum}`);
|
|
221
|
-
if (nextPhaseName) {
|
|
222
|
-
stateContent = stateContent.replace(/(\*\*Current Phase Name:\*\*\s*).*/, `$1${nextPhaseName.replace(/-/g, ' ')}`);
|
|
223
|
-
}
|
|
224
|
-
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${isLastPhase ? 'Milestone complete' : 'Ready to plan'}`);
|
|
225
|
-
stateContent = stateContent.replace(/(\*\*Current Plan:\*\*\s*).*/, `$1Not started`);
|
|
226
|
-
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
227
|
-
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`);
|
|
228
|
-
(0, core_js_1.debugLog)('phase-complete-write', `writing STATE.md for phase ${phaseNum}`);
|
|
229
|
-
await node_fs_1.promises.writeFile(stPath, stateContent, 'utf-8');
|
|
230
|
-
(0, core_js_1.debugLog)('phase-complete-write', `STATE.md updated for phase ${phaseNum}`);
|
|
231
|
-
}
|
|
232
|
-
return {
|
|
233
|
-
completed_phase: phaseNum,
|
|
234
|
-
phase_name: phaseInfo.phase_name,
|
|
235
|
-
plans_executed: `${summaryCount}/${planCount}`,
|
|
236
|
-
next_phase: nextPhaseNum,
|
|
237
|
-
next_phase_name: nextPhaseName,
|
|
238
|
-
is_last_phase: isLastPhase,
|
|
239
|
-
date: today,
|
|
240
|
-
roadmap_updated: rmExists,
|
|
241
|
-
state_updated: stExists,
|
|
242
|
-
requirements_updated: requirementsUpdated,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
// ─── Phase list ─────────────────────────────────────────────────────────────
|
|
246
|
-
async function cmdPhasesList(cwd, options) {
|
|
247
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
248
|
-
const { type, phase, includeArchived, offset, limit } = options;
|
|
249
|
-
if (!(await (0, core_js_1.pathExistsAsync)(phasesDirPath))) {
|
|
250
|
-
if (type) {
|
|
251
|
-
return (0, types_js_1.cmdOk)({ files: [], count: 0, total: 0 }, '');
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
return (0, types_js_1.cmdOk)({ directories: [], count: 0, total: 0 }, '');
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
let dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath);
|
|
259
|
-
if (includeArchived) {
|
|
260
|
-
const archived = await (0, core_js_1.getArchivedPhaseDirsAsync)(cwd);
|
|
261
|
-
for (const a of archived) {
|
|
262
|
-
dirs.push(`${a.name} [${a.milestone}]`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
dirs.sort((a, b) => (0, core_js_1.comparePhaseNum)(a, b));
|
|
266
|
-
if (phase) {
|
|
267
|
-
const normalized = (0, core_js_1.normalizePhaseName)(phase);
|
|
268
|
-
const match = dirs.find(d => d.startsWith(normalized));
|
|
269
|
-
if (!match) {
|
|
270
|
-
return (0, types_js_1.cmdOk)({ files: [], count: 0, total: 0, phase_dir: null, error: 'Phase not found' }, '');
|
|
271
|
-
}
|
|
272
|
-
dirs = [match];
|
|
273
|
-
}
|
|
274
|
-
if (type) {
|
|
275
|
-
const fileResults = await Promise.all(dirs.map(async (dir) => {
|
|
276
|
-
const dirPath = node_path_1.default.join(phasesDirPath, dir);
|
|
277
|
-
const dirFiles = await node_fs_1.promises.readdir(dirPath);
|
|
278
|
-
let filtered;
|
|
279
|
-
if (type === 'plans') {
|
|
280
|
-
filtered = dirFiles.filter(core_js_1.isPlanFile);
|
|
281
|
-
}
|
|
282
|
-
else if (type === 'summaries') {
|
|
283
|
-
filtered = dirFiles.filter(core_js_1.isSummaryFile);
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
filtered = dirFiles;
|
|
287
|
-
}
|
|
288
|
-
return filtered.sort();
|
|
289
|
-
}));
|
|
290
|
-
const files = fileResults.flat();
|
|
291
|
-
const result = {
|
|
292
|
-
files,
|
|
293
|
-
count: files.length,
|
|
294
|
-
total: files.length,
|
|
295
|
-
phase_dir: phase ? dirs[0].replace(/^\d+(?:\.\d+)?-?/, '') : null,
|
|
296
|
-
};
|
|
297
|
-
return (0, types_js_1.cmdOk)(result, files.join('\n'));
|
|
298
|
-
}
|
|
299
|
-
// Apply pagination
|
|
300
|
-
const total = dirs.length;
|
|
301
|
-
const start = offset ?? 0;
|
|
302
|
-
const paginated = limit !== undefined ? dirs.slice(start, start + limit) : dirs.slice(start);
|
|
303
|
-
return (0, types_js_1.cmdOk)({ directories: paginated, count: paginated.length, total }, paginated.join('\n'));
|
|
304
|
-
}
|
|
305
|
-
catch (e) {
|
|
306
|
-
return (0, types_js_1.cmdErr)('Failed to list phases: ' + e.message);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// ─── Next decimal ───────────────────────────────────────────────────────────
|
|
310
|
-
async function cmdPhaseNextDecimal(cwd, basePhase) {
|
|
311
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
312
|
-
const normalized = (0, core_js_1.normalizePhaseName)(basePhase);
|
|
313
|
-
if (!(await (0, core_js_1.pathExistsAsync)(phasesDirPath))) {
|
|
314
|
-
return (0, types_js_1.cmdOk)({ found: false, base_phase: normalized, next: `${normalized}.1`, existing: [] }, `${normalized}.1`);
|
|
315
|
-
}
|
|
316
|
-
try {
|
|
317
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath);
|
|
318
|
-
const baseExists = dirs.some(d => d.startsWith(normalized + '-') || d === normalized);
|
|
319
|
-
const decimalPattern = new RegExp(`^${normalized}\\.(\\d+)`);
|
|
320
|
-
const existingDecimals = [];
|
|
321
|
-
for (const dir of dirs) {
|
|
322
|
-
const match = dir.match(decimalPattern);
|
|
323
|
-
if (match) {
|
|
324
|
-
existingDecimals.push(`${normalized}.${match[1]}`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
existingDecimals.sort((a, b) => {
|
|
328
|
-
const aNum = parseFloat(a);
|
|
329
|
-
const bNum = parseFloat(b);
|
|
330
|
-
return aNum - bNum;
|
|
331
|
-
});
|
|
332
|
-
let nextDecimal;
|
|
333
|
-
if (existingDecimals.length === 0) {
|
|
334
|
-
nextDecimal = `${normalized}.1`;
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
const lastDecimal = existingDecimals[existingDecimals.length - 1];
|
|
338
|
-
const lastNum = parseInt(lastDecimal.split('.')[1], 10);
|
|
339
|
-
nextDecimal = `${normalized}.${lastNum + 1}`;
|
|
340
|
-
}
|
|
341
|
-
return (0, types_js_1.cmdOk)({ found: baseExists, base_phase: normalized, next: nextDecimal, existing: existingDecimals }, nextDecimal);
|
|
342
|
-
}
|
|
343
|
-
catch (e) {
|
|
344
|
-
return (0, types_js_1.cmdErr)('Failed to calculate next decimal phase: ' + e.message);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
// ─── Find phase ─────────────────────────────────────────────────────────────
|
|
348
|
-
async function cmdFindPhase(cwd, phase) {
|
|
349
|
-
if (!phase) {
|
|
350
|
-
return (0, types_js_1.cmdErr)('phase identifier required');
|
|
351
|
-
}
|
|
352
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
353
|
-
const normalized = (0, core_js_1.normalizePhaseName)(phase);
|
|
354
|
-
const notFound = { found: false, directory: null, phase_number: null, phase_name: null, plans: [], summaries: [] };
|
|
355
|
-
try {
|
|
356
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
357
|
-
const match = dirs.find(d => d.startsWith(normalized));
|
|
358
|
-
if (!match) {
|
|
359
|
-
return (0, types_js_1.cmdOk)(notFound, '');
|
|
360
|
-
}
|
|
361
|
-
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
362
|
-
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
363
|
-
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
364
|
-
const phaseDir = node_path_1.default.join(phasesDirPath, match);
|
|
365
|
-
const phaseFiles = await node_fs_1.promises.readdir(phaseDir);
|
|
366
|
-
const plans = phaseFiles.filter(core_js_1.isPlanFile).sort();
|
|
367
|
-
const summaries = phaseFiles.filter(core_js_1.isSummaryFile).sort();
|
|
368
|
-
const result = {
|
|
369
|
-
found: true,
|
|
370
|
-
directory: node_path_1.default.join('.planning', 'phases', match),
|
|
371
|
-
phase_number: phaseNumber,
|
|
372
|
-
phase_name: phaseName,
|
|
373
|
-
plans,
|
|
374
|
-
summaries,
|
|
375
|
-
};
|
|
376
|
-
return (0, types_js_1.cmdOk)(result, result.directory);
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
return (0, types_js_1.cmdOk)(notFound, '');
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
// ─── Phase plan index ───────────────────────────────────────────────────────
|
|
383
|
-
async function cmdPhasePlanIndex(cwd, phase) {
|
|
384
|
-
if (!phase) {
|
|
385
|
-
return (0, types_js_1.cmdErr)('phase required for phase-plan-index');
|
|
386
|
-
}
|
|
387
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
388
|
-
const normalized = (0, core_js_1.normalizePhaseName)(phase);
|
|
389
|
-
let phaseDir = null;
|
|
390
|
-
let phaseDirName = null;
|
|
391
|
-
try {
|
|
392
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
393
|
-
const match = dirs.find(d => d.startsWith(normalized));
|
|
394
|
-
if (match) {
|
|
395
|
-
phaseDir = node_path_1.default.join(phasesDirPath, match);
|
|
396
|
-
phaseDirName = match;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
catch (e) {
|
|
400
|
-
(0, core_js_1.debugLog)('phase-plan-index-failed', e);
|
|
401
|
-
}
|
|
402
|
-
if (!phaseDir) {
|
|
403
|
-
return (0, types_js_1.cmdOk)({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], has_checkpoints: false });
|
|
404
|
-
}
|
|
405
|
-
const phaseFiles = await node_fs_1.promises.readdir(phaseDir);
|
|
406
|
-
const planFiles = phaseFiles.filter(core_js_1.isPlanFile).sort();
|
|
407
|
-
const summaryFiles = phaseFiles.filter(core_js_1.isSummaryFile);
|
|
408
|
-
const completedPlanIds = new Set(summaryFiles.map(core_js_1.summaryId));
|
|
409
|
-
const plans = [];
|
|
410
|
-
const waves = {};
|
|
411
|
-
const incomplete = [];
|
|
412
|
-
let hasCheckpoints = false;
|
|
413
|
-
// Read all plan files in parallel since each read is independent
|
|
414
|
-
const planContents = await Promise.all(planFiles.map(planFile => node_fs_1.promises.readFile(node_path_1.default.join(phaseDir, planFile), 'utf-8')));
|
|
415
|
-
for (let i = 0; i < planFiles.length; i++) {
|
|
416
|
-
const planFile = planFiles[i];
|
|
417
|
-
const id = (0, core_js_1.planId)(planFile);
|
|
418
|
-
const content = planContents[i];
|
|
419
|
-
const fm = (0, frontmatter_js_1.extractFrontmatter)(content);
|
|
420
|
-
const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
421
|
-
const taskCount = taskMatches.length;
|
|
422
|
-
const wave = parseInt(fm.wave, 10) || 1;
|
|
423
|
-
let autonomous = true;
|
|
424
|
-
if (fm.autonomous !== undefined) {
|
|
425
|
-
autonomous = fm.autonomous === 'true' || fm.autonomous === true;
|
|
426
|
-
}
|
|
427
|
-
if (!autonomous) {
|
|
428
|
-
hasCheckpoints = true;
|
|
429
|
-
}
|
|
430
|
-
let filesModified = [];
|
|
431
|
-
if (fm['files-modified']) {
|
|
432
|
-
filesModified = Array.isArray(fm['files-modified']) ? fm['files-modified'] : [fm['files-modified']];
|
|
433
|
-
}
|
|
434
|
-
const hasSummary = completedPlanIds.has(id);
|
|
435
|
-
if (!hasSummary) {
|
|
436
|
-
incomplete.push(id);
|
|
437
|
-
}
|
|
438
|
-
const plan = {
|
|
439
|
-
id,
|
|
440
|
-
wave,
|
|
441
|
-
autonomous,
|
|
442
|
-
objective: fm.objective || null,
|
|
443
|
-
files_modified: filesModified,
|
|
444
|
-
task_count: taskCount,
|
|
445
|
-
has_summary: hasSummary,
|
|
446
|
-
};
|
|
447
|
-
plans.push(plan);
|
|
448
|
-
const waveKey = String(wave);
|
|
449
|
-
if (!waves[waveKey]) {
|
|
450
|
-
waves[waveKey] = [];
|
|
451
|
-
}
|
|
452
|
-
waves[waveKey].push(id);
|
|
453
|
-
}
|
|
454
|
-
return (0, types_js_1.cmdOk)({ phase: normalized, plans, waves, incomplete, has_checkpoints: hasCheckpoints });
|
|
455
|
-
}
|
|
456
|
-
// ─── Phase add ──────────────────────────────────────────────────────────────
|
|
457
|
-
async function cmdPhaseAdd(cwd, description) {
|
|
458
|
-
if (!description) {
|
|
459
|
-
return (0, types_js_1.cmdErr)('description required for phase add');
|
|
460
|
-
}
|
|
461
|
-
try {
|
|
462
|
-
const result = await phaseAddCore(cwd, description, { includeStubs: false });
|
|
463
|
-
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);
|
|
464
|
-
}
|
|
465
|
-
catch (e) {
|
|
466
|
-
return (0, types_js_1.cmdErr)(e.message);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
// ─── Phase insert ───────────────────────────────────────────────────────────
|
|
470
|
-
async function cmdPhaseInsert(cwd, afterPhase, description) {
|
|
471
|
-
if (!afterPhase || !description) {
|
|
472
|
-
return (0, types_js_1.cmdErr)('after-phase and description required for phase insert');
|
|
473
|
-
}
|
|
474
|
-
try {
|
|
475
|
-
const result = await phaseInsertCore(cwd, afterPhase, description, { includeStubs: false });
|
|
476
|
-
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);
|
|
477
|
-
}
|
|
478
|
-
catch (e) {
|
|
479
|
-
return (0, types_js_1.cmdErr)(e.message);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
// ─── Phase remove ───────────────────────────────────────────────────────────
|
|
483
|
-
async function cmdPhaseRemove(cwd, targetPhase, options) {
|
|
484
|
-
if (!targetPhase) {
|
|
485
|
-
return (0, types_js_1.cmdErr)('phase number required for phase remove');
|
|
486
|
-
}
|
|
487
|
-
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
488
|
-
const phasesDirPath = (0, core_js_1.phasesPath)(cwd);
|
|
489
|
-
const force = options.force || false;
|
|
490
|
-
if (!(await (0, core_js_1.pathExistsAsync)(rmPath))) {
|
|
491
|
-
return (0, types_js_1.cmdErr)('ROADMAP.md not found');
|
|
492
|
-
}
|
|
493
|
-
const normalized = (0, core_js_1.normalizePhaseName)(targetPhase);
|
|
494
|
-
const isDecimal = targetPhase.includes('.');
|
|
495
|
-
let targetDir = null;
|
|
496
|
-
try {
|
|
497
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
498
|
-
targetDir = dirs.find(d => d.startsWith(normalized + '-') || d === normalized) || null;
|
|
499
|
-
}
|
|
500
|
-
catch (e) {
|
|
501
|
-
(0, core_js_1.debugLog)('phase-remove-find-target-failed', e);
|
|
502
|
-
}
|
|
503
|
-
if (targetDir && !force) {
|
|
504
|
-
const targetPath = node_path_1.default.join(phasesDirPath, targetDir);
|
|
505
|
-
const files = await node_fs_1.promises.readdir(targetPath);
|
|
506
|
-
const summaries = files.filter(core_js_1.isSummaryFile);
|
|
507
|
-
if (summaries.length > 0) {
|
|
508
|
-
return (0, types_js_1.cmdErr)(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
if (targetDir) {
|
|
512
|
-
await node_fs_1.promises.rm(node_path_1.default.join(phasesDirPath, targetDir), { recursive: true, force: true });
|
|
513
|
-
}
|
|
514
|
-
const renamedDirs = [];
|
|
515
|
-
const renamedFiles = [];
|
|
516
|
-
if (isDecimal) {
|
|
517
|
-
const baseParts = normalized.split('.');
|
|
518
|
-
const baseInt = baseParts[0];
|
|
519
|
-
const removedDecimal = parseInt(baseParts[1], 10);
|
|
520
|
-
try {
|
|
521
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
522
|
-
const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
|
|
523
|
-
const toRename = [];
|
|
524
|
-
for (const dir of dirs) {
|
|
525
|
-
const dm = dir.match(decPattern);
|
|
526
|
-
if (dm && parseInt(dm[1], 10) > removedDecimal) {
|
|
527
|
-
toRename.push({ dir, oldDecimal: parseInt(dm[1], 10), slug: dm[2] });
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
toRename.sort((a, b) => b.oldDecimal - a.oldDecimal);
|
|
531
|
-
// Sequential renames — order matters
|
|
532
|
-
for (const item of toRename) {
|
|
533
|
-
const newDecimal = item.oldDecimal - 1;
|
|
534
|
-
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
535
|
-
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
536
|
-
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
537
|
-
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, item.dir), node_path_1.default.join(phasesDirPath, newDirName));
|
|
538
|
-
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
539
|
-
const dirFiles = await node_fs_1.promises.readdir(node_path_1.default.join(phasesDirPath, newDirName));
|
|
540
|
-
for (const f of dirFiles) {
|
|
541
|
-
if (f.includes(oldPhaseId)) {
|
|
542
|
-
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
543
|
-
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, newDirName, f), node_path_1.default.join(phasesDirPath, newDirName, newFileName));
|
|
544
|
-
renamedFiles.push({ from: f, to: newFileName });
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
catch (e) {
|
|
550
|
-
(0, core_js_1.debugLog)('phase-remove-decimal-rename-failed', { phase: targetPhase, error: (0, core_js_1.errorMsg)(e) });
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
const removedInt = parseInt(normalized, 10);
|
|
555
|
-
try {
|
|
556
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(phasesDirPath, true);
|
|
557
|
-
const toRename = [];
|
|
558
|
-
for (const dir of dirs) {
|
|
559
|
-
const dm = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
|
560
|
-
if (!dm)
|
|
561
|
-
continue;
|
|
562
|
-
const dirInt = parseInt(dm[1], 10);
|
|
563
|
-
if (dirInt > removedInt) {
|
|
564
|
-
toRename.push({
|
|
565
|
-
dir,
|
|
566
|
-
oldInt: dirInt,
|
|
567
|
-
letter: dm[2] ? dm[2].toUpperCase() : '',
|
|
568
|
-
decimal: dm[3] ? parseInt(dm[3], 10) : null,
|
|
569
|
-
slug: dm[4],
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
toRename.sort((a, b) => {
|
|
574
|
-
if (a.oldInt !== b.oldInt)
|
|
575
|
-
return b.oldInt - a.oldInt;
|
|
576
|
-
return (b.decimal || 0) - (a.decimal || 0);
|
|
577
|
-
});
|
|
578
|
-
// Sequential renames — order matters
|
|
579
|
-
for (const item of toRename) {
|
|
580
|
-
const newInt = item.oldInt - 1;
|
|
581
|
-
const newPadded = String(newInt).padStart(2, '0');
|
|
582
|
-
const oldPadded = String(item.oldInt).padStart(2, '0');
|
|
583
|
-
const letterSuffix = item.letter || '';
|
|
584
|
-
const decimalSuffix = item.decimal !== null ? `.${item.decimal}` : '';
|
|
585
|
-
const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
|
|
586
|
-
const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
|
|
587
|
-
const newDirName = `${newPrefix}-${item.slug}`;
|
|
588
|
-
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, item.dir), node_path_1.default.join(phasesDirPath, newDirName));
|
|
589
|
-
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
590
|
-
const dirFiles = await node_fs_1.promises.readdir(node_path_1.default.join(phasesDirPath, newDirName));
|
|
591
|
-
for (const f of dirFiles) {
|
|
592
|
-
if (f.startsWith(oldPrefix)) {
|
|
593
|
-
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
594
|
-
await node_fs_1.promises.rename(node_path_1.default.join(phasesDirPath, newDirName, f), node_path_1.default.join(phasesDirPath, newDirName, newFileName));
|
|
595
|
-
renamedFiles.push({ from: f, to: newFileName });
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
catch (e) {
|
|
601
|
-
(0, core_js_1.debugLog)('phase-remove-int-rename-failed', { phase: targetPhase, error: (0, core_js_1.errorMsg)(e) });
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
// Update ROADMAP.md
|
|
605
|
-
let roadmapContent = await node_fs_1.promises.readFile(rmPath, 'utf-8');
|
|
606
|
-
const targetEscaped = (0, core_js_1.escapePhaseNum)(targetPhase);
|
|
607
|
-
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`, 'i');
|
|
608
|
-
roadmapContent = roadmapContent.replace(sectionPattern, '');
|
|
609
|
-
const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`, 'gi');
|
|
610
|
-
roadmapContent = roadmapContent.replace(checkboxPattern, '');
|
|
611
|
-
const tableRowPattern = new RegExp(`\\n?\\|\\s*${targetEscaped}\\.?\\s[^|]*\\|[^\\n]*`, 'gi');
|
|
612
|
-
roadmapContent = roadmapContent.replace(tableRowPattern, '');
|
|
613
|
-
if (!isDecimal) {
|
|
614
|
-
const removedInt = parseInt(normalized, 10);
|
|
615
|
-
const maxPhase = 99;
|
|
616
|
-
for (let oldNum = maxPhase; oldNum > removedInt; oldNum--) {
|
|
617
|
-
const newNum = oldNum - 1;
|
|
618
|
-
const oldStr = String(oldNum);
|
|
619
|
-
const newStr = String(newNum);
|
|
620
|
-
const oldPad = oldStr.padStart(2, '0');
|
|
621
|
-
const newPad = newStr.padStart(2, '0');
|
|
622
|
-
roadmapContent = roadmapContent.replace(new RegExp(`(#{2,4}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'), `$1${newStr}$2`);
|
|
623
|
-
roadmapContent = roadmapContent.replace(new RegExp(`(Phase\\s+)${oldStr}([:\\s])`, 'g'), `$1${newStr}$2`);
|
|
624
|
-
roadmapContent = roadmapContent.replace(new RegExp(`${oldPad}-(\\d{2})`, 'g'), `${newPad}-$1`);
|
|
625
|
-
roadmapContent = roadmapContent.replace(new RegExp(`(\\|\\s*)${oldStr}\\.\\s`, 'g'), `$1${newStr}. `);
|
|
626
|
-
roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, 'gi'), `$1${newStr}`);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
await node_fs_1.promises.writeFile(rmPath, roadmapContent, 'utf-8');
|
|
630
|
-
// Update STATE.md phase count
|
|
631
|
-
const stPath = (0, core_js_1.statePath)(cwd);
|
|
632
|
-
const stExists = await (0, core_js_1.pathExistsAsync)(stPath);
|
|
633
|
-
if (stExists) {
|
|
634
|
-
let stateContent = await node_fs_1.promises.readFile(stPath, 'utf-8');
|
|
635
|
-
const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
|
|
636
|
-
const totalMatch = stateContent.match(totalPattern);
|
|
637
|
-
if (totalMatch) {
|
|
638
|
-
const oldTotal = parseInt(totalMatch[2], 10);
|
|
639
|
-
stateContent = stateContent.replace(totalPattern, `$1${oldTotal - 1}`);
|
|
640
|
-
}
|
|
641
|
-
const ofPattern = /(\bof\s+)(\d+)(\s*(?:\(|phases?))/i;
|
|
642
|
-
const ofMatch = stateContent.match(ofPattern);
|
|
643
|
-
if (ofMatch) {
|
|
644
|
-
const oldTotal = parseInt(ofMatch[2], 10);
|
|
645
|
-
stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
|
|
646
|
-
}
|
|
647
|
-
await node_fs_1.promises.writeFile(stPath, stateContent, 'utf-8');
|
|
648
|
-
}
|
|
649
|
-
return (0, types_js_1.cmdOk)({
|
|
650
|
-
removed: targetPhase,
|
|
651
|
-
directory_deleted: targetDir || null,
|
|
652
|
-
renamed_directories: renamedDirs,
|
|
653
|
-
renamed_files: renamedFiles,
|
|
654
|
-
roadmap_updated: true,
|
|
655
|
-
state_updated: stExists,
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
// ─── Phase complete ─────────────────────────────────────────────────────────
|
|
659
|
-
async function cmdPhaseComplete(cwd, phaseNum) {
|
|
660
|
-
if (!phaseNum) {
|
|
661
|
-
return (0, types_js_1.cmdErr)('phase number required for phase complete');
|
|
662
|
-
}
|
|
663
|
-
try {
|
|
664
|
-
const result = await phaseCompleteCore(cwd, phaseNum);
|
|
665
|
-
return (0, types_js_1.cmdOk)({
|
|
666
|
-
completed_phase: result.completed_phase,
|
|
667
|
-
phase_name: result.phase_name,
|
|
668
|
-
plans_executed: result.plans_executed,
|
|
669
|
-
next_phase: result.next_phase,
|
|
670
|
-
next_phase_name: result.next_phase_name,
|
|
671
|
-
is_last_phase: result.is_last_phase,
|
|
672
|
-
date: result.date,
|
|
673
|
-
roadmap_updated: result.roadmap_updated,
|
|
674
|
-
state_updated: result.state_updated,
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
catch (e) {
|
|
678
|
-
return (0, types_js_1.cmdErr)(e.message);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
// ─── Phase archive ──────────────────────────────────────────────────────────
|
|
682
|
-
/**
|
|
683
|
-
* Scan STATE.md for lines matching a phase-tagged pattern in a specific section.
|
|
684
|
-
*/
|
|
685
|
-
function findPhaseTaggedLines(content, sectionPattern, phaseNum) {
|
|
686
|
-
const match = content.match(sectionPattern);
|
|
687
|
-
if (!match || !match[2])
|
|
688
|
-
return [];
|
|
689
|
-
const escaped = (0, core_js_1.escapePhaseNum)(phaseNum);
|
|
690
|
-
const tagPattern = new RegExp(`^\\s*-\\s*\\[Phase\\s+${escaped}\\]`, 'i');
|
|
691
|
-
return match[2].split('\n').filter(line => tagPattern.test(line));
|
|
692
|
-
}
|
|
693
|
-
const DECISIONS_SECTION_PATTERN = /(#{2,3}\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n#{2,3}\s|\n##[^#]|$)/i;
|
|
694
|
-
const BLOCKERS_SECTION_PATTERN = /(#{2,3}\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n\s*\n?)([\s\S]*?)(?=\n#{2,3}\s|$)/i;
|
|
695
|
-
async function archivePhasePreview(cwd, phaseNum, outcomeSummary) {
|
|
696
|
-
const phaseInfo = await (0, core_js_1.findPhaseInternalAsync)(cwd, phaseNum);
|
|
697
|
-
if (!phaseInfo) {
|
|
698
|
-
return (0, types_js_1.cmdErr)(`Phase ${phaseNum} not found`);
|
|
699
|
-
}
|
|
700
|
-
const archiveDir = await (0, core_js_1.archivePathAsync)(cwd);
|
|
701
|
-
const phaseDirName = node_path_1.default.basename(phaseInfo.directory);
|
|
702
|
-
const archiveDest = node_path_1.default.join(archiveDir, phaseDirName);
|
|
703
|
-
// Read STATE.md
|
|
704
|
-
const stContent = await (0, core_js_1.safeReadFileAsync)((0, core_js_1.statePath)(cwd)) ?? '';
|
|
705
|
-
const decisionsToRemove = findPhaseTaggedLines(stContent, DECISIONS_SECTION_PATTERN, phaseNum);
|
|
706
|
-
const blockersToRemove = findPhaseTaggedLines(stContent, BLOCKERS_SECTION_PATTERN, phaseNum);
|
|
707
|
-
// Read ROADMAP.md section
|
|
708
|
-
const rmContent = await (0, core_js_1.safeReadFileAsync)((0, core_js_1.roadmapPath)(cwd)) ?? '';
|
|
709
|
-
const escaped = (0, core_js_1.escapePhaseNum)(phaseNum);
|
|
710
|
-
const sectionPattern = new RegExp(`#{2,4}\\s*Phase\\s+${escaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|\\n## |$)`, 'i');
|
|
711
|
-
const sectionMatch = rmContent.match(sectionPattern);
|
|
712
|
-
const sectionToCollapse = sectionMatch ? sectionMatch[0].trim() : '';
|
|
713
|
-
const phaseName = phaseInfo.phase_name ? phaseInfo.phase_name.replace(/-/g, ' ') : `Phase ${phaseNum}`;
|
|
714
|
-
const collapsedLine = `- [x] Phase ${phaseNum}: ${phaseName} -- ${outcomeSummary}`;
|
|
715
|
-
const preview = {
|
|
716
|
-
phase_dir: phaseInfo.directory,
|
|
717
|
-
archive_dir: node_path_1.default.relative(cwd, archiveDest).replace(/\\/g, '/'),
|
|
718
|
-
decisions_to_prune: decisionsToRemove,
|
|
719
|
-
blockers_to_prune: blockersToRemove,
|
|
720
|
-
roadmap_section_to_collapse: sectionToCollapse,
|
|
721
|
-
collapsed_line: collapsedLine,
|
|
722
|
-
};
|
|
723
|
-
return (0, types_js_1.cmdOk)(preview);
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Prune phase-tagged lines from a section in STATE.md content.
|
|
727
|
-
*/
|
|
728
|
-
function pruneSection(content, sectionPattern, phaseNum) {
|
|
729
|
-
const match = content.match(sectionPattern);
|
|
730
|
-
if (!match || !match[2])
|
|
731
|
-
return content;
|
|
732
|
-
const escaped = (0, core_js_1.escapePhaseNum)(phaseNum);
|
|
733
|
-
const tagPattern = new RegExp(`^\\s*-\\s*\\[Phase\\s+${escaped}\\]`, 'i');
|
|
734
|
-
const lines = match[2].split('\n');
|
|
735
|
-
const filtered = lines.filter(line => !tagPattern.test(line));
|
|
736
|
-
let newBody = filtered.join('\n');
|
|
737
|
-
// Check if remaining body has any bullet content
|
|
738
|
-
if (!newBody.trim() || !/^\s*[-*]\s+/m.test(newBody)) {
|
|
739
|
-
newBody = '\nNone.\n';
|
|
740
|
-
}
|
|
741
|
-
return content.replace(sectionPattern, (_m, header) => `${header}${newBody}`);
|
|
742
|
-
}
|
|
743
|
-
async function archivePhaseExecute(cwd, phaseNum, outcomeSummary) {
|
|
744
|
-
// Re-read all files fresh (pitfall #6)
|
|
745
|
-
const phaseInfo = await (0, core_js_1.findPhaseInternalAsync)(cwd, phaseNum);
|
|
746
|
-
if (!phaseInfo) {
|
|
747
|
-
return (0, types_js_1.cmdErr)(`Phase ${phaseNum} not found`);
|
|
748
|
-
}
|
|
749
|
-
const archiveDir = await (0, core_js_1.archivePathAsync)(cwd);
|
|
750
|
-
const phaseDirName = node_path_1.default.basename(phaseInfo.directory);
|
|
751
|
-
const archiveDest = node_path_1.default.join(archiveDir, phaseDirName);
|
|
752
|
-
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo.directory);
|
|
753
|
-
// 1. Create archive directory
|
|
754
|
-
await node_fs_1.promises.mkdir(archiveDir, { recursive: true });
|
|
755
|
-
// 2. Move phase directory (with EXDEV fallback)
|
|
756
|
-
try {
|
|
757
|
-
await node_fs_1.promises.rename(phaseDirFull, archiveDest);
|
|
758
|
-
}
|
|
759
|
-
catch (e) {
|
|
760
|
-
if (e.code === 'EXDEV') {
|
|
761
|
-
(0, core_js_1.debugLog)('archive-rename-exdev', 'falling back to copy+delete');
|
|
762
|
-
await node_fs_1.promises.cp(phaseDirFull, archiveDest, { recursive: true });
|
|
763
|
-
await node_fs_1.promises.rm(phaseDirFull, { recursive: true, force: true });
|
|
764
|
-
}
|
|
765
|
-
else {
|
|
766
|
-
throw e;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
// 3. Prune STATE.md
|
|
770
|
-
const stPath = (0, core_js_1.statePath)(cwd);
|
|
771
|
-
let stContent = await (0, core_js_1.safeReadFileAsync)(stPath);
|
|
772
|
-
if (stContent) {
|
|
773
|
-
stContent = pruneSection(stContent, DECISIONS_SECTION_PATTERN, phaseNum);
|
|
774
|
-
stContent = pruneSection(stContent, BLOCKERS_SECTION_PATTERN, phaseNum);
|
|
775
|
-
await node_fs_1.promises.writeFile(stPath, stContent, 'utf-8');
|
|
776
|
-
}
|
|
777
|
-
// 4. Collapse ROADMAP.md
|
|
778
|
-
const rmPath = (0, core_js_1.roadmapPath)(cwd);
|
|
779
|
-
let rmContent = await (0, core_js_1.safeReadFileAsync)(rmPath);
|
|
780
|
-
if (rmContent) {
|
|
781
|
-
const escaped = (0, core_js_1.escapePhaseNum)(phaseNum);
|
|
782
|
-
// Remove detail section
|
|
783
|
-
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${escaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|\\n## |$)`, 'i');
|
|
784
|
-
rmContent = rmContent.replace(sectionPattern, '');
|
|
785
|
-
// Update checklist line — handle both bold and plain formats
|
|
786
|
-
const phaseName = phaseInfo.phase_name ? phaseInfo.phase_name.replace(/-/g, ' ') : `Phase ${phaseNum}`;
|
|
787
|
-
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${escaped}[:\\s][^\\n]*`, 'i');
|
|
788
|
-
rmContent = rmContent.replace(checklistPattern, `- [x] Phase ${phaseNum}: ${phaseName} -- ${outcomeSummary}`);
|
|
789
|
-
await node_fs_1.promises.writeFile(rmPath, rmContent, 'utf-8');
|
|
790
|
-
}
|
|
791
|
-
// 5. Stage and commit
|
|
792
|
-
const filesToStage = [
|
|
793
|
-
phaseInfo.directory,
|
|
794
|
-
node_path_1.default.relative(cwd, archiveDest).replace(/\\/g, '/'),
|
|
795
|
-
'.planning/STATE.md',
|
|
796
|
-
'.planning/ROADMAP.md',
|
|
797
|
-
];
|
|
798
|
-
await (0, core_js_1.execGit)(cwd, ['add', ...filesToStage]);
|
|
799
|
-
await (0, core_js_1.execGit)(cwd, ['commit', '-m', `chore(phase-${phaseNum}): archive completed phase`]);
|
|
800
|
-
return (0, types_js_1.cmdOk)({
|
|
801
|
-
archived: true,
|
|
802
|
-
phase: phaseNum,
|
|
803
|
-
archive_path: node_path_1.default.relative(cwd, archiveDest).replace(/\\/g, '/'),
|
|
804
|
-
decisions_pruned: findPhaseTaggedLines(await (0, core_js_1.safeReadFileAsync)((0, core_js_1.statePath)(cwd)) ?? '', DECISIONS_SECTION_PATTERN, phaseNum).length === 0,
|
|
805
|
-
blockers_pruned: true,
|
|
806
|
-
roadmap_collapsed: true,
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
// ─── Get archived phase ─────────────────────────────────────────────────────
|
|
810
|
-
async function cmdGetArchivedPhase(cwd, phaseNum) {
|
|
811
|
-
if (!phaseNum) {
|
|
812
|
-
return (0, types_js_1.cmdErr)('phase number required');
|
|
813
|
-
}
|
|
814
|
-
const normalized = (0, core_js_1.normalizePhaseName)(phaseNum);
|
|
815
|
-
// Search .planning/archive/
|
|
816
|
-
const archiveDir = (0, core_js_1.planningPath)(cwd, 'archive');
|
|
817
|
-
const found = await searchArchiveLocations(archiveDir, normalized);
|
|
818
|
-
if (found)
|
|
819
|
-
return (0, types_js_1.cmdOk)(found);
|
|
820
|
-
// Search legacy .planning/milestones/
|
|
821
|
-
const milestonesDir = (0, core_js_1.planningPath)(cwd, 'milestones');
|
|
822
|
-
if (await (0, core_js_1.pathExistsAsync)(milestonesDir)) {
|
|
823
|
-
try {
|
|
824
|
-
const entries = await node_fs_1.promises.readdir(milestonesDir, { withFileTypes: true });
|
|
825
|
-
const phaseDirs = entries
|
|
826
|
-
.filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
|
|
827
|
-
.map(e => e.name)
|
|
828
|
-
.sort()
|
|
829
|
-
.reverse();
|
|
830
|
-
for (const archiveName of phaseDirs) {
|
|
831
|
-
const archPath = node_path_1.default.join(milestonesDir, archiveName);
|
|
832
|
-
const result = await searchForPhaseInDir(archPath, normalized, archiveName);
|
|
833
|
-
if (result)
|
|
834
|
-
return (0, types_js_1.cmdOk)(result);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
catch (e) {
|
|
838
|
-
(0, core_js_1.debugLog)('get-archived-phase-milestones-failed', e);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
return (0, types_js_1.cmdErr)(`Phase ${phaseNum} not found in archive`);
|
|
842
|
-
}
|
|
843
|
-
async function searchArchiveLocations(archiveDir, normalized) {
|
|
844
|
-
if (!(await (0, core_js_1.pathExistsAsync)(archiveDir)))
|
|
845
|
-
return null;
|
|
846
|
-
try {
|
|
847
|
-
const entries = await node_fs_1.promises.readdir(archiveDir, { withFileTypes: true });
|
|
848
|
-
const versionDirs = entries
|
|
849
|
-
.filter(e => e.isDirectory())
|
|
850
|
-
.map(e => e.name)
|
|
851
|
-
.sort()
|
|
852
|
-
.reverse();
|
|
853
|
-
for (const versionName of versionDirs) {
|
|
854
|
-
const versionPath = node_path_1.default.join(archiveDir, versionName);
|
|
855
|
-
const result = await searchForPhaseInDir(versionPath, normalized, versionName);
|
|
856
|
-
if (result)
|
|
857
|
-
return result;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
catch (e) {
|
|
861
|
-
(0, core_js_1.debugLog)('search-archive-locations-failed', e);
|
|
862
|
-
}
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
async function searchForPhaseInDir(baseDir, normalized, milestone) {
|
|
866
|
-
try {
|
|
867
|
-
const dirs = await (0, core_js_1.listSubDirsAsync)(baseDir, true);
|
|
868
|
-
const match = dirs.find(d => d.startsWith(normalized));
|
|
869
|
-
if (!match)
|
|
870
|
-
return null;
|
|
871
|
-
const phaseDir = node_path_1.default.join(baseDir, match);
|
|
872
|
-
const files = await node_fs_1.promises.readdir(phaseDir);
|
|
873
|
-
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
874
|
-
const contents = {};
|
|
875
|
-
for (const f of mdFiles) {
|
|
876
|
-
contents[f] = await node_fs_1.promises.readFile(node_path_1.default.join(phaseDir, f), 'utf-8');
|
|
877
|
-
}
|
|
878
|
-
return {
|
|
879
|
-
phase: normalized,
|
|
880
|
-
milestone,
|
|
881
|
-
directory: match,
|
|
882
|
-
files: mdFiles,
|
|
883
|
-
contents,
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
catch (e) {
|
|
887
|
-
(0, core_js_1.debugLog)('search-for-phase-in-dir-failed', e);
|
|
888
|
-
return null;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
//# sourceMappingURL=phase.js.map
|