maxsimcli 4.2.3 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/assets/CHANGELOG.md +7 -0
- package/dist/assets/templates/agents/AGENTS.md +8 -6
- package/dist/assets/templates/agents/maxsim-code-reviewer.md +11 -0
- package/dist/assets/templates/agents/maxsim-executor.md +1 -0
- package/dist/assets/templates/agents/maxsim-planner.md +1 -0
- package/dist/assets/templates/agents/maxsim-project-researcher.md +96 -0
- package/dist/assets/templates/agents/maxsim-research-synthesizer.md +55 -3
- package/dist/assets/templates/agents/maxsim-roadmapper.md +11 -0
- package/dist/assets/templates/references/questioning.md +184 -33
- package/dist/assets/templates/skills/code-review/SKILL.md +5 -3
- package/dist/assets/templates/skills/{batch-worktree → maxsim-batch}/SKILL.md +3 -3
- package/dist/assets/templates/skills/{simplify → maxsim-simplify}/SKILL.md +7 -6
- package/dist/assets/templates/skills/using-maxsim/SKILL.md +4 -2
- package/dist/assets/templates/templates/conventions.md +138 -0
- package/dist/assets/templates/templates/no-gos.md +45 -4
- package/dist/assets/templates/templates/project.md +23 -0
- package/dist/assets/templates/workflows/batch.md +1 -1
- package/dist/assets/templates/workflows/init-existing.md +187 -0
- package/dist/assets/templates/workflows/new-project.md +195 -7
- package/dist/backend-server.cjs +13 -0
- package/dist/backend-server.cjs.map +1 -1
- package/dist/cli.cjs +350 -40
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/core.d.ts +2 -0
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.js +67 -6
- package/dist/core/core.js.map +1 -1
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +6 -0
- package/dist/core/init.js.map +1 -1
- package/dist/core/milestone.d.ts +1 -1
- package/dist/core/milestone.d.ts.map +1 -1
- package/dist/core/milestone.js +81 -37
- package/dist/core/milestone.js.map +1 -1
- package/dist/core/phase.d.ts +3 -0
- package/dist/core/phase.d.ts.map +1 -1
- package/dist/core/phase.js +213 -0
- package/dist/core/phase.js.map +1 -1
- package/dist/core/state.d.ts +6 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +69 -0
- package/dist/core/state.js.map +1 -1
- package/dist/core/types.d.ts +11 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core-RRjCSt0G.cjs.map +1 -1
- package/dist/install/shared.d.ts +1 -1
- package/dist/install/shared.d.ts.map +1 -1
- package/dist/install/shared.js +1 -1
- package/dist/install/shared.js.map +1 -1
- package/dist/install.cjs +4 -2
- package/dist/install.cjs.map +1 -1
- package/dist/mcp-server.cjs +13 -0
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/skills-MYlMkYNt.cjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -5205,6 +5205,19 @@ async function findPhaseInternalAsync(cwd, phase) {
|
|
|
5205
5205
|
const normalized = normalizePhaseName(phase);
|
|
5206
5206
|
const current = await searchPhaseInDirAsync(pd, node_path.default.join(".planning", "phases"), normalized);
|
|
5207
5207
|
if (current) return current;
|
|
5208
|
+
const archiveDir = planningPath(cwd, "archive");
|
|
5209
|
+
if (await pathExistsAsync(archiveDir)) try {
|
|
5210
|
+
const versionDirs = (await node_fs.promises.readdir(archiveDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
5211
|
+
for (const versionName of versionDirs) {
|
|
5212
|
+
const result = await searchPhaseInDirAsync(node_path.default.join(archiveDir, versionName), node_path.default.join(".planning", "archive", versionName), normalized);
|
|
5213
|
+
if (result) {
|
|
5214
|
+
result.archived = versionName;
|
|
5215
|
+
return result;
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
} catch (e) {
|
|
5219
|
+
debugLog("find-phase-async-archive-search-failed", e);
|
|
5220
|
+
}
|
|
5208
5221
|
const milestonesDir = planningPath(cwd, "milestones");
|
|
5209
5222
|
if (!await pathExistsAsync(milestonesDir)) return null;
|
|
5210
5223
|
try {
|
|
@@ -5225,21 +5238,37 @@ async function findPhaseInternalAsync(cwd, phase) {
|
|
|
5225
5238
|
return null;
|
|
5226
5239
|
}
|
|
5227
5240
|
async function getArchivedPhaseDirsAsync(cwd) {
|
|
5228
|
-
const milestonesDir = planningPath(cwd, "milestones");
|
|
5229
5241
|
const results = [];
|
|
5242
|
+
const archiveDir = planningPath(cwd, "archive");
|
|
5243
|
+
try {
|
|
5244
|
+
const versionDirs = (await node_fs.promises.readdir(archiveDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
5245
|
+
for (const versionName of versionDirs) {
|
|
5246
|
+
const versionPath = node_path.default.join(archiveDir, versionName);
|
|
5247
|
+
const dirs = await listSubDirsAsync(versionPath, true);
|
|
5248
|
+
for (const dir of dirs) results.push({
|
|
5249
|
+
name: dir,
|
|
5250
|
+
milestone: versionName,
|
|
5251
|
+
basePath: node_path.default.join(".planning", "archive", versionName),
|
|
5252
|
+
fullPath: node_path.default.join(versionPath, dir)
|
|
5253
|
+
});
|
|
5254
|
+
}
|
|
5255
|
+
} catch (e) {
|
|
5256
|
+
debugLog("get-archived-phase-dirs-async-archive-failed", e);
|
|
5257
|
+
}
|
|
5258
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
5230
5259
|
try {
|
|
5231
5260
|
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
5261
|
for (const archiveName of phaseDirs) {
|
|
5233
5262
|
const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
|
|
5234
5263
|
if (!versionMatch) continue;
|
|
5235
5264
|
const version = versionMatch[1];
|
|
5236
|
-
const
|
|
5237
|
-
const dirs = await listSubDirsAsync(
|
|
5265
|
+
const archiveMilestonePath = node_path.default.join(milestonesDir, archiveName);
|
|
5266
|
+
const dirs = await listSubDirsAsync(archiveMilestonePath, true);
|
|
5238
5267
|
for (const dir of dirs) results.push({
|
|
5239
5268
|
name: dir,
|
|
5240
5269
|
milestone: version,
|
|
5241
5270
|
basePath: node_path.default.join(".planning", "milestones", archiveName),
|
|
5242
|
-
fullPath: node_path.default.join(
|
|
5271
|
+
fullPath: node_path.default.join(archiveMilestonePath, dir)
|
|
5243
5272
|
});
|
|
5244
5273
|
}
|
|
5245
5274
|
} catch (e) {
|
|
@@ -5247,6 +5276,32 @@ async function getArchivedPhaseDirsAsync(cwd) {
|
|
|
5247
5276
|
}
|
|
5248
5277
|
return results;
|
|
5249
5278
|
}
|
|
5279
|
+
function archivePath(cwd, milestone) {
|
|
5280
|
+
return planningPath(cwd, "archive", milestone ?? getMilestoneInfo(cwd).version);
|
|
5281
|
+
}
|
|
5282
|
+
async function archivePathAsync(cwd, milestone) {
|
|
5283
|
+
return planningPath(cwd, "archive", milestone ?? (await getMilestoneInfoAsync(cwd)).version);
|
|
5284
|
+
}
|
|
5285
|
+
async function getMilestoneInfoAsync(cwd) {
|
|
5286
|
+
try {
|
|
5287
|
+
const roadmap = await safeReadFileAsync(roadmapPath(cwd));
|
|
5288
|
+
if (!roadmap) return {
|
|
5289
|
+
version: "v1.0",
|
|
5290
|
+
name: "milestone"
|
|
5291
|
+
};
|
|
5292
|
+
const versionMatch = roadmap.match(/v(\d+\.\d+)/);
|
|
5293
|
+
const nameMatch = roadmap.match(/## .*v\d+\.\d+[:\s]+([^\n(]+)/);
|
|
5294
|
+
return {
|
|
5295
|
+
version: versionMatch ? versionMatch[0] : "v1.0",
|
|
5296
|
+
name: nameMatch ? nameMatch[1].trim() : "milestone"
|
|
5297
|
+
};
|
|
5298
|
+
} catch {
|
|
5299
|
+
return {
|
|
5300
|
+
version: "v1.0",
|
|
5301
|
+
name: "milestone"
|
|
5302
|
+
};
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5250
5305
|
|
|
5251
5306
|
//#endregion
|
|
5252
5307
|
//#region ../../node_modules/yaml/dist/nodes/identity.js
|
|
@@ -12608,6 +12663,55 @@ async function cmdStateSnapshot(cwd, raw) {
|
|
|
12608
12663
|
session
|
|
12609
12664
|
});
|
|
12610
12665
|
}
|
|
12666
|
+
async function cmdDetectStaleContext(cwd) {
|
|
12667
|
+
const rmPath = roadmapPath(cwd);
|
|
12668
|
+
const stPath = statePath(cwd);
|
|
12669
|
+
const [roadmapContent, stateContent] = await Promise.all([safeReadFileAsync(rmPath), safeReadFileAsync(stPath)]);
|
|
12670
|
+
if (!roadmapContent) return cmdErr("ROADMAP.md not found");
|
|
12671
|
+
if (!stateContent) return cmdErr("STATE.md not found");
|
|
12672
|
+
const completedPhases = [];
|
|
12673
|
+
const checkboxPattern = /^-\s*\[x\]\s*.*Phase\s+(\d+[A-Z]?(?:\.\d+)?)/gim;
|
|
12674
|
+
let match;
|
|
12675
|
+
while ((match = checkboxPattern.exec(roadmapContent)) !== null) completedPhases.push(match[1]);
|
|
12676
|
+
if (completedPhases.length === 0) return cmdOk({
|
|
12677
|
+
stale_references: [],
|
|
12678
|
+
completed_phases: [],
|
|
12679
|
+
clean: true,
|
|
12680
|
+
message: "No completed phases found in ROADMAP.md"
|
|
12681
|
+
});
|
|
12682
|
+
const staleReferences = [];
|
|
12683
|
+
for (const section of [{
|
|
12684
|
+
name: "Decisions",
|
|
12685
|
+
pattern: /(#{2,3}\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n#{2,3}\s|\n##[^#]|$)/i
|
|
12686
|
+
}, {
|
|
12687
|
+
name: "Blockers",
|
|
12688
|
+
pattern: /(#{2,3}\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n\s*\n?)([\s\S]*?)(?=\n#{2,3}\s|$)/i
|
|
12689
|
+
}]) {
|
|
12690
|
+
const sectionMatch = stateContent.match(section.pattern);
|
|
12691
|
+
if (!sectionMatch || !sectionMatch[2]) continue;
|
|
12692
|
+
const lines = sectionMatch[2].split("\n");
|
|
12693
|
+
for (const line of lines) {
|
|
12694
|
+
if (!line.trim()) continue;
|
|
12695
|
+
for (const phase of completedPhases) {
|
|
12696
|
+
const escaped = escapePhaseNum(phase);
|
|
12697
|
+
if (new RegExp(`\\bPhase\\s+${escaped}\\b`, "i").test(line)) {
|
|
12698
|
+
staleReferences.push({
|
|
12699
|
+
section: section.name,
|
|
12700
|
+
line: line.trim(),
|
|
12701
|
+
phase
|
|
12702
|
+
});
|
|
12703
|
+
break;
|
|
12704
|
+
}
|
|
12705
|
+
}
|
|
12706
|
+
}
|
|
12707
|
+
}
|
|
12708
|
+
return cmdOk({
|
|
12709
|
+
stale_references: staleReferences,
|
|
12710
|
+
completed_phases: completedPhases,
|
|
12711
|
+
clean: staleReferences.length === 0,
|
|
12712
|
+
message: staleReferences.length === 0 ? "No stale references found — STATE.md is clean" : `Found ${staleReferences.length} stale reference(s) to completed phases`
|
|
12713
|
+
});
|
|
12714
|
+
}
|
|
12611
12715
|
|
|
12612
12716
|
//#endregion
|
|
12613
12717
|
//#region src/core/roadmap.ts
|
|
@@ -12850,31 +12954,31 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw) {
|
|
|
12850
12954
|
total: reqIds.length
|
|
12851
12955
|
}, `${updated.length}/${reqIds.length} requirements marked complete`);
|
|
12852
12956
|
}
|
|
12853
|
-
function cmdMilestoneComplete(cwd, version, options) {
|
|
12957
|
+
async function cmdMilestoneComplete(cwd, version, options) {
|
|
12854
12958
|
if (!version) return cmdErr("version required for milestone complete (e.g., v1.0)");
|
|
12855
12959
|
const roadmapPath$1 = roadmapPath(cwd);
|
|
12856
12960
|
const reqPath = planningPath(cwd, "REQUIREMENTS.md");
|
|
12857
12961
|
const statePath$1 = statePath(cwd);
|
|
12858
12962
|
const milestonesPath = planningPath(cwd, "MILESTONES.md");
|
|
12859
|
-
const archiveDir =
|
|
12963
|
+
const archiveDir = archivePath(cwd, version);
|
|
12860
12964
|
const phasesDir = phasesPath(cwd);
|
|
12861
12965
|
const today = todayISO();
|
|
12862
12966
|
const milestoneName = options.name || version;
|
|
12863
|
-
node_fs.
|
|
12967
|
+
await node_fs.promises.mkdir(archiveDir, { recursive: true });
|
|
12864
12968
|
let phaseCount = 0;
|
|
12865
12969
|
let totalPlans = 0;
|
|
12866
12970
|
let totalTasks = 0;
|
|
12867
12971
|
const accomplishments = [];
|
|
12868
12972
|
try {
|
|
12869
|
-
const dirs =
|
|
12973
|
+
const dirs = await listSubDirsAsync(phasesDir, true);
|
|
12870
12974
|
for (const dir of dirs) {
|
|
12871
12975
|
phaseCount++;
|
|
12872
|
-
const phaseFiles = node_fs.
|
|
12976
|
+
const phaseFiles = await node_fs.promises.readdir(node_path.default.join(phasesDir, dir));
|
|
12873
12977
|
const plans = phaseFiles.filter(isPlanFile);
|
|
12874
12978
|
const summaries = phaseFiles.filter(isSummaryFile);
|
|
12875
12979
|
totalPlans += plans.length;
|
|
12876
12980
|
for (const s of summaries) try {
|
|
12877
|
-
const content = node_fs.
|
|
12981
|
+
const content = await node_fs.promises.readFile(node_path.default.join(phasesDir, dir, s), "utf-8");
|
|
12878
12982
|
const fm = extractFrontmatter(content);
|
|
12879
12983
|
if (fm["one-liner"]) accomplishments.push(String(fm["one-liner"]));
|
|
12880
12984
|
const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
@@ -12886,36 +12990,77 @@ function cmdMilestoneComplete(cwd, version, options) {
|
|
|
12886
12990
|
} catch (e) {
|
|
12887
12991
|
debugLog(e);
|
|
12888
12992
|
}
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
node_fs.
|
|
12993
|
+
const stateExists = await pathExistsAsync(statePath$1);
|
|
12994
|
+
if (stateExists) {
|
|
12995
|
+
const stateContent = await node_fs.promises.readFile(statePath$1, "utf-8");
|
|
12996
|
+
await node_fs.promises.writeFile(node_path.default.join(archiveDir, "STATE.md"), stateContent, "utf-8");
|
|
12892
12997
|
}
|
|
12893
|
-
|
|
12894
|
-
|
|
12998
|
+
const roadmapExists = await pathExistsAsync(roadmapPath$1);
|
|
12999
|
+
if (roadmapExists) {
|
|
13000
|
+
const roadmapContent = await node_fs.promises.readFile(roadmapPath$1, "utf-8");
|
|
13001
|
+
await node_fs.promises.writeFile(node_path.default.join(archiveDir, "ROADMAP.md"), roadmapContent, "utf-8");
|
|
13002
|
+
}
|
|
13003
|
+
if (roadmapExists) {
|
|
13004
|
+
const roadmapContent = await node_fs.promises.readFile(roadmapPath$1, "utf-8");
|
|
13005
|
+
await node_fs.promises.writeFile(node_path.default.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, "utf-8");
|
|
13006
|
+
}
|
|
13007
|
+
if (await pathExistsAsync(reqPath)) {
|
|
13008
|
+
const reqContent = await node_fs.promises.readFile(reqPath, "utf-8");
|
|
12895
13009
|
const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
|
|
12896
|
-
node_fs.
|
|
13010
|
+
await node_fs.promises.writeFile(node_path.default.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent, "utf-8");
|
|
12897
13011
|
}
|
|
12898
13012
|
const auditFile = node_path.default.join(cwd, ".planning", `${version}-MILESTONE-AUDIT.md`);
|
|
12899
|
-
if (
|
|
13013
|
+
if (await pathExistsAsync(auditFile)) await node_fs.promises.rename(auditFile, node_path.default.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
|
12900
13014
|
const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join("\n");
|
|
12901
13015
|
const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || "- (none recorded)"}\n\n---\n\n`;
|
|
12902
|
-
if (
|
|
12903
|
-
const existing = node_fs.
|
|
12904
|
-
node_fs.
|
|
12905
|
-
} else node_fs.
|
|
12906
|
-
if (
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
13016
|
+
if (await pathExistsAsync(milestonesPath)) {
|
|
13017
|
+
const existing = await node_fs.promises.readFile(milestonesPath, "utf-8");
|
|
13018
|
+
await node_fs.promises.writeFile(milestonesPath, existing + "\n" + milestoneEntry, "utf-8");
|
|
13019
|
+
} else await node_fs.promises.writeFile(milestonesPath, `# Milestones\n\n${milestoneEntry}`, "utf-8");
|
|
13020
|
+
if (stateExists) {
|
|
13021
|
+
const cleanState = `# Project State
|
|
13022
|
+
|
|
13023
|
+
## Project Reference
|
|
13024
|
+
|
|
13025
|
+
See: .planning/PROJECT.md (updated ${today})
|
|
13026
|
+
|
|
13027
|
+
## Current Position
|
|
13028
|
+
|
|
13029
|
+
Milestone: ${options.name || "Next milestone"}
|
|
13030
|
+
Phase: 0 of ? (not started)
|
|
13031
|
+
Status: planning
|
|
13032
|
+
Last activity: ${today}
|
|
13033
|
+
|
|
13034
|
+
## Performance Metrics
|
|
13035
|
+
|
|
13036
|
+
No plans executed yet in this milestone.
|
|
13037
|
+
|
|
13038
|
+
## Accumulated Context
|
|
13039
|
+
|
|
13040
|
+
### Decisions
|
|
13041
|
+
|
|
13042
|
+
None.
|
|
13043
|
+
|
|
13044
|
+
### Pending Todos
|
|
13045
|
+
|
|
13046
|
+
None.
|
|
13047
|
+
|
|
13048
|
+
### Blockers/Concerns
|
|
13049
|
+
|
|
13050
|
+
None.
|
|
13051
|
+
|
|
13052
|
+
## Session Continuity
|
|
13053
|
+
|
|
13054
|
+
Last session: ${today}
|
|
13055
|
+
`;
|
|
13056
|
+
await node_fs.promises.writeFile(statePath$1, cleanState, "utf-8");
|
|
12912
13057
|
}
|
|
12913
13058
|
let phasesArchived = false;
|
|
12914
13059
|
if (options.archivePhases) try {
|
|
12915
|
-
const phaseArchiveDir = node_path.default.join(archiveDir,
|
|
12916
|
-
node_fs.
|
|
12917
|
-
const phaseDirNames =
|
|
12918
|
-
for (const dir of phaseDirNames) node_fs.
|
|
13060
|
+
const phaseArchiveDir = node_path.default.join(archiveDir, "phases");
|
|
13061
|
+
await node_fs.promises.mkdir(phaseArchiveDir, { recursive: true });
|
|
13062
|
+
const phaseDirNames = await listSubDirsAsync(phasesDir);
|
|
13063
|
+
for (const dir of phaseDirNames) await node_fs.promises.rename(node_path.default.join(phasesDir, dir), node_path.default.join(phaseArchiveDir, dir));
|
|
12919
13064
|
phasesArchived = phaseDirNames.length > 0;
|
|
12920
13065
|
} catch (e) {
|
|
12921
13066
|
debugLog(e);
|
|
@@ -12929,13 +13074,16 @@ function cmdMilestoneComplete(cwd, version, options) {
|
|
|
12929
13074
|
tasks: totalTasks,
|
|
12930
13075
|
accomplishments,
|
|
12931
13076
|
archived: {
|
|
12932
|
-
roadmap:
|
|
12933
|
-
requirements:
|
|
12934
|
-
audit:
|
|
12935
|
-
phases: phasesArchived
|
|
13077
|
+
roadmap: await pathExistsAsync(node_path.default.join(archiveDir, `${version}-ROADMAP.md`)),
|
|
13078
|
+
requirements: await pathExistsAsync(node_path.default.join(archiveDir, `${version}-REQUIREMENTS.md`)),
|
|
13079
|
+
audit: await pathExistsAsync(node_path.default.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
|
|
13080
|
+
phases: phasesArchived,
|
|
13081
|
+
state_snapshot: await pathExistsAsync(node_path.default.join(archiveDir, "STATE.md")),
|
|
13082
|
+
roadmap_snapshot: await pathExistsAsync(node_path.default.join(archiveDir, "ROADMAP.md"))
|
|
12936
13083
|
},
|
|
12937
13084
|
milestones_updated: true,
|
|
12938
|
-
state_updated:
|
|
13085
|
+
state_updated: stateExists,
|
|
13086
|
+
state_reset: stateExists
|
|
12939
13087
|
});
|
|
12940
13088
|
}
|
|
12941
13089
|
|
|
@@ -15010,6 +15158,162 @@ async function cmdPhaseComplete(cwd, phaseNum) {
|
|
|
15010
15158
|
return cmdErr(e.message);
|
|
15011
15159
|
}
|
|
15012
15160
|
}
|
|
15161
|
+
/**
|
|
15162
|
+
* Scan STATE.md for lines matching a phase-tagged pattern in a specific section.
|
|
15163
|
+
*/
|
|
15164
|
+
function findPhaseTaggedLines(content, sectionPattern, phaseNum) {
|
|
15165
|
+
const match = content.match(sectionPattern);
|
|
15166
|
+
if (!match || !match[2]) return [];
|
|
15167
|
+
const escaped = escapePhaseNum(phaseNum);
|
|
15168
|
+
const tagPattern = new RegExp(`^\\s*-\\s*\\[Phase\\s+${escaped}\\]`, "i");
|
|
15169
|
+
return match[2].split("\n").filter((line) => tagPattern.test(line));
|
|
15170
|
+
}
|
|
15171
|
+
const DECISIONS_SECTION_PATTERN = /(#{2,3}\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n#{2,3}\s|\n##[^#]|$)/i;
|
|
15172
|
+
const BLOCKERS_SECTION_PATTERN = /(#{2,3}\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n\s*\n?)([\s\S]*?)(?=\n#{2,3}\s|$)/i;
|
|
15173
|
+
async function archivePhasePreview(cwd, phaseNum, outcomeSummary) {
|
|
15174
|
+
const phaseInfo = await findPhaseInternalAsync(cwd, phaseNum);
|
|
15175
|
+
if (!phaseInfo) return cmdErr(`Phase ${phaseNum} not found`);
|
|
15176
|
+
const archiveDir = await archivePathAsync(cwd);
|
|
15177
|
+
const phaseDirName = node_path.default.basename(phaseInfo.directory);
|
|
15178
|
+
const archiveDest = node_path.default.join(archiveDir, phaseDirName);
|
|
15179
|
+
const stContent = await safeReadFileAsync(statePath(cwd)) ?? "";
|
|
15180
|
+
const decisionsToRemove = findPhaseTaggedLines(stContent, DECISIONS_SECTION_PATTERN, phaseNum);
|
|
15181
|
+
const blockersToRemove = findPhaseTaggedLines(stContent, BLOCKERS_SECTION_PATTERN, phaseNum);
|
|
15182
|
+
const rmContent = await safeReadFileAsync(roadmapPath(cwd)) ?? "";
|
|
15183
|
+
const escaped = escapePhaseNum(phaseNum);
|
|
15184
|
+
const sectionPattern = new RegExp(`#{2,4}\\s*Phase\\s+${escaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|\\n## |$)`, "i");
|
|
15185
|
+
const sectionMatch = rmContent.match(sectionPattern);
|
|
15186
|
+
const sectionToCollapse = sectionMatch ? sectionMatch[0].trim() : "";
|
|
15187
|
+
const collapsedLine = `- [x] Phase ${phaseNum}: ${phaseInfo.phase_name ? phaseInfo.phase_name.replace(/-/g, " ") : `Phase ${phaseNum}`} -- ${outcomeSummary}`;
|
|
15188
|
+
return cmdOk({
|
|
15189
|
+
phase_dir: phaseInfo.directory,
|
|
15190
|
+
archive_dir: node_path.default.relative(cwd, archiveDest).replace(/\\/g, "/"),
|
|
15191
|
+
decisions_to_prune: decisionsToRemove,
|
|
15192
|
+
blockers_to_prune: blockersToRemove,
|
|
15193
|
+
roadmap_section_to_collapse: sectionToCollapse,
|
|
15194
|
+
collapsed_line: collapsedLine
|
|
15195
|
+
});
|
|
15196
|
+
}
|
|
15197
|
+
/**
|
|
15198
|
+
* Prune phase-tagged lines from a section in STATE.md content.
|
|
15199
|
+
*/
|
|
15200
|
+
function pruneSection(content, sectionPattern, phaseNum) {
|
|
15201
|
+
const match = content.match(sectionPattern);
|
|
15202
|
+
if (!match || !match[2]) return content;
|
|
15203
|
+
const escaped = escapePhaseNum(phaseNum);
|
|
15204
|
+
const tagPattern = new RegExp(`^\\s*-\\s*\\[Phase\\s+${escaped}\\]`, "i");
|
|
15205
|
+
let newBody = match[2].split("\n").filter((line) => !tagPattern.test(line)).join("\n");
|
|
15206
|
+
if (!newBody.trim() || !/^\s*[-*]\s+/m.test(newBody)) newBody = "\nNone.\n";
|
|
15207
|
+
return content.replace(sectionPattern, (_m, header) => `${header}${newBody}`);
|
|
15208
|
+
}
|
|
15209
|
+
async function archivePhaseExecute(cwd, phaseNum, outcomeSummary) {
|
|
15210
|
+
const phaseInfo = await findPhaseInternalAsync(cwd, phaseNum);
|
|
15211
|
+
if (!phaseInfo) return cmdErr(`Phase ${phaseNum} not found`);
|
|
15212
|
+
const archiveDir = await archivePathAsync(cwd);
|
|
15213
|
+
const phaseDirName = node_path.default.basename(phaseInfo.directory);
|
|
15214
|
+
const archiveDest = node_path.default.join(archiveDir, phaseDirName);
|
|
15215
|
+
const phaseDirFull = node_path.default.join(cwd, phaseInfo.directory);
|
|
15216
|
+
await node_fs.promises.mkdir(archiveDir, { recursive: true });
|
|
15217
|
+
try {
|
|
15218
|
+
await node_fs.promises.rename(phaseDirFull, archiveDest);
|
|
15219
|
+
} catch (e) {
|
|
15220
|
+
if (e.code === "EXDEV") {
|
|
15221
|
+
debugLog("archive-rename-exdev", "falling back to copy+delete");
|
|
15222
|
+
await node_fs.promises.cp(phaseDirFull, archiveDest, { recursive: true });
|
|
15223
|
+
await node_fs.promises.rm(phaseDirFull, {
|
|
15224
|
+
recursive: true,
|
|
15225
|
+
force: true
|
|
15226
|
+
});
|
|
15227
|
+
} else throw e;
|
|
15228
|
+
}
|
|
15229
|
+
const stPath = statePath(cwd);
|
|
15230
|
+
let stContent = await safeReadFileAsync(stPath);
|
|
15231
|
+
if (stContent) {
|
|
15232
|
+
stContent = pruneSection(stContent, DECISIONS_SECTION_PATTERN, phaseNum);
|
|
15233
|
+
stContent = pruneSection(stContent, BLOCKERS_SECTION_PATTERN, phaseNum);
|
|
15234
|
+
await node_fs.promises.writeFile(stPath, stContent, "utf-8");
|
|
15235
|
+
}
|
|
15236
|
+
const rmPath = roadmapPath(cwd);
|
|
15237
|
+
let rmContent = await safeReadFileAsync(rmPath);
|
|
15238
|
+
if (rmContent) {
|
|
15239
|
+
const escaped = escapePhaseNum(phaseNum);
|
|
15240
|
+
const sectionPattern = new RegExp(`\\n?#{2,4}\\s*Phase\\s+${escaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|\\n## |$)`, "i");
|
|
15241
|
+
rmContent = rmContent.replace(sectionPattern, "");
|
|
15242
|
+
const phaseName = phaseInfo.phase_name ? phaseInfo.phase_name.replace(/-/g, " ") : `Phase ${phaseNum}`;
|
|
15243
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${escaped}[:\\s][^\\n]*`, "i");
|
|
15244
|
+
rmContent = rmContent.replace(checklistPattern, `- [x] Phase ${phaseNum}: ${phaseName} -- ${outcomeSummary}`);
|
|
15245
|
+
await node_fs.promises.writeFile(rmPath, rmContent, "utf-8");
|
|
15246
|
+
}
|
|
15247
|
+
await execGit(cwd, ["add", ...[
|
|
15248
|
+
phaseInfo.directory,
|
|
15249
|
+
node_path.default.relative(cwd, archiveDest).replace(/\\/g, "/"),
|
|
15250
|
+
".planning/STATE.md",
|
|
15251
|
+
".planning/ROADMAP.md"
|
|
15252
|
+
]]);
|
|
15253
|
+
await execGit(cwd, [
|
|
15254
|
+
"commit",
|
|
15255
|
+
"-m",
|
|
15256
|
+
`chore(phase-${phaseNum}): archive completed phase`
|
|
15257
|
+
]);
|
|
15258
|
+
return cmdOk({
|
|
15259
|
+
archived: true,
|
|
15260
|
+
phase: phaseNum,
|
|
15261
|
+
archive_path: node_path.default.relative(cwd, archiveDest).replace(/\\/g, "/"),
|
|
15262
|
+
decisions_pruned: findPhaseTaggedLines(await safeReadFileAsync(statePath(cwd)) ?? "", DECISIONS_SECTION_PATTERN, phaseNum).length === 0,
|
|
15263
|
+
blockers_pruned: true,
|
|
15264
|
+
roadmap_collapsed: true
|
|
15265
|
+
});
|
|
15266
|
+
}
|
|
15267
|
+
async function cmdGetArchivedPhase(cwd, phaseNum) {
|
|
15268
|
+
if (!phaseNum) return cmdErr("phase number required");
|
|
15269
|
+
const normalized = normalizePhaseName(phaseNum);
|
|
15270
|
+
const found = await searchArchiveLocations(planningPath(cwd, "archive"), normalized);
|
|
15271
|
+
if (found) return cmdOk(found);
|
|
15272
|
+
const milestonesDir = planningPath(cwd, "milestones");
|
|
15273
|
+
if (await pathExistsAsync(milestonesDir)) try {
|
|
15274
|
+
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();
|
|
15275
|
+
for (const archiveName of phaseDirs) {
|
|
15276
|
+
const result = await searchForPhaseInDir(node_path.default.join(milestonesDir, archiveName), normalized, archiveName);
|
|
15277
|
+
if (result) return cmdOk(result);
|
|
15278
|
+
}
|
|
15279
|
+
} catch (e) {
|
|
15280
|
+
debugLog("get-archived-phase-milestones-failed", e);
|
|
15281
|
+
}
|
|
15282
|
+
return cmdErr(`Phase ${phaseNum} not found in archive`);
|
|
15283
|
+
}
|
|
15284
|
+
async function searchArchiveLocations(archiveDir, normalized) {
|
|
15285
|
+
if (!await pathExistsAsync(archiveDir)) return null;
|
|
15286
|
+
try {
|
|
15287
|
+
const versionDirs = (await node_fs.promises.readdir(archiveDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
15288
|
+
for (const versionName of versionDirs) {
|
|
15289
|
+
const result = await searchForPhaseInDir(node_path.default.join(archiveDir, versionName), normalized, versionName);
|
|
15290
|
+
if (result) return result;
|
|
15291
|
+
}
|
|
15292
|
+
} catch (e) {
|
|
15293
|
+
debugLog("search-archive-locations-failed", e);
|
|
15294
|
+
}
|
|
15295
|
+
return null;
|
|
15296
|
+
}
|
|
15297
|
+
async function searchForPhaseInDir(baseDir, normalized, milestone) {
|
|
15298
|
+
try {
|
|
15299
|
+
const match = (await listSubDirsAsync(baseDir, true)).find((d) => d.startsWith(normalized));
|
|
15300
|
+
if (!match) return null;
|
|
15301
|
+
const phaseDir = node_path.default.join(baseDir, match);
|
|
15302
|
+
const mdFiles = (await node_fs.promises.readdir(phaseDir)).filter((f) => f.endsWith(".md"));
|
|
15303
|
+
const contents = {};
|
|
15304
|
+
for (const f of mdFiles) contents[f] = await node_fs.promises.readFile(node_path.default.join(phaseDir, f), "utf-8");
|
|
15305
|
+
return {
|
|
15306
|
+
phase: normalized,
|
|
15307
|
+
milestone,
|
|
15308
|
+
directory: match,
|
|
15309
|
+
files: mdFiles,
|
|
15310
|
+
contents
|
|
15311
|
+
};
|
|
15312
|
+
} catch (e) {
|
|
15313
|
+
debugLog("search-for-phase-in-dir-failed", e);
|
|
15314
|
+
return null;
|
|
15315
|
+
}
|
|
15316
|
+
}
|
|
15013
15317
|
|
|
15014
15318
|
//#endregion
|
|
15015
15319
|
//#region src/core/template.ts
|
|
@@ -16016,6 +16320,7 @@ function cmdInitPlanPhase(cwd, phase) {
|
|
|
16016
16320
|
roadmap_path: ".planning/ROADMAP.md",
|
|
16017
16321
|
requirements_path: ".planning/REQUIREMENTS.md"
|
|
16018
16322
|
};
|
|
16323
|
+
if (pathExistsInternal(cwd, ".planning/CONVENTIONS.md")) result.conventions_path = ".planning/CONVENTIONS.md";
|
|
16019
16324
|
if (phaseInfo?.directory) {
|
|
16020
16325
|
const artifacts = scanPhaseArtifacts(cwd, phaseInfo.directory);
|
|
16021
16326
|
if (artifacts.context_path) result.context_path = artifacts.context_path;
|
|
@@ -16175,6 +16480,7 @@ function cmdInitPhaseOp(cwd, phase) {
|
|
|
16175
16480
|
roadmap_path: ".planning/ROADMAP.md",
|
|
16176
16481
|
requirements_path: ".planning/REQUIREMENTS.md"
|
|
16177
16482
|
};
|
|
16483
|
+
if (pathExistsInternal(cwd, ".planning/CONVENTIONS.md")) result.conventions_path = ".planning/CONVENTIONS.md";
|
|
16178
16484
|
if (phaseInfo?.directory) {
|
|
16179
16485
|
const artifacts = scanPhaseArtifacts(cwd, phaseInfo.directory);
|
|
16180
16486
|
if (artifacts.context_path) result.context_path = artifacts.context_path;
|
|
@@ -16545,12 +16851,14 @@ const handlePhase = async (args, cwd, raw) => {
|
|
|
16545
16851
|
"add": () => cmdPhaseAdd(cwd, args.slice(2).join(" ")),
|
|
16546
16852
|
"insert": () => cmdPhaseInsert(cwd, args[2], args.slice(3).join(" ")),
|
|
16547
16853
|
"remove": () => cmdPhaseRemove(cwd, args[2], { force: hasFlag(args, "force") }),
|
|
16548
|
-
"complete": () => cmdPhaseComplete(cwd, args[2])
|
|
16854
|
+
"complete": () => cmdPhaseComplete(cwd, args[2]),
|
|
16855
|
+
"archive-preview": () => archivePhasePreview(cwd, args[2], args.slice(3).join(" ")),
|
|
16856
|
+
"archive-execute": () => archivePhaseExecute(cwd, args[2], args.slice(3).join(" "))
|
|
16549
16857
|
}[sub] : void 0;
|
|
16550
16858
|
if (handler) return handleResult(await handler(), raw);
|
|
16551
|
-
error("Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete");
|
|
16859
|
+
error("Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete, archive-preview, archive-execute");
|
|
16552
16860
|
};
|
|
16553
|
-
const handleMilestone = (args, cwd, raw) => {
|
|
16861
|
+
const handleMilestone = async (args, cwd, raw) => {
|
|
16554
16862
|
if (args[1] === "complete") {
|
|
16555
16863
|
const nameIndex = args.indexOf("--name");
|
|
16556
16864
|
let milestoneName = null;
|
|
@@ -16562,7 +16870,7 @@ const handleMilestone = (args, cwd, raw) => {
|
|
|
16562
16870
|
}
|
|
16563
16871
|
milestoneName = nameArgs.join(" ") || null;
|
|
16564
16872
|
}
|
|
16565
|
-
handleResult(cmdMilestoneComplete(cwd, args[2], {
|
|
16873
|
+
handleResult(await cmdMilestoneComplete(cwd, args[2], {
|
|
16566
16874
|
name: milestoneName ?? void 0,
|
|
16567
16875
|
archivePhases: hasFlag(args, "archive-phases")
|
|
16568
16876
|
}), raw);
|
|
@@ -16643,6 +16951,8 @@ const COMMANDS = {
|
|
|
16643
16951
|
}, raw), raw);
|
|
16644
16952
|
},
|
|
16645
16953
|
"init": handleInit,
|
|
16954
|
+
"detect-stale-context": async (_args, cwd, raw) => handleResult(await cmdDetectStaleContext(cwd), raw),
|
|
16955
|
+
"get-archived-phase": async (args, cwd, raw) => handleResult(await cmdGetArchivedPhase(cwd, args[1]), raw),
|
|
16646
16956
|
"phase-plan-index": async (args, cwd, raw) => handleResult(await cmdPhasePlanIndex(cwd, args[1]), raw),
|
|
16647
16957
|
"state-snapshot": async (_args, cwd, raw) => handleResult(await cmdStateSnapshot(cwd, raw), raw),
|
|
16648
16958
|
"summary-extract": (args, cwd, raw) => {
|