@tuan_son.dinh/gsd 2.6.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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +453 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +269 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +70 -0
  9. package/dist/logo.d.ts +16 -0
  10. package/dist/logo.js +25 -0
  11. package/dist/onboarding.d.ts +43 -0
  12. package/dist/onboarding.js +418 -0
  13. package/dist/pi-migration.d.ts +14 -0
  14. package/dist/pi-migration.js +57 -0
  15. package/dist/resource-loader.d.ts +22 -0
  16. package/dist/resource-loader.js +60 -0
  17. package/dist/tool-bootstrap.d.ts +4 -0
  18. package/dist/tool-bootstrap.js +74 -0
  19. package/dist/wizard.d.ts +7 -0
  20. package/dist/wizard.js +25 -0
  21. package/package.json +60 -0
  22. package/patches/@mariozechner+pi-coding-agent+0.57.1.patch +108 -0
  23. package/patches/@mariozechner+pi-tui+0.57.1.patch +47 -0
  24. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  25. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  26. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  27. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  28. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  29. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  30. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  31. package/pkg/package.json +8 -0
  32. package/scripts/postinstall.js +127 -0
  33. package/src/resources/GSD-WORKFLOW.md +661 -0
  34. package/src/resources/agents/researcher.md +29 -0
  35. package/src/resources/agents/scout.md +56 -0
  36. package/src/resources/agents/worker.md +31 -0
  37. package/src/resources/extensions/ask-user-questions.ts +249 -0
  38. package/src/resources/extensions/bg-shell/index.ts +2808 -0
  39. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  40. package/src/resources/extensions/browser-tools/core.js +1057 -0
  41. package/src/resources/extensions/browser-tools/index.ts +4989 -0
  42. package/src/resources/extensions/browser-tools/package.json +20 -0
  43. package/src/resources/extensions/context7/index.ts +428 -0
  44. package/src/resources/extensions/context7/package.json +11 -0
  45. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  46. package/src/resources/extensions/google-search/index.ts +323 -0
  47. package/src/resources/extensions/google-search/package.json +9 -0
  48. package/src/resources/extensions/gsd/activity-log.ts +69 -0
  49. package/src/resources/extensions/gsd/auto.ts +2744 -0
  50. package/src/resources/extensions/gsd/commands.ts +313 -0
  51. package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
  52. package/src/resources/extensions/gsd/dashboard-overlay.ts +521 -0
  53. package/src/resources/extensions/gsd/docs/preferences-reference.md +176 -0
  54. package/src/resources/extensions/gsd/doctor.ts +690 -0
  55. package/src/resources/extensions/gsd/files.ts +732 -0
  56. package/src/resources/extensions/gsd/git-service.ts +597 -0
  57. package/src/resources/extensions/gsd/gitignore.ts +168 -0
  58. package/src/resources/extensions/gsd/guided-flow.ts +817 -0
  59. package/src/resources/extensions/gsd/index.ts +558 -0
  60. package/src/resources/extensions/gsd/metrics.ts +374 -0
  61. package/src/resources/extensions/gsd/migrate/command.ts +218 -0
  62. package/src/resources/extensions/gsd/migrate/index.ts +42 -0
  63. package/src/resources/extensions/gsd/migrate/parser.ts +323 -0
  64. package/src/resources/extensions/gsd/migrate/parsers.ts +624 -0
  65. package/src/resources/extensions/gsd/migrate/preview.ts +48 -0
  66. package/src/resources/extensions/gsd/migrate/transformer.ts +346 -0
  67. package/src/resources/extensions/gsd/migrate/types.ts +370 -0
  68. package/src/resources/extensions/gsd/migrate/validator.ts +55 -0
  69. package/src/resources/extensions/gsd/migrate/writer.ts +539 -0
  70. package/src/resources/extensions/gsd/observability-validator.ts +408 -0
  71. package/src/resources/extensions/gsd/package.json +11 -0
  72. package/src/resources/extensions/gsd/paths.ts +308 -0
  73. package/src/resources/extensions/gsd/preferences.ts +757 -0
  74. package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
  75. package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  76. package/src/resources/extensions/gsd/prompts/complete-slice.md +29 -0
  77. package/src/resources/extensions/gsd/prompts/discuss.md +189 -0
  78. package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  79. package/src/resources/extensions/gsd/prompts/execute-task.md +61 -0
  80. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
  81. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
  82. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
  83. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
  84. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
  85. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
  86. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
  87. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  88. package/src/resources/extensions/gsd/prompts/plan-milestone.md +65 -0
  89. package/src/resources/extensions/gsd/prompts/plan-slice.md +51 -0
  90. package/src/resources/extensions/gsd/prompts/queue.md +85 -0
  91. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  92. package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  93. package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  94. package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
  95. package/src/resources/extensions/gsd/prompts/review-migration.md +66 -0
  96. package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
  97. package/src/resources/extensions/gsd/prompts/system.md +187 -0
  98. package/src/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
  99. package/src/resources/extensions/gsd/session-forensics.ts +487 -0
  100. package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
  101. package/src/resources/extensions/gsd/state.ts +460 -0
  102. package/src/resources/extensions/gsd/templates/context.md +76 -0
  103. package/src/resources/extensions/gsd/templates/decisions.md +8 -0
  104. package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  105. package/src/resources/extensions/gsd/templates/plan.md +131 -0
  106. package/src/resources/extensions/gsd/templates/preferences.md +24 -0
  107. package/src/resources/extensions/gsd/templates/project.md +31 -0
  108. package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
  109. package/src/resources/extensions/gsd/templates/requirements.md +81 -0
  110. package/src/resources/extensions/gsd/templates/research.md +46 -0
  111. package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
  112. package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
  113. package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
  114. package/src/resources/extensions/gsd/templates/state.md +19 -0
  115. package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
  116. package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
  117. package/src/resources/extensions/gsd/templates/uat.md +54 -0
  118. package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +327 -0
  119. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +56 -0
  120. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
  121. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +225 -0
  122. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +160 -0
  123. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +341 -0
  124. package/src/resources/extensions/gsd/tests/derive-state.test.ts +689 -0
  125. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +38 -0
  126. package/src/resources/extensions/gsd/tests/doctor.test.ts +505 -0
  127. package/src/resources/extensions/gsd/tests/git-service.test.ts +1313 -0
  128. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +308 -0
  129. package/src/resources/extensions/gsd/tests/metrics-io.test.ts +201 -0
  130. package/src/resources/extensions/gsd/tests/metrics.test.ts +217 -0
  131. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +390 -0
  132. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +786 -0
  133. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +657 -0
  134. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +443 -0
  135. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +318 -0
  136. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +420 -0
  137. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +309 -0
  138. package/src/resources/extensions/gsd/tests/parsers.test.ts +1351 -0
  139. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +163 -0
  140. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +386 -0
  141. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +171 -0
  142. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
  143. package/src/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
  144. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +521 -0
  145. package/src/resources/extensions/gsd/tests/requirements.test.ts +125 -0
  146. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +34 -0
  147. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
  148. package/src/resources/extensions/gsd/tests/run-uat.test.ts +348 -0
  149. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +247 -0
  150. package/src/resources/extensions/gsd/tests/workflow-config.test.mjs +53 -0
  151. package/src/resources/extensions/gsd/tests/workspace-index.test.ts +94 -0
  152. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +253 -0
  153. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +160 -0
  154. package/src/resources/extensions/gsd/tests/worktree.test.ts +264 -0
  155. package/src/resources/extensions/gsd/types.ts +159 -0
  156. package/src/resources/extensions/gsd/unit-runtime.ts +184 -0
  157. package/src/resources/extensions/gsd/workspace-index.ts +203 -0
  158. package/src/resources/extensions/gsd/worktree-command.ts +845 -0
  159. package/src/resources/extensions/gsd/worktree-manager.ts +392 -0
  160. package/src/resources/extensions/gsd/worktree.ts +183 -0
  161. package/src/resources/extensions/mac-tools/index.ts +852 -0
  162. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  163. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  164. package/src/resources/extensions/mcporter/index.ts +429 -0
  165. package/src/resources/extensions/remote-questions/config.ts +81 -0
  166. package/src/resources/extensions/remote-questions/discord-adapter.ts +128 -0
  167. package/src/resources/extensions/remote-questions/format.ts +163 -0
  168. package/src/resources/extensions/remote-questions/manager.ts +192 -0
  169. package/src/resources/extensions/remote-questions/remote-command.ts +307 -0
  170. package/src/resources/extensions/remote-questions/slack-adapter.ts +92 -0
  171. package/src/resources/extensions/remote-questions/status.ts +31 -0
  172. package/src/resources/extensions/remote-questions/store.ts +77 -0
  173. package/src/resources/extensions/remote-questions/types.ts +75 -0
  174. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  175. package/src/resources/extensions/search-the-web/command-search-provider.ts +95 -0
  176. package/src/resources/extensions/search-the-web/format.ts +258 -0
  177. package/src/resources/extensions/search-the-web/http.ts +238 -0
  178. package/src/resources/extensions/search-the-web/index.ts +65 -0
  179. package/src/resources/extensions/search-the-web/native-search.ts +157 -0
  180. package/src/resources/extensions/search-the-web/provider.ts +118 -0
  181. package/src/resources/extensions/search-the-web/tavily.ts +116 -0
  182. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  183. package/src/resources/extensions/search-the-web/tool-llm-context.ts +561 -0
  184. package/src/resources/extensions/search-the-web/tool-search.ts +576 -0
  185. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  186. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  187. package/src/resources/extensions/shared/interview-ui.ts +613 -0
  188. package/src/resources/extensions/shared/next-action-ui.ts +197 -0
  189. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  190. package/src/resources/extensions/shared/terminal.ts +23 -0
  191. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  192. package/src/resources/extensions/shared/ui.ts +400 -0
  193. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  194. package/src/resources/extensions/slash-commands/audit.ts +88 -0
  195. package/src/resources/extensions/slash-commands/clear.ts +10 -0
  196. package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
  197. package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  198. package/src/resources/extensions/slash-commands/index.ts +12 -0
  199. package/src/resources/extensions/subagent/agents.ts +126 -0
  200. package/src/resources/extensions/subagent/index.ts +1020 -0
  201. package/src/resources/extensions/voice/index.ts +195 -0
  202. package/src/resources/extensions/voice/speech-recognizer.swift +154 -0
  203. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  204. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  205. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  206. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  207. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  208. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  209. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  210. package/src/resources/skills/swiftui/SKILL.md +208 -0
  211. package/src/resources/skills/swiftui/references/animations.md +921 -0
  212. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  213. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  214. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  215. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  216. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  217. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  218. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  219. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  220. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  221. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  222. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  223. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  224. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  225. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  226. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  227. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
