@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,420 @@
1
+ // Migration writer format round-trip test suite
2
+ // Tests that format functions produce output that parses back correctly
3
+ // through parseRoadmap(), parsePlan(), parseSummary(), and parseRequirementCounts().
4
+ // Pure in-memory tests — no filesystem needed.
5
+
6
+ import {
7
+ formatRoadmap,
8
+ formatPlan,
9
+ formatSliceSummary,
10
+ formatTaskSummary,
11
+ formatTaskPlan,
12
+ formatRequirements,
13
+ formatProject,
14
+ formatDecisions,
15
+ formatContext,
16
+ formatState,
17
+ } from '../migrate/writer.ts';
18
+ import {
19
+ parseRoadmap,
20
+ parsePlan,
21
+ parseSummary,
22
+ parseRequirementCounts,
23
+ } from '../files.ts';
24
+ import type {
25
+ GSDMilestone,
26
+ GSDSlice,
27
+ GSDTask,
28
+ GSDRequirement,
29
+ GSDSliceSummaryData,
30
+ GSDTaskSummaryData,
31
+ } from '../migrate/types.ts';
32
+
33
+ let passed = 0;
34
+ let failed = 0;
35
+
36
+ function assert(condition: boolean, message: string): void {
37
+ if (condition) {
38
+ passed++;
39
+ } else {
40
+ failed++;
41
+ console.error(`FAIL: ${message}`);
42
+ }
43
+ }
44
+
45
+ function assertEq(actual: unknown, expected: unknown, message: string): void {
46
+ const a = JSON.stringify(actual);
47
+ const e = JSON.stringify(expected);
48
+ if (a === e) {
49
+ passed++;
50
+ } else {
51
+ failed++;
52
+ console.error(`FAIL: ${message} — expected ${e}, got ${a}`);
53
+ }
54
+ }
55
+
56
+ // ─── Test Data Builders ────────────────────────────────────────────────────
57
+
58
+ function makeTask(overrides: Partial<GSDTask> = {}): GSDTask {
59
+ return {
60
+ id: 'T01',
61
+ title: 'Setup Auth',
62
+ description: 'Implement authentication',
63
+ done: false,
64
+ estimate: '30m',
65
+ files: ['src/auth.ts'],
66
+ mustHaves: ['JWT support'],
67
+ summary: null,
68
+ ...overrides,
69
+ };
70
+ }
71
+
72
+ function makeSlice(overrides: Partial<GSDSlice> = {}): GSDSlice {
73
+ return {
74
+ id: 'S01',
75
+ title: 'Auth System',
76
+ risk: 'medium' as const,
77
+ depends: [],
78
+ done: false,
79
+ demo: 'Login flow works end-to-end',
80
+ goal: 'Working authentication',
81
+ tasks: [makeTask()],
82
+ research: null,
83
+ summary: null,
84
+ ...overrides,
85
+ };
86
+ }
87
+
88
+ function makeMilestone(overrides: Partial<GSDMilestone> = {}): GSDMilestone {
89
+ return {
90
+ id: 'M001',
91
+ title: 'Core Platform',
92
+ vision: 'Build the core platform',
93
+ successCriteria: ['All tests pass', 'Deploy to staging'],
94
+ slices: [makeSlice()],
95
+ research: null,
96
+ boundaryMap: [],
97
+ ...overrides,
98
+ };
99
+ }
100
+
101
+ function makeSliceSummary(overrides: Partial<GSDSliceSummaryData> = {}): GSDSliceSummaryData {
102
+ return {
103
+ completedAt: '2026-03-10',
104
+ provides: ['auth-flow', 'jwt-tokens'],
105
+ keyFiles: ['src/auth.ts', 'src/middleware.ts'],
106
+ keyDecisions: ['Use JWT over sessions'],
107
+ patternsEstablished: ['Middleware pattern'],
108
+ duration: '2h',
109
+ whatHappened: 'Implemented full auth system with JWT.',
110
+ ...overrides,
111
+ };
112
+ }
113
+
114
+ function makeTaskSummary(overrides: Partial<GSDTaskSummaryData> = {}): GSDTaskSummaryData {
115
+ return {
116
+ completedAt: '2026-03-09',
117
+ provides: ['auth-endpoint'],
118
+ keyFiles: ['src/auth.ts'],
119
+ duration: '45m',
120
+ whatHappened: 'Built the auth endpoint.',
121
+ ...overrides,
122
+ };
123
+ }
124
+
125
+ // ═══════════════════════════════════════════════════════════════════════════
126
+ // Scenario A: Roadmap round-trip with 2 slices (1 done, 1 not)
127
+ // ═══════════════════════════════════════════════════════════════════════════
128
+
129
+ {
130
+ const milestone = makeMilestone({
131
+ slices: [
132
+ makeSlice({
133
+ id: 'S01',
134
+ title: 'Auth System',
135
+ risk: 'high',
136
+ depends: [],
137
+ done: true,
138
+ demo: 'Login flow works',
139
+ }),
140
+ makeSlice({
141
+ id: 'S02',
142
+ title: 'Dashboard',
143
+ risk: 'low',
144
+ depends: ['S01'],
145
+ done: false,
146
+ demo: 'Dashboard renders data',
147
+ }),
148
+ ],
149
+ });
150
+
151
+ const output = formatRoadmap(milestone);
152
+ const parsed = parseRoadmap(output);
153
+
154
+ assertEq(parsed.title, 'M001: Core Platform', 'roadmap: title');
155
+ assertEq(parsed.vision, 'Build the core platform', 'roadmap: vision');
156
+ assertEq(parsed.successCriteria.length, 2, 'roadmap: successCriteria count');
157
+ assertEq(parsed.successCriteria[0], 'All tests pass', 'roadmap: successCriteria[0]');
158
+ assertEq(parsed.successCriteria[1], 'Deploy to staging', 'roadmap: successCriteria[1]');
159
+ assertEq(parsed.slices.length, 2, 'roadmap: slices count');
160
+
161
+ assertEq(parsed.slices[0].id, 'S01', 'roadmap: S01 id');
162
+ assertEq(parsed.slices[0].title, 'Auth System', 'roadmap: S01 title');
163
+ assertEq(parsed.slices[0].done, true, 'roadmap: S01 done');
164
+ assertEq(parsed.slices[0].risk, 'high', 'roadmap: S01 risk');
165
+ assertEq(parsed.slices[0].depends.length, 0, 'roadmap: S01 depends empty');
166
+ assertEq(parsed.slices[0].demo, 'Login flow works', 'roadmap: S01 demo');
167
+
168
+ assertEq(parsed.slices[1].id, 'S02', 'roadmap: S02 id');
169
+ assertEq(parsed.slices[1].title, 'Dashboard', 'roadmap: S02 title');
170
+ assertEq(parsed.slices[1].done, false, 'roadmap: S02 done');
171
+ assertEq(parsed.slices[1].risk, 'low', 'roadmap: S02 risk');
172
+ assertEq(parsed.slices[1].depends, ['S01'], 'roadmap: S02 depends');
173
+ assertEq(parsed.slices[1].demo, 'Dashboard renders data', 'roadmap: S02 demo');
174
+
175
+ assertEq(parsed.boundaryMap.length, 0, 'roadmap: boundaryMap empty');
176
+ }
177
+
178
+ // ═══════════════════════════════════════════════════════════════════════════
179
+ // Scenario B: Plan round-trip with 3 tasks (mixed done)
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+
182
+ {
183
+ const slice = makeSlice({
184
+ id: 'S01',
185
+ title: 'Auth System',
186
+ goal: 'Working authentication system',
187
+ demo: 'Login works with valid credentials',
188
+ tasks: [
189
+ makeTask({ id: 'T01', title: 'Setup Models', done: true, estimate: '15m', description: 'Define user model' }),
190
+ makeTask({ id: 'T02', title: 'Build Endpoints', done: false, estimate: '30m', description: 'REST API endpoints' }),
191
+ makeTask({ id: 'T03', title: 'Write Tests', done: true, estimate: '20m', description: 'Unit and integration tests' }),
192
+ ],
193
+ });
194
+
195
+ const output = formatPlan(slice);
196
+ const parsed = parsePlan(output);
197
+
198
+ assertEq(parsed.id, 'S01', 'plan: id');
199
+ assertEq(parsed.title, 'Auth System', 'plan: title');
200
+ assertEq(parsed.goal, 'Working authentication system', 'plan: goal');
201
+ assertEq(parsed.demo, 'Login works with valid credentials', 'plan: demo');
202
+ assertEq(parsed.tasks.length, 3, 'plan: tasks count');
203
+
204
+ assertEq(parsed.tasks[0].id, 'T01', 'plan: T01 id');
205
+ assertEq(parsed.tasks[0].title, 'Setup Models', 'plan: T01 title');
206
+ assertEq(parsed.tasks[0].done, true, 'plan: T01 done');
207
+ assertEq(parsed.tasks[0].estimate, '15m', 'plan: T01 estimate');
208
+
209
+ assertEq(parsed.tasks[1].id, 'T02', 'plan: T02 id');
210
+ assertEq(parsed.tasks[1].done, false, 'plan: T02 done');
211
+ assertEq(parsed.tasks[1].estimate, '30m', 'plan: T02 estimate');
212
+
213
+ assertEq(parsed.tasks[2].id, 'T03', 'plan: T03 id');
214
+ assertEq(parsed.tasks[2].done, true, 'plan: T03 done');
215
+ assertEq(parsed.tasks[2].estimate, '20m', 'plan: T03 estimate');
216
+ }
217
+
218
+ // ═══════════════════════════════════════════════════════════════════════════
219
+ // Scenario C: Slice summary round-trip with full data
220
+ // ═══════════════════════════════════════════════════════════════════════════
221
+
222
+ {
223
+ const slice = makeSlice({
224
+ id: 'S01',
225
+ title: 'Auth System',
226
+ done: true,
227
+ summary: makeSliceSummary(),
228
+ });
229
+
230
+ const output = formatSliceSummary(slice, 'M001');
231
+ const parsed = parseSummary(output);
232
+
233
+ assertEq(parsed.frontmatter.id, 'S01', 'sliceSummary: id');
234
+ assertEq(parsed.frontmatter.parent, 'M001', 'sliceSummary: parent');
235
+ assertEq(parsed.frontmatter.milestone, 'M001', 'sliceSummary: milestone');
236
+ assertEq(parsed.frontmatter.provides, ['auth-flow', 'jwt-tokens'], 'sliceSummary: provides');
237
+ assertEq(parsed.frontmatter.requires.length, 0, 'sliceSummary: requires empty');
238
+ assertEq(parsed.frontmatter.affects.length, 0, 'sliceSummary: affects empty');
239
+ assertEq(parsed.frontmatter.key_files, ['src/auth.ts', 'src/middleware.ts'], 'sliceSummary: key_files');
240
+ assertEq(parsed.frontmatter.key_decisions, ['Use JWT over sessions'], 'sliceSummary: key_decisions');
241
+ assertEq(parsed.frontmatter.patterns_established, ['Middleware pattern'], 'sliceSummary: patterns_established');
242
+ assertEq(parsed.frontmatter.duration, '2h', 'sliceSummary: duration');
243
+ assertEq(parsed.frontmatter.completed_at, '2026-03-10', 'sliceSummary: completed_at');
244
+ assertEq(parsed.frontmatter.verification_result, 'passed', 'sliceSummary: verification_result');
245
+ assertEq(parsed.frontmatter.blocker_discovered, false, 'sliceSummary: blocker_discovered');
246
+ assert(parsed.whatHappened.includes('Implemented full auth system'), 'sliceSummary: whatHappened content');
247
+ assertEq(parsed.title, 'S01: Auth System', 'sliceSummary: title');
248
+ }
249
+
250
+ // ═══════════════════════════════════════════════════════════════════════════
251
+ // Scenario D: Task summary round-trip
252
+ // ═══════════════════════════════════════════════════════════════════════════
253
+
254
+ {
255
+ const task = makeTask({
256
+ id: 'T01',
257
+ title: 'Setup Auth',
258
+ done: true,
259
+ summary: makeTaskSummary(),
260
+ });
261
+
262
+ const output = formatTaskSummary(task, 'S01', 'M001');
263
+ const parsed = parseSummary(output);
264
+
265
+ assertEq(parsed.frontmatter.id, 'T01', 'taskSummary: id');
266
+ assertEq(parsed.frontmatter.parent, 'S01', 'taskSummary: parent');
267
+ assertEq(parsed.frontmatter.milestone, 'M001', 'taskSummary: milestone');
268
+ assertEq(parsed.frontmatter.provides, ['auth-endpoint'], 'taskSummary: provides');
269
+ assertEq(parsed.frontmatter.key_files, ['src/auth.ts'], 'taskSummary: key_files');
270
+ assertEq(parsed.frontmatter.duration, '45m', 'taskSummary: duration');
271
+ assertEq(parsed.frontmatter.completed_at, '2026-03-09', 'taskSummary: completed_at');
272
+ assert(parsed.whatHappened.includes('Built the auth endpoint'), 'taskSummary: whatHappened content');
273
+ assertEq(parsed.title, 'T01: Setup Auth', 'taskSummary: title');
274
+ }
275
+
276
+ // ═══════════════════════════════════════════════════════════════════════════
277
+ // Scenario E: Requirements round-trip with mixed statuses
278
+ // ═══════════════════════════════════════════════════════════════════════════
279
+
280
+ {
281
+ const requirements: GSDRequirement[] = [
282
+ { id: 'R001', title: 'Auth Required', class: 'core-capability', status: 'active', description: 'Must have auth', source: 'spec', primarySlice: 'S01' },
283
+ { id: 'R002', title: 'Logging', class: 'observability', status: 'active', description: 'Must log', source: 'spec', primarySlice: 'S02' },
284
+ { id: 'R003', title: 'OAuth Support', class: 'core-capability', status: 'validated', description: 'OAuth working', source: 'testing', primarySlice: 'S01' },
285
+ { id: 'R004', title: 'Dark Mode', class: 'ui', status: 'deferred', description: 'Nice to have', source: 'feedback', primarySlice: 'none' },
286
+ { id: 'R005', title: 'Legacy API', class: 'compat', status: 'out-of-scope', description: 'Dropped', source: 'decision', primarySlice: 'none' },
287
+ ];
288
+
289
+ const output = formatRequirements(requirements);
290
+ const counts = parseRequirementCounts(output);
291
+
292
+ assertEq(counts.active, 2, 'requirements: active count');
293
+ assertEq(counts.validated, 1, 'requirements: validated count');
294
+ assertEq(counts.deferred, 1, 'requirements: deferred count');
295
+ assertEq(counts.outOfScope, 1, 'requirements: outOfScope count');
296
+ assertEq(counts.total, 5, 'requirements: total count');
297
+ }
298
+
299
+ // ═══════════════════════════════════════════════════════════════════════════
300
+ // Scenario F: Edge cases
301
+ // ═══════════════════════════════════════════════════════════════════════════
302
+
303
+ // F1: Empty vision → fallback text
304
+ {
305
+ const milestone = makeMilestone({ vision: '' });
306
+ const output = formatRoadmap(milestone);
307
+ const parsed = parseRoadmap(output);
308
+ assertEq(parsed.vision, '(migrated project)', 'edge: empty vision fallback');
309
+ }
310
+
311
+ // F2: Empty successCriteria → empty array
312
+ {
313
+ const milestone = makeMilestone({ successCriteria: [] });
314
+ const output = formatRoadmap(milestone);
315
+ const parsed = parseRoadmap(output);
316
+ assertEq(parsed.successCriteria.length, 0, 'edge: empty successCriteria');
317
+ }
318
+
319
+ // F3: Empty tasks → empty array in parsed plan
320
+ {
321
+ const slice = makeSlice({ tasks: [] });
322
+ const output = formatPlan(slice);
323
+ const parsed = parsePlan(output);
324
+ assertEq(parsed.tasks.length, 0, 'edge: empty tasks');
325
+ }
326
+
327
+ // F4: Null summary → empty string from formatSliceSummary
328
+ {
329
+ const slice = makeSlice({ summary: null });
330
+ const output = formatSliceSummary(slice, 'M001');
331
+ assertEq(output, '', 'edge: null summary returns empty string');
332
+ }
333
+
334
+ // F5: Done=true checkbox in roadmap
335
+ {
336
+ const milestone = makeMilestone({
337
+ slices: [makeSlice({ id: 'S01', done: true })],
338
+ });
339
+ const output = formatRoadmap(milestone);
340
+ const parsed = parseRoadmap(output);
341
+ assertEq(parsed.slices[0].done, true, 'edge: done checkbox true');
342
+ }
343
+
344
+ // F6: Done=false checkbox in roadmap
345
+ {
346
+ const milestone = makeMilestone({
347
+ slices: [makeSlice({ id: 'S01', done: false })],
348
+ });
349
+ const output = formatRoadmap(milestone);
350
+ const parsed = parseRoadmap(output);
351
+ assertEq(parsed.slices[0].done, false, 'edge: done checkbox false');
352
+ }
353
+
354
+ // F7: Null task summary → empty string from formatTaskSummary
355
+ {
356
+ const task = makeTask({ summary: null });
357
+ const output = formatTaskSummary(task, 'S01', 'M001');
358
+ assertEq(output, '', 'edge: null task summary returns empty string');
359
+ }
360
+
361
+ // F8: Empty requirements → all zeros
362
+ {
363
+ const output = formatRequirements([]);
364
+ const counts = parseRequirementCounts(output);
365
+ assertEq(counts.total, 0, 'edge: empty requirements total 0');
366
+ }
367
+
368
+ // F9: formatProject with empty content → produces valid stub
369
+ {
370
+ const output = formatProject('');
371
+ assert(output.includes('# Project'), 'edge: empty project has heading');
372
+ assert(output.length > 10, 'edge: empty project not blank');
373
+ }
374
+
375
+ // F10: formatProject with existing content → passes through
376
+ {
377
+ const content = '# My Project\n\nDescription here.\n';
378
+ const output = formatProject(content);
379
+ assertEq(output, content, 'edge: project passthrough');
380
+ }
381
+
382
+ // F11: formatDecisions with empty content → produces valid stub
383
+ {
384
+ const output = formatDecisions('');
385
+ assert(output.includes('# Decisions'), 'edge: empty decisions has heading');
386
+ }
387
+
388
+ // F12: formatContext produces valid content
389
+ {
390
+ const output = formatContext('M001');
391
+ assert(output.includes('M001'), 'edge: context mentions milestone');
392
+ }
393
+
394
+ // F13: formatState produces valid content
395
+ {
396
+ const milestones = [makeMilestone({
397
+ slices: [
398
+ makeSlice({ done: true }),
399
+ makeSlice({ id: 'S02', done: false }),
400
+ ],
401
+ })];
402
+ const output = formatState(milestones);
403
+ assert(output.includes('1/2'), 'edge: state shows slice progress');
404
+ }
405
+
406
+ // F14: Task with no estimate → no est backtick in plan
407
+ {
408
+ const slice = makeSlice({
409
+ tasks: [makeTask({ id: 'T01', title: 'Quick Fix', estimate: '' })],
410
+ });
411
+ const output = formatPlan(slice);
412
+ const parsed = parsePlan(output);
413
+ assertEq(parsed.tasks[0].id, 'T01', 'edge: task no estimate id');
414
+ assertEq(parsed.tasks[0].estimate, '', 'edge: task no estimate empty');
415
+ }
416
+
417
+ // ═══════════════════════════════════════════════════════════════════════════
418
+
419
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
420
+ if (failed > 0) process.exit(1);