@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,38 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ let passed = 0;
5
+ let failed = 0;
6
+
7
+ function assert(condition: boolean, message: string): void {
8
+ if (condition) passed++;
9
+ else {
10
+ failed++;
11
+ console.error(` FAIL: ${message}`);
12
+ }
13
+ }
14
+
15
+ const promptPath = join(process.cwd(), 'src/resources/extensions/gsd/prompts/discuss.md');
16
+ const discussPrompt = readFileSync(promptPath, 'utf-8');
17
+
18
+ console.log('\n=== discuss prompt: resilient vision framing ===');
19
+ {
20
+ const hardenedPattern = /Say exactly:\s*"What's the vision\?"/;
21
+ assert(!hardenedPattern.test(discussPrompt), 'prompt no longer uses exact-verbosity lock');
22
+ assert(
23
+ discussPrompt.includes('Ask: "What\'s the vision?" once'),
24
+ 'prompt asks for vision exactly once',
25
+ );
26
+ assert(
27
+ discussPrompt.includes('Special handling'),
28
+ 'prompt documents special handling for non-vision user messages',
29
+ );
30
+ assert(
31
+ discussPrompt.includes('instead of repeating "What\'s the vision?"'),
32
+ 'prompt forbids repeating the vision question',
33
+ );
34
+ }
35
+
36
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
37
+ if (failed > 0) process.exit(1);
38
+ console.log('All tests passed ✓');
@@ -0,0 +1,505 @@
1
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+
5
+ import { formatDoctorReport, runGSDDoctor, summarizeDoctorIssues, filterDoctorIssues, selectDoctorScope } from "../doctor.js";
6
+
7
+ let passed = 0;
8
+ let failed = 0;
9
+
10
+ function assert(condition: boolean, message: string): void {
11
+ if (condition) {
12
+ passed++;
13
+ } else {
14
+ failed++;
15
+ console.error(` FAIL: ${message}`);
16
+ }
17
+ }
18
+
19
+ function assertEq<T>(actual: T, expected: T, message: string): void {
20
+ if (JSON.stringify(actual) === JSON.stringify(expected)) {
21
+ passed++;
22
+ } else {
23
+ failed++;
24
+ console.error(` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
25
+ }
26
+ }
27
+
28
+ const tmpBase = mkdtempSync(join(tmpdir(), "gsd-doctor-test-"));
29
+ const gsd = join(tmpBase, ".gsd");
30
+ const mDir = join(gsd, "milestones", "M001");
31
+ const sDir = join(mDir, "slices", "S01");
32
+ const tDir = join(sDir, "tasks");
33
+ mkdirSync(tDir, { recursive: true });
34
+
35
+ writeFileSync(join(mDir, "M001-ROADMAP.md"), `# M001: Test Milestone
36
+
37
+ ## Slices
38
+ - [ ] **S01: Demo Slice** \`risk:low\` \`depends:[]\`
39
+ > After this: demo works
40
+ `);
41
+
42
+ writeFileSync(join(sDir, "S01-PLAN.md"), `# S01: Demo Slice
43
+
44
+ **Goal:** Demo
45
+ **Demo:** Demo
46
+
47
+ ## Must-Haves
48
+ - done
49
+
50
+ ## Tasks
51
+ - [x] **T01: Implement thing** \`est:10m\`
52
+ Task is complete.
53
+ `);
54
+
55
+ writeFileSync(join(tDir, "T01-SUMMARY.md"), `---
56
+ id: T01
57
+ parent: S01
58
+ milestone: M001
59
+ provides: []
60
+ requires: []
61
+ affects: []
62
+ key_files: []
63
+ key_decisions: []
64
+ patterns_established: []
65
+ observability_surfaces: []
66
+ drill_down_paths: []
67
+ duration: 10m
68
+ verification_result: passed
69
+ completed_at: 2026-03-09T00:00:00Z
70
+ ---
71
+
72
+ # T01: Implement thing
73
+
74
+ **Done**
75
+
76
+ ## What Happened
77
+ Implemented.
78
+
79
+ ## Diagnostics
80
+ - log
81
+ `);
82
+
83
+ async function main(): Promise<void> {
84
+ console.log("\n=== doctor diagnose ===");
85
+ {
86
+ const report = await runGSDDoctor(tmpBase, { fix: false });
87
+ assert(!report.ok, "report is not ok when completion artifacts are missing");
88
+ assert(report.issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary"), "detects missing slice summary");
89
+ assert(report.issues.some(issue => issue.code === "all_tasks_done_missing_slice_uat"), "detects missing slice UAT");
90
+ }
91
+
92
+ console.log("\n=== doctor formatting ===");
93
+ {
94
+ const report = await runGSDDoctor(tmpBase, { fix: false });
95
+ const summary = summarizeDoctorIssues(report.issues);
96
+ assertEq(summary.errors, 2, "two blocking errors in summary");
97
+ const scoped = filterDoctorIssues(report.issues, { scope: "M001/S01", includeWarnings: true });
98
+ assert(scoped.length >= 2, "scope filter keeps slice issues");
99
+ const text = formatDoctorReport(report, { scope: "M001/S01", includeWarnings: true, maxIssues: 5 });
100
+ assert(text.includes("Scope: M001/S01"), "formatted report shows scope");
101
+ assert(text.includes("Top issue types:"), "formatted report shows grouped issue types");
102
+ }
103
+
104
+ console.log("\n=== doctor default scope ===");
105
+ {
106
+ const scope = await selectDoctorScope(tmpBase);
107
+ assertEq(scope, "M001/S01", "default doctor scope targets the active slice");
108
+ }
109
+
110
+ console.log("\n=== doctor fix ===");
111
+ {
112
+ const report = await runGSDDoctor(tmpBase, { fix: true });
113
+ if (report.fixesApplied.length < 3) console.error(report);
114
+ assert(report.fixesApplied.length >= 3, "applies multiple fixes");
115
+ assert(existsSync(join(sDir, "S01-SUMMARY.md")), "creates placeholder slice summary");
116
+ assert(existsSync(join(sDir, "S01-UAT.md")), "creates placeholder UAT");
117
+
118
+ const plan = readFileSync(join(sDir, "S01-PLAN.md"), "utf-8");
119
+ assert(plan.includes("- [x] **T01:"), "marks task checkbox done");
120
+
121
+ const roadmap = readFileSync(join(mDir, "M001-ROADMAP.md"), "utf-8");
122
+ assert(roadmap.includes("- [x] **S01:"), "marks slice checkbox done");
123
+
124
+ const state = readFileSync(join(gsd, "STATE.md"), "utf-8");
125
+ assert(state.includes("# GSD State"), "writes state file");
126
+ }
127
+
128
+ rmSync(tmpBase, { recursive: true, force: true });
129
+
130
+ // ─── Milestone summary detection: missing summary ──────────────────────
131
+ console.log("\n=== doctor detects missing milestone summary ===");
132
+ {
133
+ const msBase = mkdtempSync(join(tmpdir(), "gsd-doctor-ms-test-"));
134
+ const msGsd = join(msBase, ".gsd");
135
+ const msMDir = join(msGsd, "milestones", "M001");
136
+ const msSDir = join(msMDir, "slices", "S01");
137
+ const msTDir = join(msSDir, "tasks");
138
+ mkdirSync(msTDir, { recursive: true });
139
+
140
+ // Roadmap with ALL slices [x] — milestone is complete by slice status
141
+ writeFileSync(join(msMDir, "M001-ROADMAP.md"), `# M001: Test Milestone
142
+
143
+ ## Slices
144
+ - [x] **S01: Done Slice** \`risk:low\` \`depends:[]\`
145
+ > After this: done
146
+ `);
147
+
148
+ // Slice has plan with all tasks done
149
+ writeFileSync(join(msSDir, "S01-PLAN.md"), `# S01: Done Slice
150
+
151
+ **Goal:** Done
152
+ **Demo:** Done
153
+
154
+ ## Tasks
155
+ - [x] **T01: Done Task** \`est:10m\`
156
+ Done.
157
+ `);
158
+
159
+ // Task summary exists
160
+ writeFileSync(join(msTDir, "T01-SUMMARY.md"), `---
161
+ id: T01
162
+ parent: S01
163
+ milestone: M001
164
+ ---
165
+ # T01: Done
166
+ **Done**
167
+ ## What Happened
168
+ Done.
169
+ `);
170
+
171
+ // Slice summary exists (so slice-level checks pass)
172
+ writeFileSync(join(msSDir, "S01-SUMMARY.md"), `---
173
+ id: S01
174
+ parent: M001
175
+ ---
176
+ # S01: Done
177
+ `);
178
+
179
+ // Slice UAT exists (so slice-level checks pass)
180
+ writeFileSync(join(msSDir, "S01-UAT.md"), `# S01 UAT\nDone.\n`);
181
+
182
+ // NO milestone summary — this is the condition we're detecting
183
+
184
+ const report = await runGSDDoctor(msBase, { fix: false });
185
+ assert(
186
+ report.issues.some(issue => issue.code === "all_slices_done_missing_milestone_summary"),
187
+ "detects missing milestone summary when all slices are done"
188
+ );
189
+ const msIssue = report.issues.find(issue => issue.code === "all_slices_done_missing_milestone_summary");
190
+ assertEq(msIssue?.scope, "milestone", "milestone summary issue has scope 'milestone'");
191
+ assertEq(msIssue?.severity, "warning", "milestone summary issue has severity 'warning'");
192
+ assertEq(msIssue?.unitId, "M001", "milestone summary issue unitId is 'M001'");
193
+ assert(msIssue?.message?.includes("SUMMARY") ?? false, "milestone summary issue message mentions SUMMARY");
194
+
195
+ rmSync(msBase, { recursive: true, force: true });
196
+ }
197
+
198
+ // ─── Milestone summary detection: summary present (no false positive) ──
199
+ console.log("\n=== doctor does NOT flag milestone with summary ===");
200
+ {
201
+ const msBase = mkdtempSync(join(tmpdir(), "gsd-doctor-ms-ok-test-"));
202
+ const msGsd = join(msBase, ".gsd");
203
+ const msMDir = join(msGsd, "milestones", "M001");
204
+ const msSDir = join(msMDir, "slices", "S01");
205
+ const msTDir = join(msSDir, "tasks");
206
+ mkdirSync(msTDir, { recursive: true });
207
+
208
+ // Roadmap with ALL slices [x]
209
+ writeFileSync(join(msMDir, "M001-ROADMAP.md"), `# M001: Test Milestone
210
+
211
+ ## Slices
212
+ - [x] **S01: Done Slice** \`risk:low\` \`depends:[]\`
213
+ > After this: done
214
+ `);
215
+
216
+ writeFileSync(join(msSDir, "S01-PLAN.md"), `# S01: Done Slice
217
+
218
+ **Goal:** Done
219
+ **Demo:** Done
220
+
221
+ ## Tasks
222
+ - [x] **T01: Done Task** \`est:10m\`
223
+ Done.
224
+ `);
225
+
226
+ writeFileSync(join(msTDir, "T01-SUMMARY.md"), `---
227
+ id: T01
228
+ parent: S01
229
+ milestone: M001
230
+ ---
231
+ # T01: Done
232
+ **Done**
233
+ ## What Happened
234
+ Done.
235
+ `);
236
+
237
+ writeFileSync(join(msSDir, "S01-SUMMARY.md"), `---
238
+ id: S01
239
+ parent: M001
240
+ ---
241
+ # S01: Done
242
+ `);
243
+
244
+ writeFileSync(join(msSDir, "S01-UAT.md"), `# S01 UAT\nDone.\n`);
245
+
246
+ // Milestone summary EXISTS
247
+ writeFileSync(join(msMDir, "M001-SUMMARY.md"), `# M001 Summary\n\nMilestone complete.`);
248
+
249
+ const report = await runGSDDoctor(msBase, { fix: false });
250
+ assert(
251
+ !report.issues.some(issue => issue.code === "all_slices_done_missing_milestone_summary"),
252
+ "does NOT report missing milestone summary when summary exists"
253
+ );
254
+
255
+ rmSync(msBase, { recursive: true, force: true });
256
+ }
257
+
258
+ // ─── blocker_discovered_no_replan detection ────────────────────────────
259
+ console.log("\n=== doctor detects blocker_discovered_no_replan ===");
260
+ {
261
+ const bBase = mkdtempSync(join(tmpdir(), "gsd-doctor-blocker-test-"));
262
+ const bGsd = join(bBase, ".gsd");
263
+ const bMDir = join(bGsd, "milestones", "M001");
264
+ const bSDir = join(bMDir, "slices", "S01");
265
+ const bTDir = join(bSDir, "tasks");
266
+ mkdirSync(bTDir, { recursive: true });
267
+
268
+ writeFileSync(join(bMDir, "M001-ROADMAP.md"), `# M001: Test Milestone
269
+
270
+ ## Slices
271
+ - [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
272
+ > After this: stuff works
273
+ `);
274
+
275
+ writeFileSync(join(bSDir, "S01-PLAN.md"), `# S01: Test Slice
276
+
277
+ **Goal:** Test
278
+ **Demo:** Test
279
+
280
+ ## Tasks
281
+ - [x] **T01: First task** \`est:10m\`
282
+ First task.
283
+
284
+ - [ ] **T02: Second task** \`est:10m\`
285
+ Second task.
286
+ `);
287
+
288
+ // Task summary with blocker_discovered: true
289
+ writeFileSync(join(bTDir, "T01-SUMMARY.md"), `---
290
+ id: T01
291
+ parent: S01
292
+ milestone: M001
293
+ provides: []
294
+ key_files: []
295
+ key_decisions: []
296
+ patterns_established: []
297
+ observability_surfaces: []
298
+ duration: 10m
299
+ verification_result: passed
300
+ completed_at: 2026-03-10T00:00:00Z
301
+ blocker_discovered: true
302
+ ---
303
+
304
+ # T01: First task
305
+
306
+ **Found a blocker.**
307
+
308
+ ## What Happened
309
+
310
+ Discovered an issue.
311
+ `);
312
+
313
+ // No REPLAN.md — should trigger the issue
314
+ const report = await runGSDDoctor(bBase, { fix: false });
315
+ const blockerIssues = report.issues.filter(i => i.code === "blocker_discovered_no_replan");
316
+ assert(blockerIssues.length > 0, "detects blocker_discovered_no_replan");
317
+ assertEq(blockerIssues[0]?.severity, "warning", "blocker issue has warning severity");
318
+ assertEq(blockerIssues[0]?.scope, "slice", "blocker issue has slice scope");
319
+ assert(blockerIssues[0]?.message?.includes("T01") ?? false, "blocker issue message mentions T01");
320
+ assert(blockerIssues[0]?.message?.includes("S01") ?? false, "blocker issue message mentions S01");
321
+
322
+ rmSync(bBase, { recursive: true, force: true });
323
+ }
324
+
325
+ // ─── blocker_discovered with REPLAN.md (no false positive) ─────────────
326
+ console.log("\n=== doctor does NOT flag blocker when REPLAN.md exists ===");
327
+ {
328
+ const bBase = mkdtempSync(join(tmpdir(), "gsd-doctor-blocker-ok-test-"));
329
+ const bGsd = join(bBase, ".gsd");
330
+ const bMDir = join(bGsd, "milestones", "M001");
331
+ const bSDir = join(bMDir, "slices", "S01");
332
+ const bTDir = join(bSDir, "tasks");
333
+ mkdirSync(bTDir, { recursive: true });
334
+
335
+ writeFileSync(join(bMDir, "M001-ROADMAP.md"), `# M001: Test Milestone
336
+
337
+ ## Slices
338
+ - [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
339
+ > After this: stuff works
340
+ `);
341
+
342
+ writeFileSync(join(bSDir, "S01-PLAN.md"), `# S01: Test Slice
343
+
344
+ **Goal:** Test
345
+ **Demo:** Test
346
+
347
+ ## Tasks
348
+ - [x] **T01: First task** \`est:10m\`
349
+ First task.
350
+
351
+ - [ ] **T02: Second task** \`est:10m\`
352
+ Second task.
353
+ `);
354
+
355
+ writeFileSync(join(bTDir, "T01-SUMMARY.md"), `---
356
+ id: T01
357
+ parent: S01
358
+ milestone: M001
359
+ blocker_discovered: true
360
+ completed_at: 2026-03-10T00:00:00Z
361
+ ---
362
+
363
+ # T01: First task
364
+
365
+ **Found a blocker.**
366
+
367
+ ## What Happened
368
+
369
+ Discovered an issue.
370
+ `);
371
+
372
+ // REPLAN.md exists — should NOT trigger
373
+ writeFileSync(join(bSDir, "S01-REPLAN.md"), `# Replan\n\nAlready replanned.`);
374
+
375
+ const report = await runGSDDoctor(bBase, { fix: false });
376
+ const blockerIssues = report.issues.filter(i => i.code === "blocker_discovered_no_replan");
377
+ assertEq(blockerIssues.length, 0, "no blocker_discovered_no_replan when REPLAN.md exists");
378
+
379
+ rmSync(bBase, { recursive: true, force: true });
380
+ }
381
+
382
+ // ─── Must-have verification: all addressed → no issue ─────────────────
383
+ console.log("\n=== doctor: done task with must-haves all addressed → no issue ===");
384
+ {
385
+ const mhBase = mkdtempSync(join(tmpdir(), "gsd-doctor-mh-ok-"));
386
+ const mhGsd = join(mhBase, ".gsd");
387
+ const mhMDir = join(mhGsd, "milestones", "M001");
388
+ const mhSDir = join(mhMDir, "slices", "S01");
389
+ const mhTDir = join(mhSDir, "tasks");
390
+ mkdirSync(mhTDir, { recursive: true });
391
+
392
+ writeFileSync(join(mhMDir, "M001-ROADMAP.md"), `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
393
+ writeFileSync(join(mhSDir, "S01-PLAN.md"), `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`);
394
+
395
+ // Task plan with must-haves
396
+ writeFileSync(join(mhTDir, "T01-PLAN.md"), `# T01: Implement\n\n## Must-Haves\n\n- [ ] \`parseWidgets\` function exported\n- [ ] Unit tests pass with zero failures\n`);
397
+
398
+ // Summary mentioning both must-haves
399
+ writeFileSync(join(mhTDir, "T01-SUMMARY.md"), `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nAdded parseWidgets function. Unit tests pass with zero failures.\n`);
400
+
401
+ const report = await runGSDDoctor(mhBase, { fix: false });
402
+ assert(
403
+ !report.issues.some(i => i.code === "task_done_must_haves_not_verified"),
404
+ "no must-have issue when all must-haves are addressed"
405
+ );
406
+
407
+ rmSync(mhBase, { recursive: true, force: true });
408
+ }
409
+
410
+ // ─── Must-have verification: not addressed → warning fired ───────────
411
+ console.log("\n=== doctor: done task with must-haves NOT addressed → warning ===");
412
+ {
413
+ const mhBase = mkdtempSync(join(tmpdir(), "gsd-doctor-mh-fail-"));
414
+ const mhGsd = join(mhBase, ".gsd");
415
+ const mhMDir = join(mhGsd, "milestones", "M001");
416
+ const mhSDir = join(mhMDir, "slices", "S01");
417
+ const mhTDir = join(mhSDir, "tasks");
418
+ mkdirSync(mhTDir, { recursive: true });
419
+
420
+ writeFileSync(join(mhMDir, "M001-ROADMAP.md"), `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
421
+ writeFileSync(join(mhSDir, "S01-PLAN.md"), `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`);
422
+
423
+ // Task plan with 3 must-haves
424
+ writeFileSync(join(mhTDir, "T01-PLAN.md"), `# T01: Implement\n\n## Must-Haves\n\n- [ ] \`parseWidgets\` function exported\n- [ ] \`countWidgets\` utility added\n- [ ] Full regression suite passes\n`);
425
+
426
+ // Summary mentions only parseWidgets — the other two are missing
427
+ writeFileSync(join(mhTDir, "T01-SUMMARY.md"), `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nAdded parseWidgets function.\n`);
428
+
429
+ const report = await runGSDDoctor(mhBase, { fix: false });
430
+ const mhIssue = report.issues.find(i => i.code === "task_done_must_haves_not_verified");
431
+ assert(!!mhIssue, "must-have issue is fired when summary doesn't address all must-haves");
432
+ assertEq(mhIssue?.severity, "warning", "must-have issue is warning severity");
433
+ assertEq(mhIssue?.scope, "task", "must-have issue scope is task");
434
+ assert(mhIssue?.message?.includes("3 must-haves") ?? false, "message mentions total must-have count");
435
+ assert(mhIssue?.message?.includes("only 1") ?? false, "message mentions addressed count");
436
+ assertEq(mhIssue?.fixable, false, "must-have issue is not fixable");
437
+
438
+ rmSync(mhBase, { recursive: true, force: true });
439
+ }
440
+
441
+ // ─── Must-have verification: no task plan → no issue ─────────────────
442
+ console.log("\n=== doctor: done task with no task plan file → no issue ===");
443
+ {
444
+ const mhBase = mkdtempSync(join(tmpdir(), "gsd-doctor-mh-noplan-"));
445
+ const mhGsd = join(mhBase, ".gsd");
446
+ const mhMDir = join(mhGsd, "milestones", "M001");
447
+ const mhSDir = join(mhMDir, "slices", "S01");
448
+ const mhTDir = join(mhSDir, "tasks");
449
+ mkdirSync(mhTDir, { recursive: true });
450
+
451
+ writeFileSync(join(mhMDir, "M001-ROADMAP.md"), `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
452
+ writeFileSync(join(mhSDir, "S01-PLAN.md"), `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`);
453
+
454
+ // NO task plan file — just a summary
455
+ writeFileSync(join(mhTDir, "T01-SUMMARY.md"), `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nDone.\n`);
456
+
457
+ const report = await runGSDDoctor(mhBase, { fix: false });
458
+ assert(
459
+ !report.issues.some(i => i.code === "task_done_must_haves_not_verified"),
460
+ "no must-have issue when task plan file doesn't exist"
461
+ );
462
+
463
+ rmSync(mhBase, { recursive: true, force: true });
464
+ }
465
+
466
+ // ─── Must-have verification: plan exists but no Must-Haves section → no issue
467
+ console.log("\n=== doctor: done task with plan but no Must-Haves section → no issue ===");
468
+ {
469
+ const mhBase = mkdtempSync(join(tmpdir(), "gsd-doctor-mh-nosect-"));
470
+ const mhGsd = join(mhBase, ".gsd");
471
+ const mhMDir = join(mhGsd, "milestones", "M001");
472
+ const mhSDir = join(mhMDir, "slices", "S01");
473
+ const mhTDir = join(mhSDir, "tasks");
474
+ mkdirSync(mhTDir, { recursive: true });
475
+
476
+ writeFileSync(join(mhMDir, "M001-ROADMAP.md"), `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
477
+ writeFileSync(join(mhSDir, "S01-PLAN.md"), `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`);
478
+
479
+ // Task plan with NO Must-Haves section
480
+ writeFileSync(join(mhTDir, "T01-PLAN.md"), `# T01: Implement\n\n## Steps\n\n1. Do the thing.\n\n## Verification\n\n- Run tests.\n`);
481
+
482
+ writeFileSync(join(mhTDir, "T01-SUMMARY.md"), `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nDone.\n`);
483
+
484
+ const report = await runGSDDoctor(mhBase, { fix: false });
485
+ assert(
486
+ !report.issues.some(i => i.code === "task_done_must_haves_not_verified"),
487
+ "no must-have issue when task plan has no Must-Haves section"
488
+ );
489
+
490
+ rmSync(mhBase, { recursive: true, force: true });
491
+ }
492
+
493
+ console.log(`\n${"=".repeat(40)}`);
494
+ console.log(`Results: ${passed} passed, ${failed} failed`);
495
+ if (failed > 0) {
496
+ process.exit(1);
497
+ } else {
498
+ console.log("All tests passed ✓");
499
+ }
500
+ }
501
+
502
+ main().catch((error) => {
503
+ console.error(error);
504
+ process.exit(1);
505
+ });