@@ -0,0 +1,323 @@
1
+ // Old .planning directory parser orchestrator
2
+ // Walks a .planning directory tree, delegates to per-file parsers,
3
+ // and assembles the complete typed PlanningProject.
4
+ // Zero Pi dependencies — uses only Node built-ins + local parsers.
5
+
6
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
7
+ import { join, basename } from 'node:path';
8
+
9
+ import {
10
+ parseOldRoadmap,
11
+ parseOldPlan,
12
+ parseOldSummary,
13
+ parseOldRequirements,
14
+ parseOldProject,
15
+ parseOldState,
16
+ parseOldConfig,
17
+ } from './parsers.ts';
18
+ import { validatePlanningDirectory } from './validator.ts';
19
+
20
+ import type {
21
+ PlanningProject,
22
+ PlanningPhase,
23
+ PlanningQuickTask,
24
+ PlanningMilestone,
25
+ PlanningResearch,
26
+ PlanningPhaseFile,
27
+ } from './types.ts';
28
+
29
+ // ─── Helpers ───────────────────────────────────────────────────────────────
30
+
31
+ /** Read a file, returning null if it doesn't exist. */
32
+ function readOptional(path: string): string | null {
33
+ try {
34
+ return readFileSync(path, 'utf-8');
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /** List directory entries (names only), returning [] if dir doesn't exist. */
41
+ function listDir(path: string): string[] {
42
+ try {
43
+ return readdirSync(path);
44
+ } catch {
45
+ return [];
46
+ }
47
+ }
48
+
49
+ /** Check if a path is a directory. */
50
+ function isDir(path: string): boolean {
51
+ try {
52
+ return statSync(path).isDirectory();
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /** Extract phase number and slug from a directory name like "29-auth-system" or "01.2-setup". */
59
+ function parsePhaseDir(dirName: string): { number: number; slug: string } | null {
60
+ const match = dirName.match(/^(\d+(?:\.\d+)?)-(.+)$/);
61
+ if (!match) return null;
62
+ return { number: parseFloat(match[1]), slug: match[2] };
63
+ }
64
+
65
+ /** Extract quick task number and slug from a directory name like "001-fix-login". */
66
+ function parseQuickDir(dirName: string): { number: number; slug: string } | null {
67
+ const match = dirName.match(/^(\d+)-(.+)$/);
68
+ if (!match) return null;
69
+ return { number: parseInt(match[1], 10), slug: match[2] };
70
+ }
71
+
72
+ // ─── Phase Scanner ─────────────────────────────────────────────────────────
73
+
74
+ /** Plan file pattern: NN-NN-PLAN.md (e.g. 29-01-PLAN.md) */
75
+ const PLAN_RE = /^(\d+(?:\.\d+)?)-(\d+)-PLAN\.md$/i;
76
+
77
+ /** Summary file pattern: NN-NN-SUMMARY.md (e.g. 29-01-SUMMARY.md) */
78
+ const SUMMARY_RE = /^(\d+(?:\.\d+)?)-(\d+)-SUMMARY\.md$/i;
79
+
80
+ /** Research file pattern: contains RESEARCH (case-insensitive) */
81
+ const RESEARCH_RE = /research/i;
82
+
83
+ /** Verification file pattern: contains VERIFICATION (case-insensitive) */
84
+ const VERIFICATION_RE = /verification/i;
85
+
86
+ function scanPhaseDirectory(phaseDir: string, dirName: string, parsed: ReturnType<typeof parsePhaseDir>): PlanningPhase {
87
+ const phase: PlanningPhase = {
88
+ dirName,
89
+ number: parsed!.number,
90
+ slug: parsed!.slug,
91
+ plans: {},
92
+ summaries: {},
93
+ research: [],
94
+ verifications: [],
95
+ extraFiles: [],
96
+ };
97
+
98
+ const entries = listDir(phaseDir);
99
+
100
+ for (const entry of entries) {
101
+ const entryPath = join(phaseDir, entry);
102
+
103
+ // Skip directories within phase dirs
104
+ if (isDir(entryPath)) continue;
105
+
106
+ const planMatch = entry.match(PLAN_RE);
107
+ if (planMatch) {
108
+ const planNumber = planMatch[2];
109
+ const content = readFileSync(entryPath, 'utf-8');
110
+ phase.plans[planNumber] = parseOldPlan(content, entry, planNumber);
111
+ continue;
112
+ }
113
+
114
+ const summaryMatch = entry.match(SUMMARY_RE);
115
+ if (summaryMatch) {
116
+ const planNumber = summaryMatch[2];
117
+ const content = readFileSync(entryPath, 'utf-8');
118
+ phase.summaries[planNumber] = parseOldSummary(content, entry, planNumber);
119
+ continue;
120
+ }
121
+
122
+ if (VERIFICATION_RE.test(entry)) {
123
+ const content = readFileSync(entryPath, 'utf-8');
124
+ phase.verifications.push({ fileName: entry, content });
125
+ continue;
126
+ }
127
+
128
+ if (RESEARCH_RE.test(entry)) {
129
+ const content = readFileSync(entryPath, 'utf-8');
130
+ phase.research.push({ fileName: entry, content });
131
+ continue;
132
+ }
133
+
134
+ // Everything else is an extra file
135
+ const content = readFileSync(entryPath, 'utf-8');
136
+ phase.extraFiles.push({ fileName: entry, content });
137
+ }
138
+
139
+ return phase;
140
+ }
141
+
142
+ // ─── Quick Task Scanner ────────────────────────────────────────────────────
143
+
144
+ function scanQuickDirectory(quickDir: string): PlanningQuickTask[] {
145
+ const tasks: PlanningQuickTask[] = [];
146
+ const entries = listDir(quickDir).sort();
147
+
148
+ for (const dirName of entries) {
149
+ const dirPath = join(quickDir, dirName);
150
+ if (!isDir(dirPath)) continue;
151
+
152
+ const parsed = parseQuickDir(dirName);
153
+ if (!parsed) continue;
154
+
155
+ // Look for NNN-PLAN.md and NNN-SUMMARY.md
156
+ const files = listDir(dirPath);
157
+ let plan: string | null = null;
158
+ let summary: string | null = null;
159
+
160
+ for (const file of files) {
161
+ if (/^\d+-PLAN\.md$/i.test(file)) {
162
+ plan = readFileSync(join(dirPath, file), 'utf-8');
163
+ } else if (/^\d+-SUMMARY\.md$/i.test(file)) {
164
+ summary = readFileSync(join(dirPath, file), 'utf-8');
165
+ }
166
+ }
167
+
168
+ tasks.push({
169
+ dirName,
170
+ number: parsed.number,
171
+ slug: parsed.slug,
172
+ plan,
173
+ summary,
174
+ });
175
+ }
176
+
177
+ return tasks;
178
+ }
179
+
180
+ // ─── Milestones Scanner ────────────────────────────────────────────────────
181
+
182
+ function scanMilestonesDirectory(msDir: string): PlanningMilestone[] {
183
+ const entries = listDir(msDir);
184
+ if (entries.length === 0) return [];
185
+
186
+ // Group files by milestone ID prefix (e.g. "v2.2" from "v2.2-ROADMAP.md")
187
+ const grouped = new Map<string, { requirements: string | null; roadmap: string | null; extraFiles: PlanningPhaseFile[] }>();
188
+
189
+ for (const entry of entries) {
190
+ const entryPath = join(msDir, entry);
191
+ if (isDir(entryPath)) continue;
192
+
193
+ // Extract milestone ID: everything before the first dash-followed-by-uppercase or common suffix
194
+ const idMatch = entry.match(/^(.+?)-(ROADMAP|REQUIREMENTS|SUMMARY)\.md$/i);
195
+ if (idMatch) {
196
+ const id = idMatch[1];
197
+ const type = idMatch[2].toUpperCase();
198
+ if (!grouped.has(id)) grouped.set(id, { requirements: null, roadmap: null, extraFiles: [] });
199
+ const ms = grouped.get(id)!;
200
+ const content = readFileSync(entryPath, 'utf-8');
201
+
202
+ if (type === 'REQUIREMENTS') ms.requirements = content;
203
+ else if (type === 'ROADMAP') ms.roadmap = content;
204
+ else ms.extraFiles.push({ fileName: entry, content });
205
+ } else {
206
+ // Non-standard file — try to extract ID from filename
207
+ const simpleMatch = entry.match(/^(.+?)\./);
208
+ const id = simpleMatch ? simpleMatch[1] : entry;
209
+ if (!grouped.has(id)) grouped.set(id, { requirements: null, roadmap: null, extraFiles: [] });
210
+ const content = readFileSync(entryPath, 'utf-8');
211
+ grouped.get(id)!.extraFiles.push({ fileName: entry, content });
212
+ }
213
+ }
214
+
215
+ return Array.from(grouped.entries()).map(([id, data]) => ({
216
+ id,
217
+ requirements: data.requirements,
218
+ roadmap: data.roadmap,
219
+ extraFiles: data.extraFiles,
220
+ }));
221
+ }
222
+
223
+ // ─── Research Scanner ──────────────────────────────────────────────────────
224
+
225
+ function scanResearchDirectory(researchDir: string): PlanningResearch[] {
226
+ const entries = listDir(researchDir);
227
+ const research: PlanningResearch[] = [];
228
+
229
+ for (const entry of entries) {
230
+ const entryPath = join(researchDir, entry);
231
+ if (isDir(entryPath)) continue;
232
+ const content = readFileSync(entryPath, 'utf-8');
233
+ research.push({ fileName: entry, content });
234
+ }
235
+
236
+ return research;
237
+ }
238
+
239
+ // ─── Main Orchestrator ─────────────────────────────────────────────────────
240
+
241
+ /**
242
+ * Parse an old .planning directory into a complete typed PlanningProject.
243
+ *
244
+ * Handles:
245
+ * - Top-level files: PROJECT.md, ROADMAP.md, REQUIREMENTS.md, STATE.md, config.json
246
+ * - Phase directories with plans, summaries, research, verification, extras
247
+ * - Duplicate phase numbers (full directory name as key)
248
+ * - .archive/ skipping
249
+ * - Orphan summaries (summaries without matching plans)
250
+ * - Quick tasks from quick/ directory
251
+ * - Milestones from milestones/ directory
252
+ * - Research from research/ directory
253
+ *
254
+ * Missing files produce null values, not thrown errors.
255
+ * Use validatePlanningDirectory() for pre-flight structural checks.
256
+ */
257
+ export async function parsePlanningDirectory(path: string): Promise<PlanningProject> {
258
+ // Run validation first
259
+ const validation = await validatePlanningDirectory(path);
260
+
261
+ // Parse top-level files
262
+ const projectContent = readOptional(join(path, 'PROJECT.md'));
263
+ const project = projectContent !== null ? parseOldProject(projectContent) : null;
264
+
265
+ const roadmapContent = readOptional(join(path, 'ROADMAP.md'));
266
+ const roadmap = roadmapContent !== null ? parseOldRoadmap(roadmapContent) : null;
267
+
268
+ const reqContent = readOptional(join(path, 'REQUIREMENTS.md'));
269
+ const requirements = reqContent !== null ? parseOldRequirements(reqContent) : [];
270
+
271
+ const stateContent = readOptional(join(path, 'STATE.md'));
272
+ const state = stateContent !== null ? parseOldState(stateContent) : null;
273
+
274
+ const configContent = readOptional(join(path, 'config.json'));
275
+ const config = configContent !== null ? parseOldConfig(configContent) : null;
276
+
277
+ // Scan phases/ directory
278
+ const phases: Record<string, PlanningPhase> = {};
279
+ const phasesDir = join(path, 'phases');
280
+
281
+ if (isDir(phasesDir)) {
282
+ const phaseDirs = listDir(phasesDir).sort();
283
+
284
+ for (const dirName of phaseDirs) {
285
+ // Skip .archive and hidden directories
286
+ if (dirName.startsWith('.')) continue;
287
+
288
+ const dirPath = join(phasesDir, dirName);
289
+ if (!isDir(dirPath)) continue;
290
+
291
+ const parsed = parsePhaseDir(dirName);
292
+ if (!parsed) continue;
293
+
294
+ phases[dirName] = scanPhaseDirectory(dirPath, dirName, parsed);
295
+ }
296
+ }
297
+
298
+ // Scan quick/ directory
299
+ const quickDir = join(path, 'quick');
300
+ const quickTasks = isDir(quickDir) ? scanQuickDirectory(quickDir) : [];
301
+
302
+ // Scan milestones/ directory
303
+ const msDir = join(path, 'milestones');
304
+ const milestones = isDir(msDir) ? scanMilestonesDirectory(msDir) : [];
305
+
306
+ // Scan research/ directory
307
+ const researchDir = join(path, 'research');
308
+ const research = isDir(researchDir) ? scanResearchDirectory(researchDir) : [];
309
+
310
+ return {
311
+ path,
312
+ project,
313
+ roadmap,
314
+ requirements,
315
+ state,
316
+ config,
317
+ phases,
318
+ quickTasks,
319
+ milestones,
320
+ research,
321
+ validation,
322
+ };
323
+ }