maxsimcli 4.15.3 → 4.16.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 (38) hide show
  1. package/dist/assets/CHANGELOG.md +14 -0
  2. package/dist/assets/hooks/maxsim-statusline.cjs +46 -7
  3. package/dist/assets/hooks/maxsim-statusline.cjs.map +1 -1
  4. package/dist/assets/templates/agents/AGENTS.md +1 -1
  5. package/dist/assets/templates/agents/executor.md +1 -1
  6. package/dist/assets/templates/agents/planner.md +4 -4
  7. package/dist/assets/templates/references/git-planning-commit.md +1 -1
  8. package/dist/assets/templates/references/questioning.md +1 -1
  9. package/dist/assets/templates/templates/codebase/structure.md +1 -1
  10. package/dist/assets/templates/templates/milestone-archive.md +3 -3
  11. package/dist/assets/templates/workflows/batch.md +2 -3
  12. package/dist/assets/templates/workflows/diagnose-issues.md +6 -6
  13. package/dist/assets/templates/workflows/discovery-phase.md +6 -7
  14. package/dist/assets/templates/workflows/discuss-phase.md +8 -11
  15. package/dist/assets/templates/workflows/execute-phase.md +11 -71
  16. package/dist/assets/templates/workflows/execute-plan.md +8 -37
  17. package/dist/assets/templates/workflows/execute.md +7 -9
  18. package/dist/assets/templates/workflows/go.md +4 -4
  19. package/dist/assets/templates/workflows/help.md +1 -1
  20. package/dist/assets/templates/workflows/init-existing.md +0 -5
  21. package/dist/assets/templates/workflows/new-milestone.md +2 -7
  22. package/dist/assets/templates/workflows/new-project.md +0 -5
  23. package/dist/assets/templates/workflows/progress.md +10 -11
  24. package/dist/assets/templates/workflows/quick.md +0 -1
  25. package/dist/assets/templates/workflows/sdd.md +29 -30
  26. package/dist/assets/templates/workflows/settings.md +2 -7
  27. package/dist/assets/templates/workflows/verify-work.md +2 -16
  28. package/dist/cli.cjs +6913 -6499
  29. package/dist/cli.cjs.map +1 -1
  30. package/dist/core-D5zUr9cb.cjs.map +1 -1
  31. package/dist/install.cjs +10 -26
  32. package/dist/install.cjs.map +1 -1
  33. package/dist/mcp-server.cjs +186 -91
  34. package/dist/mcp-server.cjs.map +1 -1
  35. package/dist/skills-CjFWZIGM.cjs.map +1 -1
  36. package/package.json +1 -1
  37. package/dist/assets/templates/references/dashboard-bridge.md +0 -59
  38. package/dist/assets/templates/workflows/plan-phase.md +0 -501
@@ -7,6 +7,19 @@ var __getOwnPropNames$1 = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
9
9
  var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
