@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,341 @@
1
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+
5
+ import { deriveState } from '../state.ts';
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
+ // ─── Fixture Helpers ───────────────────────────────────────────────────────
29
+
30
+ function createFixtureBase(): string {
31
+ const base = mkdtempSync(join(tmpdir(), 'gsd-deps-test-'));
32
+ mkdirSync(join(base, '.gsd', 'milestones'), { recursive: true });
33
+ return base;
34
+ }
35
+
36
+ function writeRoadmap(base: string, mid: string, content: string): void {
37
+ const dir = join(base, '.gsd', 'milestones', mid);
38
+ mkdirSync(dir, { recursive: true });
39
+ writeFileSync(join(dir, `${mid}-ROADMAP.md`), content);
40
+ }
41
+
42
+ function writeMilestoneSummary(base: string, mid: string, content: string): void {
43
+ const dir = join(base, '.gsd', 'milestones', mid);
44
+ mkdirSync(dir, { recursive: true });
45
+ writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
46
+ }
47
+
48
+ /**
49
+ * Creates M00x-CONTEXT.md with a valid YAML frontmatter block.
50
+ * frontmatter is the raw YAML lines between the --- delimiters.
51
+ */
52
+ function writeContext(base: string, mid: string, frontmatter: string): void {
53
+ const dir = join(base, '.gsd', 'milestones', mid);
54
+ mkdirSync(dir, { recursive: true });
55
+ writeFileSync(join(dir, `${mid}-CONTEXT.md`), `---\n${frontmatter}\n---\n`);
56
+ }
57
+
58
+ function writeSlicePlan(base: string, mid: string, sid: string, content: string): void {
59
+ const dir = join(base, '.gsd', 'milestones', mid, 'slices', sid);
60
+ mkdirSync(join(dir, 'tasks'), { recursive: true });
61
+ writeFileSync(join(dir, `${sid}-PLAN.md`), content);
62
+ }
63
+
64
+ function cleanup(base: string): void {
65
+ rmSync(base, { recursive: true, force: true });
66
+ }
67
+
68
+ // ═══════════════════════════════════════════════════════════════════════════
69
+ // Test Groups
70
+ // ═══════════════════════════════════════════════════════════════════════════
71
+
72
+ async function main(): Promise<void> {
73
+
74
+ // ─── Test Group 1: blocked-deps ────────────────────────────────────────
75
+ // M001 is incomplete (no SUMMARY), M002 depends_on M001 → M002 is pending
76
+ console.log('\n=== blocked-deps ===');
77
+ {
78
+ const base = createFixtureBase();
79
+ try {
80
+ // M001: incomplete (one slice, no SUMMARY)
81
+ writeRoadmap(base, 'M001', `# M001: First Milestone
82
+
83
+ **Vision:** First milestone still in progress.
84
+
85
+ ## Slices
86
+
87
+ - [ ] **S01: Incomplete Slice** \`risk:low\` \`depends:[]\`
88
+ > After this: Done.
89
+ `);
90
+
91
+ // M001: add a slice plan with an active task so phase is 'executing'
92
+ writeSlicePlan(base, 'M001', 'S01', `# S01: Incomplete Slice
93
+
94
+ **Goal:** Verify dep-blocked milestone behavior.
95
+ **Demo:** Tests pass.
96
+
97
+ ## Tasks
98
+
99
+ - [ ] **T01: Do work** \`est:15m\`
100
+ First task still in progress.
101
+ `);
102
+
103
+ // M002: depends on M001, also incomplete
104
+ writeRoadmap(base, 'M002', `# M002: Second Milestone
105
+
106
+ **Vision:** Second milestone blocked by M001.
107
+
108
+ ## Slices
109
+
110
+ - [ ] **S01: Blocked Slice** \`risk:low\` \`depends:[]\`
111
+ > After this: Done.
112
+ `);
113
+ writeContext(base, 'M002', 'depends_on: [M001]');
114
+
115
+ const state = await deriveState(base);
116
+
117
+ assertEq(state.registry[0]?.status, 'active', 'blocked-deps: M001 is active');
118
+ assertEq(state.registry[1]?.status, 'pending', 'blocked-deps: M002 is pending (dep-blocked)');
119
+ assertEq(state.phase, 'executing', 'blocked-deps: phase is executing (M001 is active)');
120
+ assertEq(state.activeMilestone?.id, 'M001', 'blocked-deps: activeMilestone is M001');
121
+ } finally {
122
+ cleanup(base);
123
+ }
124
+ }
125
+
126
+ // ─── Test Group 2: unblocked-deps ──────────────────────────────────────
127
+ // M001 is complete (all slices [x] + SUMMARY), M002 depends_on M001 → M002 becomes active
128
+ console.log('\n=== unblocked-deps ===');
129
+ {
130
+ const base = createFixtureBase();
131
+ try {
132
+ // M001: complete (all slices done + SUMMARY present)
133
+ writeRoadmap(base, 'M001', `# M001: First Milestone
134
+
135
+ **Vision:** First milestone complete.
136
+
137
+ ## Slices
138
+
139
+ - [x] **S01: Done** \`risk:low\` \`depends:[]\`
140
+ > After this: Done.
141
+ `);
142
+ writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nFirst milestone is complete.');
143
+
144
+ // M002: depends on M001, now unblocked
145
+ writeRoadmap(base, 'M002', `# M002: Second Milestone
146
+
147
+ **Vision:** Second milestone now active.
148
+
149
+ ## Slices
150
+
151
+ - [ ] **S01: Active Slice** \`risk:low\` \`depends:[]\`
152
+ > After this: Done.
153
+ `);
154
+ writeContext(base, 'M002', 'depends_on: [M001]');
155
+
156
+ const state = await deriveState(base);
157
+
158
+ assertEq(state.registry[0]?.status, 'complete', 'unblocked-deps: M001 is complete');
159
+ assertEq(state.registry[1]?.status, 'active', 'unblocked-deps: M002 is active');
160
+ assertEq(state.activeMilestone?.id, 'M002', 'unblocked-deps: activeMilestone is M002');
161
+ assert(state.phase !== 'blocked', 'unblocked-deps: phase is not blocked');
162
+ } finally {
163
+ cleanup(base);
164
+ }
165
+ }
166
+
167
+ // ─── Test Group 3: all-blocked ─────────────────────────────────────────
168
+ // M001 depends_on M002, M002 depends_on M001 — circular dep, neither can activate
169
+ console.log('\n=== all-blocked ===');
170
+ {
171
+ const base = createFixtureBase();
172
+ try {
173
+ // M001: depends on M002
174
+ writeRoadmap(base, 'M001', `# M001: First Milestone
175
+
176
+ **Vision:** Circular dependency.
177
+
178
+ ## Slices
179
+
180
+ - [ ] **S01: Waiting** \`risk:low\` \`depends:[]\`
181
+ > After this: Done.
182
+ `);
183
+ writeContext(base, 'M001', 'depends_on: [M002]');
184
+
185
+ // M002: depends on M001
186
+ writeRoadmap(base, 'M002', `# M002: Second Milestone
187
+
188
+ **Vision:** Also in circular dependency.
189
+
190
+ ## Slices
191
+
192
+ - [ ] **S01: Also Waiting** \`risk:low\` \`depends:[]\`
193
+ > After this: Done.
194
+ `);
195
+ writeContext(base, 'M002', 'depends_on: [M001]');
196
+
197
+ const state = await deriveState(base);
198
+
199
+ assertEq(state.phase, 'blocked', 'all-blocked: phase is blocked');
200
+ assert(state.activeMilestone === null || state.activeMilestone !== null, 'all-blocked: state is consistent');
201
+ assert(state.blockers.length > 0, 'all-blocked: blockers array is non-empty');
202
+ } finally {
203
+ cleanup(base);
204
+ }
205
+ }
206
+
207
+ // ─── Test Group 4: absent-context ──────────────────────────────────────
208
+ // Neither M001 nor M002 has a CONTEXT.md → no dep constraints, normal sequential behavior
209
+ console.log('\n=== absent-context ===');
210
+ {
211
+ const base = createFixtureBase();
212
+ try {
213
+ // M001: incomplete, no CONTEXT.md
214
+ writeRoadmap(base, 'M001', `# M001: First Milestone
215
+
216
+ **Vision:** No context file, no deps.
217
+
218
+ ## Slices
219
+
220
+ - [ ] **S01: Incomplete** \`risk:low\` \`depends:[]\`
221
+ > After this: Done.
222
+ `);
223
+
224
+ // M002: incomplete, no CONTEXT.md
225
+ writeRoadmap(base, 'M002', `# M002: Second Milestone
226
+
227
+ **Vision:** Also no context file.
228
+
229
+ ## Slices
230
+
231
+ - [ ] **S01: Pending** \`risk:low\` \`depends:[]\`
232
+ > After this: Done.
233
+ `);
234
+
235
+ const state = await deriveState(base);
236
+
237
+ assertEq(state.registry[0]?.status, 'active', 'absent-context: M001 is active');
238
+ assertEq(state.registry[1]?.status, 'pending', 'absent-context: M002 is pending');
239
+ assertEq(state.activeMilestone?.id, 'M001', 'absent-context: activeMilestone is M001');
240
+ assert(state.phase !== 'blocked', 'absent-context: phase is not blocked');
241
+ } finally {
242
+ cleanup(base);
243
+ }
244
+ }
245
+
246
+ // ─── Test Group 5: forward-dep ─────────────────────────────────────────
247
+ // M001 depends_on M002, but M002 is already complete → M001 can activate
248
+ console.log('\n=== forward-dep ===');
249
+ {
250
+ const base = createFixtureBase();
251
+ try {
252
+ // M001: depends on M002, but M002 is complete so M001 is unblocked
253
+ writeRoadmap(base, 'M001', `# M001: First Milestone
254
+
255
+ **Vision:** Depends on M002 which is already complete.
256
+
257
+ ## Slices
258
+
259
+ - [ ] **S01: Ready** \`risk:low\` \`depends:[]\`
260
+ > After this: Done.
261
+ `);
262
+ writeContext(base, 'M001', 'depends_on: [M002]');
263
+
264
+ // M002: complete (all slices [x] + SUMMARY)
265
+ writeRoadmap(base, 'M002', `# M002: Second Milestone
266
+
267
+ **Vision:** Already complete.
268
+
269
+ ## Slices
270
+
271
+ - [x] **S01: Done** \`risk:low\` \`depends:[]\`
272
+ > After this: Done.
273
+ `);
274
+ writeMilestoneSummary(base, 'M002', '# M002 Summary\n\nSecond milestone is complete.');
275
+
276
+ const state = await deriveState(base);
277
+
278
+ assertEq(state.activeMilestone?.id, 'M001', 'forward-dep: activeMilestone is M001');
279
+ assertEq(state.registry[1]?.status, 'complete', 'forward-dep: M002 is complete');
280
+ assert(state.phase !== 'blocked', 'forward-dep: phase is not blocked');
281
+ } finally {
282
+ cleanup(base);
283
+ }
284
+ }
285
+
286
+ // ─── Test Group 6: empty-deps-list ─────────────────────────────────────
287
+ // M002 has `depends_on: []` — empty list means no constraint, normal sequential behavior
288
+ console.log('\n=== empty-deps-list ===');
289
+ {
290
+ const base = createFixtureBase();
291
+ try {
292
+ // M001: incomplete, no context
293
+ writeRoadmap(base, 'M001', `# M001: First Milestone
294
+
295
+ **Vision:** First milestone still in progress.
296
+
297
+ ## Slices
298
+
299
+ - [ ] **S01: Incomplete** \`risk:low\` \`depends:[]\`
300
+ > After this: Done.
301
+ `);
302
+
303
+ // M002: empty deps list — no constraint from deps, but still sequential after M001
304
+ writeRoadmap(base, 'M002', `# M002: Second Milestone
305
+
306
+ **Vision:** Empty deps list, no blocking constraint.
307
+
308
+ ## Slices
309
+
310
+ - [ ] **S01: Waiting for M001** \`risk:low\` \`depends:[]\`
311
+ > After this: Done.
312
+ `);
313
+ writeContext(base, 'M002', 'depends_on: []');
314
+
315
+ const state = await deriveState(base);
316
+
317
+ assertEq(state.registry[0]?.status, 'active', 'empty-deps-list: M001 is active');
318
+ assertEq(state.registry[1]?.status, 'pending', 'empty-deps-list: M002 is pending (M001 not done yet)');
319
+ assert(state.phase !== 'blocked', 'empty-deps-list: phase is not blocked');
320
+ } finally {
321
+ cleanup(base);
322
+ }
323
+ }
324
+
325
+ // ═════════════════════════════════════════════════════════════════════════
326
+ // Results
327
+ // ═════════════════════════════════════════════════════════════════════════
328
+
329
+ console.log(`\n${'='.repeat(40)}`);
330
+ console.log(`Results: ${passed} passed, ${failed} failed`);
331
+ if (failed > 0) {
332
+ process.exit(1);
333
+ } else {
334
+ console.log('All tests passed ✓');
335
+ }
336
+ }
337
+
338
+ main().catch((error) => {
339
+ console.error(error);
340
+ process.exit(1);
341
+ });