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.
Files changed (63) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/assets/CHANGELOG.md +7 -0
  3. package/dist/assets/templates/agents/AGENTS.md +8 -6
  4. package/dist/assets/templates/agents/maxsim-code-reviewer.md +11 -0
  5. package/dist/assets/templates/agents/maxsim-executor.md +1 -0
  6. package/dist/assets/templates/agents/maxsim-planner.md +1 -0
  7. package/dist/assets/templates/agents/maxsim-project-researcher.md +96 -0
  8. package/dist/assets/templates/agents/maxsim-research-synthesizer.md +55 -3
  9. package/dist/assets/templates/agents/maxsim-roadmapper.md +11 -0
  10. package/dist/assets/templates/references/questioning.md +184 -33
  11. package/dist/assets/templates/skills/code-review/SKILL.md +5 -3
  12. package/dist/assets/templates/skills/{batch-worktree → maxsim-batch}/SKILL.md +3 -3
  13. package/dist/assets/templates/skills/{simplify → maxsim-simplify}/SKILL.md +7 -6
  14. package/dist/assets/templates/skills/using-maxsim/SKILL.md +4 -2
  15. package/dist/assets/templates/templates/conventions.md +138 -0
  16. package/dist/assets/templates/templates/no-gos.md +45 -4
  17. package/dist/assets/templates/templates/project.md +23 -0
  18. package/dist/assets/templates/workflows/batch.md +1 -1
  19. package/dist/assets/templates/workflows/init-existing.md +187 -0
  20. package/dist/assets/templates/workflows/new-project.md +195 -7
  21. package/dist/backend-server.cjs +13 -0
  22. package/dist/backend-server.cjs.map +1 -1
  23. package/dist/cli.cjs +350 -40
  24. package/dist/cli.cjs.map +1 -1
  25. package/dist/cli.js +7 -3
  26. package/dist/cli.js.map +1 -1
  27. package/dist/core/core.d.ts +2 -0
  28. package/dist/core/core.d.ts.map +1 -1
  29. package/dist/core/core.js +67 -6
  30. package/dist/core/core.js.map +1 -1
  31. package/dist/core/index.d.ts +4 -4
  32. package/dist/core/index.d.ts.map +1 -1
  33. package/dist/core/index.js +9 -3
  34. package/dist/core/index.js.map +1 -1
  35. package/dist/core/init.d.ts +2 -0
  36. package/dist/core/init.d.ts.map +1 -1
  37. package/dist/core/init.js +6 -0
  38. package/dist/core/init.js.map +1 -1
  39. package/dist/core/milestone.d.ts +1 -1
  40. package/dist/core/milestone.d.ts.map +1 -1
  41. package/dist/core/milestone.js +81 -37
  42. package/dist/core/milestone.js.map +1 -1
  43. package/dist/core/phase.d.ts +3 -0
  44. package/dist/core/phase.d.ts.map +1 -1
  45. package/dist/core/phase.js +213 -0
  46. package/dist/core/phase.js.map +1 -1
  47. package/dist/core/state.d.ts +6 -0
  48. package/dist/core/state.d.ts.map +1 -1
  49. package/dist/core/state.js +69 -0
  50. package/dist/core/state.js.map +1 -1
  51. package/dist/core/types.d.ts +11 -0
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/core-RRjCSt0G.cjs.map +1 -1
  54. package/dist/install/shared.d.ts +1 -1
  55. package/dist/install/shared.d.ts.map +1 -1
  56. package/dist/install/shared.js +1 -1
  57. package/dist/install/shared.js.map +1 -1
  58. package/dist/install.cjs +4 -2
  59. package/dist/install.cjs.map +1 -1
  60. package/dist/mcp-server.cjs +13 -0
  61. package/dist/mcp-server.cjs.map +1 -1
  62. package/dist/skills-MYlMkYNt.cjs.map +1 -1
  63. 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 archivePath = node_path.default.join(milestonesDir, archiveName);
5237
- const dirs = await listSubDirsAsync(archivePath, true);
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(archivePath, dir)
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 = planningPath(cwd, "milestones");
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.default.mkdirSync(archiveDir, { recursive: true });
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 = listSubDirs(phasesDir, true);
12973
+ const dirs = await listSubDirsAsync(phasesDir, true);
12870
12974
  for (const dir of dirs) {
12871
12975
  phaseCount++;
12872
- const phaseFiles = node_fs.default.readdirSync(node_path.default.join(phasesDir, dir));
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.default.readFileSync(node_path.default.join(phasesDir, dir, s), "utf-8");
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
- if (node_fs.default.existsSync(roadmapPath$1)) {
12890
- const roadmapContent = node_fs.default.readFileSync(roadmapPath$1, "utf-8");
12891
- node_fs.default.writeFileSync(node_path.default.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, "utf-8");
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
- if (node_fs.default.existsSync(reqPath)) {
12894
- const reqContent = node_fs.default.readFileSync(reqPath, "utf-8");
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.default.writeFileSync(node_path.default.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent, "utf-8");
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 (node_fs.default.existsSync(auditFile)) node_fs.default.renameSync(auditFile, node_path.default.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
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 (node_fs.default.existsSync(milestonesPath)) {
12903
- const existing = node_fs.default.readFileSync(milestonesPath, "utf-8");
12904
- node_fs.default.writeFileSync(milestonesPath, existing + "\n" + milestoneEntry, "utf-8");
12905
- } else node_fs.default.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, "utf-8");
12906
- if (node_fs.default.existsSync(statePath$1)) {
12907
- let stateContent = node_fs.default.readFileSync(statePath$1, "utf-8");
12908
- stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${version} milestone complete`);
12909
- stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
12910
- stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1${version} milestone completed and archived`);
12911
- node_fs.default.writeFileSync(statePath$1, stateContent, "utf-8");
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, `${version}-phases`);
12916
- node_fs.default.mkdirSync(phaseArchiveDir, { recursive: true });
12917
- const phaseDirNames = listSubDirs(phasesDir);
12918
- for (const dir of phaseDirNames) node_fs.default.renameSync(node_path.default.join(phasesDir, dir), node_path.default.join(phaseArchiveDir, dir));
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: node_fs.default.existsSync(node_path.default.join(archiveDir, `${version}-ROADMAP.md`)),
12933
- requirements: node_fs.default.existsSync(node_path.default.join(archiveDir, `${version}-REQUIREMENTS.md`)),
12934
- audit: node_fs.default.existsSync(node_path.default.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
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: node_fs.default.existsSync(statePath$1)
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) => {