10
+ var __exportAll = (all, no_symbols) => {
11
+ let target = {};
12
+ for (var name in all) {
13
+ __defProp$1(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ }
18
+ if (!no_symbols) {
19
+ __defProp$1(target, Symbol.toStringTag, { value: "Module" });
20
+ }
21
+ return target;
22
+ };
10
23
  var __copyProps$1 = (to, from, except, desc) => {
11
24
  if (from && typeof from === "object" || typeof from === "function") {
12
25
  for (var keys = __getOwnPropNames$1(from), i = 0, n = keys.length, key; i < n; i++) {
@@ -26765,8 +26778,9 @@ function configPath(cwd) {
26765
26778
  function phasesPath(cwd) {
26766
26779
  return planningPath(cwd, "phases");
26767
26780
  }
26768
- /** Phase-file predicates. */
26781
+ /** Phase-file predicates. @deprecated Use GitHub Issues for plan/summary tracking. Local fallback only. */
26769
26782
  const isPlanFile = (f) => f.endsWith("-PLAN.md") || f === "PLAN.md";
26783
+ /** @deprecated Use GitHub Issues for plan/summary tracking. Local fallback only. */
26770
26784
  const isSummaryFile = (f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md";
26771
26785
  /** Strip suffix to get plan/summary ID. */
26772
26786
  const planId = (f) => f.replace("-PLAN.md", "").replace("PLAN.md", "");
@@ -27005,10 +27019,19 @@ async function searchPhaseInDir(baseDir, relBase, normalized) {
27005
27019
  }
27006
27020
  async function findPhaseInternal(cwd, phase) {
27007
27021
  if (!phase) return null;
27008
- const pd = phasesPath(cwd);
27009
27022
  const normalized = normalizePhaseName(phase);
27010
- const current = await searchPhaseInDir(pd, node_path.default.join(".planning", "phases"), normalized);
27011
- if (current) return current;
27023
+ try {
27024
+ const { findPhaseFromGitHub } = await Promise.resolve().then(() => sync_exports);
27025
+ const ghResult = await findPhaseFromGitHub(cwd, normalized);
27026
+ if (ghResult) return ghResult;
27027
+ } catch {
27028
+ debugLog("find-phase-github-fallback", "GitHub lookup failed, falling back to local");
27029
+ }
27030
+ const current = await searchPhaseInDir(phasesPath(cwd), node_path.default.join(".planning", "phases"), normalized);
27031
+ if (current) {
27032
+ current.source = "local";
27033
+ return current;
27034
+ }
27012
27035
  const archiveDir = planningPath(cwd, "archive");
27013
27036
  if (await pathExistsInternal(archiveDir)) try {
27014
27037
  const versionDirs = (await node_fs.promises.readdir(archiveDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
@@ -27016,29 +27039,13 @@ async function findPhaseInternal(cwd, phase) {
27016
27039
  const result = await searchPhaseInDir(node_path.default.join(archiveDir, versionName), node_path.default.join(".planning", "archive", versionName), normalized);
27017
27040
  if (result) {
27018
27041
  result.archived = versionName;
27042
+ result.source = "local";
27019
27043
  return result;
27020
27044
  }
27021
27045
  }
27022
27046
  } catch (e) {
27023
27047
  debugLog("find-phase-async-archive-search-failed", e);
27024
27048
  }
27025
- const milestonesDir = planningPath(cwd, "milestones");
27026
- if (!await pathExistsInternal(milestonesDir)) return null;
27027
- try {
27028
- const archiveDirs = (await node_fs.promises.readdir(milestonesDir, { withFileTypes: true })).filter((e) => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name)).map((e) => e.name).sort().reverse();
27029
- for (const archiveName of archiveDirs) {
27030
- const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
27031
- if (!versionMatch) continue;
27032
- const version = versionMatch[1];
27033
- const result = await searchPhaseInDir(node_path.default.join(milestonesDir, archiveName), node_path.default.join(".planning", "milestones", archiveName), normalized);
27034
- if (result) {
27035
- result.archived = version;
27036
- return result;
27037
- }
27038
- }
27039
- } catch (e) {
27040
- debugLog("find-phase-async-milestone-search-failed", e);
27041
- }
27042
27049
  return null;
27043
27050
  }
27044
27051
  async function getArchivedPhaseDirs(cwd) {
@@ -27059,25 +27066,6 @@ async function getArchivedPhaseDirs(cwd) {
27059
27066
  } catch (e) {
27060
27067
  debugLog("get-archived-phase-dirs-async-archive-failed", e);
27061
27068
  }
27062
- const milestonesDir = planningPath(cwd, "milestones");
27063
- try {
27064
- 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();
27065
- for (const archiveName of phaseDirs) {
27066
- const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
27067
- if (!versionMatch) continue;
27068
- const version = versionMatch[1];
27069
- const archiveMilestonePath = node_path.default.join(milestonesDir, archiveName);
27070
- const dirs = await listSubDirs(archiveMilestonePath, true);
27071
- for (const dir of dirs) results.push({
27072
- name: dir,
27073
- milestone: version,
27074
- basePath: node_path.default.join(".planning", "milestones", archiveName),
27075
- fullPath: node_path.default.join(archiveMilestonePath, dir)
27076
- });
27077
- }
27078
- } catch (e) {
27079
- debugLog("get-archived-phase-dirs-async-failed", e);
27080
- }
27081
27069
  return results;
27082
27070
  }
27083
27071
 
@@ -33763,10 +33751,7 @@ function cmdErr(error) {
33763
33751
  *
33764
33752
  * Ported from maxsim/bin/lib/phase.cjs
33765
33753
  */
33766
- async function scaffoldPhaseStubs(dirPath, phaseId, name) {
33767
- const today = todayISO();
33768
- await Promise.all([node_fs.promises.writeFile(node_path.default.join(dirPath, `${phaseId}-CONTEXT.md`), `# Phase ${phaseId} Context: ${name}\n\n**Created:** ${today}\n**Phase goal:** [To be defined during /maxsim:discuss-phase]\n\n---\n\n_Context will be populated by /maxsim:discuss-phase_\n`), node_fs.promises.writeFile(node_path.default.join(dirPath, `${phaseId}-RESEARCH.md`), `# Phase ${phaseId}: ${name} - Research\n\n**Researched:** Not yet\n**Domain:** TBD\n**Confidence:** TBD\n\n---\n\n_Research will be populated by /maxsim:research-phase_\n`)]);
33769
- }
33754
+ async function scaffoldPhaseStubs(_dirPath, _phaseId, _name) {}
33770
33755
  async function phaseAddCore(cwd, description, options) {
33771
33756
  const rmPath = roadmapPath(cwd);
33772
33757
  let content;
@@ -33789,8 +33774,8 @@ async function phaseAddCore(cwd, description, options) {
33789
33774
  const dirPath = planningPath(cwd, "phases", dirName);
33790
33775
  await node_fs.promises.mkdir(dirPath, { recursive: true });
33791
33776
  await node_fs.promises.writeFile(node_path.default.join(dirPath, ".gitkeep"), "");
33792
- if (options?.includeStubs) await scaffoldPhaseStubs(dirPath, paddedNum, description);
33793
- 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`;
33777
+ if (options?.includeStubs) await /* @__PURE__ */ scaffoldPhaseStubs(dirPath, paddedNum, description);
33778
+ 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 ${newPhaseNum} to break down)\n`;
33794
33779
  let updatedContent;
33795
33780
  const lastSeparator = content.lastIndexOf("\n---");
33796
33781
  if (lastSeparator > 0) updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
@@ -33833,8 +33818,8 @@ async function phaseInsertCore(cwd, afterPhase, description, options) {
33833
33818
  const dirPath = planningPath(cwd, "phases", dirName);
33834
33819
  await node_fs.promises.mkdir(dirPath, { recursive: true });
33835
33820
  await node_fs.promises.writeFile(node_path.default.join(dirPath, ".gitkeep"), "");
33836
- if (options?.includeStubs) await scaffoldPhaseStubs(dirPath, decimalPhase, description);
33837
- 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`;
33821
+ if (options?.includeStubs) await /* @__PURE__ */ scaffoldPhaseStubs(dirPath, decimalPhase, description);
33822
+ 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 ${decimalPhase} to break down)\n`;
33838
33823
  const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, "i");
33839
33824
  const headerMatch = content.match(headerPattern);
33840
33825
  if (!headerMatch) throw new Error(`Could not find Phase ${afterPhase} header`);
@@ -38842,48 +38827,6 @@ function escapeStringRegexp(string) {
38842
38827
  return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
38843
38828
  }
38844
38829
 
38845
- //#endregion
38846
- //#region src/core/state.ts
38847
- /**
38848
- * State — STATE.md operations and progression engine
38849
- *
38850
- * Ported from maxsim/bin/lib/state.cjs
38851
- */
38852
- function stateExtractField(content, fieldName) {
38853
- const escaped = escapeStringRegexp(fieldName);
38854
- const boldPattern = new RegExp(`\\*\\*\\s*${escaped}\\s*:\\s*\\*\\*\\s*(.+)`, "i");
38855
- const boldMatch = content.match(boldPattern);
38856
- if (boldMatch) return boldMatch[1].trim();
38857
- const plainPattern = new RegExp(`^\\s*${escaped}\\s*:\\s*(.+)`, "im");
38858
- const plainMatch = content.match(plainPattern);
38859
- return plainMatch ? plainMatch[1].trim() : null;
38860
- }
38861
- function stateReplaceField(content, fieldName, newValue) {
38862
- const escaped = escapeStringRegexp(fieldName);
38863
- const boldPattern = new RegExp(`(\\*\\*\\s*${escaped}\\s*:\\s*\\*\\*\\s*)(.*)`, "i");
38864
- let replaced = content.replace(boldPattern, (_match, prefix) => `${prefix}${newValue}`);
38865
- if (replaced !== content) return replaced;
38866
- const plainPattern = new RegExp(`(^[ \\t]*${escaped}\\s*:\\s*)(.*)`, "im");
38867
- replaced = content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
38868
- return replaced !== content ? replaced : null;
38869
- }
38870
- /**
38871
- * Append an entry to a section in STATE.md content, removing placeholder text.
38872
- * Returns updated content or null if section not found.
38873
- */
38874
- function appendToStateSection(content, sectionPattern, entry, placeholderPatterns) {
38875
- const match = content.match(sectionPattern);
38876
- if (!match) return null;
38877
- let sectionBody = match[2];
38878
- for (const pat of placeholderPatterns || [
38879
- /None yet\.?\s*\n?/gi,
38880
- /No decisions yet\.?\s*\n?/gi,
38881
- /None\.?\s*\n?/gi
38882
- ]) sectionBody = sectionBody.replace(pat, "");
38883
- sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
38884
- return content.replace(sectionPattern, (_m, header) => `${header}${sectionBody}`);
38885
- }
38886
-
38887
38830
  //#endregion
38888
38831
  //#region src/github/sync.ts
38889
38832
  /**
@@ -38902,6 +38845,12 @@ function appendToStateSection(content, sectionPattern, entry, placeholderPattern
38902
38845
  * CRITICAL: All operations use client.ts (Octokit adapter) exclusively.
38903
38846
  * CRITICAL: Never call process.exit() — return GhResult instead.
38904
38847
  */
38848
+ var sync_exports = /* @__PURE__ */ __exportAll({
38849
+ checkPhaseProgress: () => checkPhaseProgress,
38850
+ detectInterruptedPhase: () => detectInterruptedPhase,
38851
+ findPhaseFromGitHub: () => findPhaseFromGitHub,
38852
+ getAllPhasesProgress: () => getAllPhasesProgress
38853
+ });
38905
38854
  /**
38906
38855
  * Get the progress of a phase by counting open/closed sub-issues.
38907
38856
  *
@@ -39019,6 +38968,130 @@ async function getAllPhasesProgress() {
39019
38968
  return results;
39020
38969
  });
39021
38970
  }
38971
+ /**
38972
+ * Populate a PhaseSearchResult from GitHub Issues data.
38973
+ *
38974
+ * Looks up the phase in the local mapping cache, then queries GitHub for
38975
+ * sub-issue counts and comments to derive plans/summaries/research status.
38976
+ *
38977
+ * Returns null if:
38978
+ * - No mapping file exists
38979
+ * - Phase not found in mapping
38980
+ * - GitHub API call fails
38981
+ *
38982
+ * @param cwd - Project root directory
38983
+ * @param phaseNum - Normalized phase number (e.g., "01", "02A")
38984
+ */
38985
+ async function findPhaseFromGitHub(cwd, phaseNum) {
38986
+ let mapping;
38987
+ try {
38988
+ mapping = loadMapping(cwd);
38989
+ } catch {
38990
+ return null;
38991
+ }
38992
+ if (!mapping) return null;
38993
+ const phaseMapping = mapping.phases[phaseNum];
38994
+ if (!phaseMapping) return null;
38995
+ const issueNumber = phaseMapping.tracking_issue.number;
38996
+ if (!issueNumber) return null;
38997
+ const progressResult = await checkPhaseProgress(issueNumber);
38998
+ if (!progressResult.ok) return null;
38999
+ const { tasks, completed, total } = progressResult.data;
39000
+ let phaseName = null;
39001
+ let phaseSlug = null;
39002
+ try {
39003
+ const octokit = getOctokit();
39004
+ const { owner, repo } = await getRepoInfo();
39005
+ const titleMatch = (await octokit.rest.issues.get({
39006
+ owner,
39007
+ repo,
39008
+ issue_number: issueNumber
39009
+ })).data.title.match(/\[Phase\s+\S+\]\s*(.*)/);
39010
+ if (titleMatch) {
39011
+ phaseName = titleMatch[1].trim();
39012
+ phaseSlug = phaseName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
39013
+ }
39014
+ } catch {}
39015
+ const planNames = tasks.map((t) => `task-${t.number}`);
39016
+ const summaryNames = tasks.filter((t) => t.state === "closed").map((t) => `task-${t.number}`);
39017
+ const completedSet = new Set(summaryNames);
39018
+ const incompletePlans = planNames.filter((p) => !completedSet.has(p));
39019
+ let hasResearch = false;
39020
+ let hasContext = false;
39021
+ let hasVerification = false;
39022
+ try {
39023
+ const octokit = getOctokit();
39024
+ const { owner, repo } = await getRepoInfo();
39025
+ const comments = await octokit.rest.issues.listComments({
39026
+ owner,
39027
+ repo,
39028
+ issue_number: issueNumber,
39029
+ per_page: 100
39030
+ });
39031
+ for (const comment of comments.data) {
39032
+ const body = comment.body ?? "";
39033
+ if (body.includes("<!-- maxsim:type=research -->") || body.includes("## Research") || body.startsWith("## Phase") && body.includes("Research")) hasResearch = true;
39034
+ if (body.includes("<!-- maxsim:type=context -->") || body.includes("## Context")) hasContext = true;
39035
+ if (body.includes("<!-- maxsim:type=verification -->") || body.includes("## Verification")) hasVerification = true;
39036
+ }
39037
+ } catch {}
39038
+ return {
39039
+ found: true,
39040
+ directory: `.planning/phases/${phaseNum}-${phaseSlug || "unknown"}`,
39041
+ phase_number: phaseNum,
39042
+ phase_name: phaseName,
39043
+ phase_slug: phaseSlug,
39044
+ plans: planNames,
39045
+ summaries: summaryNames,
39046
+ incomplete_plans: incompletePlans,
39047
+ has_research: hasResearch,
39048
+ has_context: hasContext,
39049
+ has_verification: hasVerification,
39050
+ source: "github"
39051
+ };
39052
+ }
39053
+
39054
+ //#endregion
39055
+ //#region src/core/state.ts
39056
+ /**
39057
+ * State — STATE.md operations and progression engine
39058
+ *
39059
+ * Ported from maxsim/bin/lib/state.cjs
39060
+ */
39061
+ function stateExtractField(content, fieldName) {
39062
+ const escaped = escapeStringRegexp(fieldName);
39063
+ const boldPattern = new RegExp(`\\*\\*\\s*${escaped}\\s*:\\s*\\*\\*\\s*(.+)`, "i");
39064
+ const boldMatch = content.match(boldPattern);
39065
+ if (boldMatch) return boldMatch[1].trim();
39066
+ const plainPattern = new RegExp(`^\\s*${escaped}\\s*:\\s*(.+)`, "im");
39067
+ const plainMatch = content.match(plainPattern);
39068
+ return plainMatch ? plainMatch[1].trim() : null;
39069
+ }
39070
+ function stateReplaceField(content, fieldName, newValue) {
39071
+ const escaped = escapeStringRegexp(fieldName);
39072
+ const boldPattern = new RegExp(`(\\*\\*\\s*${escaped}\\s*:\\s*\\*\\*\\s*)(.*)`, "i");
39073
+ let replaced = content.replace(boldPattern, (_match, prefix) => `${prefix}${newValue}`);
39074
+ if (replaced !== content) return replaced;
39075
+ const plainPattern = new RegExp(`(^[ \\t]*${escaped}\\s*:\\s*)(.*)`, "im");
39076
+ replaced = content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
39077
+ return replaced !== content ? replaced : null;
39078
+ }
39079
+ /**
39080
+ * Append an entry to a section in STATE.md content, removing placeholder text.
39081
+ * Returns updated content or null if section not found.
39082
+ */
39083
+ function appendToStateSection(content, sectionPattern, entry, placeholderPatterns) {
39084
+ const match = content.match(sectionPattern);
39085
+ if (!match) return null;
39086
+ let sectionBody = match[2];
39087
+ for (const pat of placeholderPatterns || [
39088
+ /None yet\.?\s*\n?/gi,
39089
+ /No decisions yet\.?\s*\n?/gi,
39090
+ /None\.?\s*\n?/gi
39091
+ ]) sectionBody = sectionBody.replace(pat, "");
39092
+ sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
39093
+ return content.replace(sectionPattern, (_m, header) => `${header}${sectionBody}`);
39094
+ }
39022
39095
 
39023
39096
  //#endregion
39024
39097
  //#region src/mcp/state-tools.ts
@@ -39315,6 +39388,20 @@ async function cmdRoadmapAnalyze(cwd) {
39315
39388
  checkboxPattern: new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i")
39316
39389
  });
39317
39390
  }
39391
+ let ghPhaseProgress = null;
39392
+ try {
39393
+ const mapping = loadMapping(cwd);
39394
+ if (mapping && Object.keys(mapping.phases).length > 0) {
39395
+ const ghResult = await getAllPhasesProgress();
39396
+ if (ghResult.ok) {
39397
+ ghPhaseProgress = /* @__PURE__ */ new Map();
39398
+ for (const entry of ghResult.data) ghPhaseProgress.set(entry.phaseNumber, {
39399
+ total: entry.progress.total,
39400
+ completed: entry.progress.completed
39401
+ });
39402
+ }
39403
+ }
39404
+ } catch {}
39318
39405
  let allDirs = [];
39319
39406
  try {
39320
39407
  allDirs = await listSubDirs(phasesDir);
@@ -39325,7 +39412,15 @@ async function cmdRoadmapAnalyze(cwd) {
39325
39412
  let summaryCount = 0;
39326
39413
  let hasContext = false;
39327
39414
  let hasResearch = false;
39328
- try {
39415
+ const ghData = ghPhaseProgress?.get(p.phaseNum);
39416
+ if (ghData) {
39417
+ planCount = ghData.total;
39418
+ summaryCount = ghData.completed;
39419
+ if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
39420
+ else if (summaryCount > 0) diskStatus = "partial";
39421
+ else if (planCount > 0) diskStatus = "planned";
39422
+ else diskStatus = "empty";
39423
+ } else try {
39329
39424
  const dirMatch = allDirs.find((d) => d.startsWith(p.normalized + "-") || d === p.normalized);
39330
39425
  if (dirMatch) {
39331
39426
  const phaseFiles = await node_fs.promises.readdir(node_path.default.join(phasesDir, dirMatch));