@kata-sh/cli 0.1.0 → 0.1.2

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 (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -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 +56 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +95 -0
  9. package/dist/resource-loader.d.ts +18 -0
  10. package/dist/resource-loader.js +50 -0
  11. package/dist/wizard.d.ts +15 -0
  12. package/dist/wizard.js +159 -0
  13. package/package.json +50 -21
  14. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  15. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  16. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  17. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  18. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  19. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  20. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  21. package/pkg/package.json +8 -0
  22. package/scripts/postinstall.js +45 -0
  23. package/src/resources/AGENTS.md +108 -0
  24. package/src/resources/KATA-WORKFLOW.md +661 -0
  25. package/src/resources/agents/researcher.md +29 -0
  26. package/src/resources/agents/scout.md +56 -0
  27. package/src/resources/agents/worker.md +31 -0
  28. package/src/resources/extensions/ask-user-questions.ts +200 -0
  29. package/src/resources/extensions/bg-shell/index.ts +2758 -0
  30. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  31. package/src/resources/extensions/browser-tools/core.js +1057 -0
  32. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  33. package/src/resources/extensions/browser-tools/package.json +20 -0
  34. package/src/resources/extensions/context7/index.ts +428 -0
  35. package/src/resources/extensions/context7/package.json +11 -0
  36. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  37. package/src/resources/extensions/github/formatters.ts +207 -0
  38. package/src/resources/extensions/github/gh-api.ts +537 -0
  39. package/src/resources/extensions/github/index.ts +778 -0
  40. package/src/resources/extensions/kata/activity-log.ts +88 -0
  41. package/src/resources/extensions/kata/auto.ts +2786 -0
  42. package/src/resources/extensions/kata/commands.ts +355 -0
  43. package/src/resources/extensions/kata/crash-recovery.ts +85 -0
  44. package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
  45. package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
  46. package/src/resources/extensions/kata/doctor.ts +683 -0
  47. package/src/resources/extensions/kata/files.ts +730 -0
  48. package/src/resources/extensions/kata/gitignore.ts +165 -0
  49. package/src/resources/extensions/kata/guided-flow.ts +976 -0
  50. package/src/resources/extensions/kata/index.ts +556 -0
  51. package/src/resources/extensions/kata/metrics.ts +397 -0
  52. package/src/resources/extensions/kata/observability-validator.ts +408 -0
  53. package/src/resources/extensions/kata/package.json +11 -0
  54. package/src/resources/extensions/kata/paths.ts +346 -0
  55. package/src/resources/extensions/kata/preferences.ts +695 -0
  56. package/src/resources/extensions/kata/prompt-loader.ts +50 -0
  57. package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
  58. package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
  59. package/src/resources/extensions/kata/prompts/discuss.md +151 -0
  60. package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
  61. package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
  62. package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
  63. package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
  64. package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
  65. package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
  66. package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
  67. package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
  68. package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
  69. package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
  70. package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
  71. package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
  72. package/src/resources/extensions/kata/prompts/queue.md +85 -0
  73. package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
  74. package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
  75. package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
  76. package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
  77. package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
  78. package/src/resources/extensions/kata/prompts/system.md +341 -0
  79. package/src/resources/extensions/kata/session-forensics.ts +550 -0
  80. package/src/resources/extensions/kata/skill-discovery.ts +137 -0
  81. package/src/resources/extensions/kata/state.ts +509 -0
  82. package/src/resources/extensions/kata/templates/context.md +76 -0
  83. package/src/resources/extensions/kata/templates/decisions.md +8 -0
  84. package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
  85. package/src/resources/extensions/kata/templates/plan.md +133 -0
  86. package/src/resources/extensions/kata/templates/preferences.md +15 -0
  87. package/src/resources/extensions/kata/templates/project.md +31 -0
  88. package/src/resources/extensions/kata/templates/reassessment.md +28 -0
  89. package/src/resources/extensions/kata/templates/requirements.md +81 -0
  90. package/src/resources/extensions/kata/templates/research.md +46 -0
  91. package/src/resources/extensions/kata/templates/roadmap.md +118 -0
  92. package/src/resources/extensions/kata/templates/slice-context.md +58 -0
  93. package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
  94. package/src/resources/extensions/kata/templates/state.md +19 -0
  95. package/src/resources/extensions/kata/templates/task-plan.md +52 -0
  96. package/src/resources/extensions/kata/templates/task-summary.md +57 -0
  97. package/src/resources/extensions/kata/templates/uat.md +54 -0
  98. package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
  99. package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
  100. package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
  101. package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
  102. package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
  103. package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
  104. package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
  105. package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
  106. package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
  107. package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
  108. package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
  109. package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
  110. package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
  111. package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
  112. package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
  113. package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
  114. package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
  115. package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
  116. package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
  117. package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
  118. package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
  119. package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
  120. package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
  121. package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
  122. package/src/resources/extensions/kata/types.ts +159 -0
  123. package/src/resources/extensions/kata/unit-runtime.ts +163 -0
  124. package/src/resources/extensions/kata/workspace-index.ts +203 -0
  125. package/src/resources/extensions/kata/worktree.ts +182 -0
  126. package/src/resources/extensions/mac-tools/index.ts +852 -0
  127. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  128. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  129. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  130. package/src/resources/extensions/search-the-web/format.ts +258 -0
  131. package/src/resources/extensions/search-the-web/http.ts +238 -0
  132. package/src/resources/extensions/search-the-web/index.ts +68 -0
  133. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  134. package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
  135. package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
  136. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  137. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  138. package/src/resources/extensions/shared/interview-ui.ts +822 -0
  139. package/src/resources/extensions/shared/next-action-ui.ts +235 -0
  140. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  141. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  142. package/src/resources/extensions/shared/ui.ts +400 -0
  143. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  144. package/src/resources/extensions/slash-commands/audit.ts +92 -0
  145. package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
  146. package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
  147. package/src/resources/extensions/slash-commands/index.ts +12 -0
  148. package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
  149. package/src/resources/extensions/subagent/agents.ts +126 -0
  150. package/src/resources/extensions/subagent/index.ts +1293 -0
  151. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  152. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  153. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  154. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  155. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  156. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  157. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  158. package/src/resources/skills/swiftui/SKILL.md +208 -0
  159. package/src/resources/skills/swiftui/references/animations.md +921 -0
  160. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  161. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  162. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  163. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  164. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  165. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  166. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  167. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  168. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  169. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  170. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  171. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  172. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  173. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  174. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  175. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
  176. package/dist/commands/task.d.ts +0 -9
  177. package/dist/commands/task.d.ts.map +0 -1
  178. package/dist/commands/task.js +0 -129
  179. package/dist/commands/task.js.map +0 -1
  180. package/dist/commands/task.test.d.ts +0 -2
  181. package/dist/commands/task.test.d.ts.map +0 -1
  182. package/dist/commands/task.test.js +0 -169
  183. package/dist/commands/task.test.js.map +0 -1
  184. package/dist/e2e/task-e2e.test.d.ts +0 -2
  185. package/dist/e2e/task-e2e.test.d.ts.map +0 -1
  186. package/dist/e2e/task-e2e.test.js +0 -173
  187. package/dist/e2e/task-e2e.test.js.map +0 -1
  188. package/dist/index.d.ts +0 -3
  189. package/dist/index.d.ts.map +0 -1
  190. package/dist/index.js +0 -93
  191. package/dist/index.js.map +0 -1
  192. package/dist/slug.d.ts +0 -2
  193. package/dist/slug.d.ts.map +0 -1
  194. package/dist/slug.js +0 -12
  195. package/dist/slug.js.map +0 -1
  196. package/dist/slug.test.d.ts +0 -2
  197. package/dist/slug.test.d.ts.map +0 -1
  198. package/dist/slug.test.js +0 -32
  199. package/dist/slug.test.js.map +0 -1
@@ -0,0 +1,477 @@
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(
25
+ ` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
26
+ );
27
+ }
28
+ }
29
+
30
+ // ─── Fixture Helpers ───────────────────────────────────────────────────────
31
+
32
+ function createFixtureBase(): string {
33
+ const base = mkdtempSync(join(tmpdir(), "kata-deps-test-"));
34
+ mkdirSync(join(base, ".kata", "milestones"), { recursive: true });
35
+ return base;
36
+ }
37
+
38
+ function writeRoadmap(base: string, mid: string, content: string): void {
39
+ const dir = join(base, ".kata", "milestones", mid);
40
+ mkdirSync(dir, { recursive: true });
41
+ writeFileSync(join(dir, `${mid}-ROADMAP.md`), content);
42
+ }
43
+
44
+ function writeMilestoneSummary(
45
+ base: string,
46
+ mid: string,
47
+ content: string,
48
+ ): void {
49
+ const dir = join(base, ".kata", "milestones", mid);
50
+ mkdirSync(dir, { recursive: true });
51
+ writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
52
+ }
53
+
54
+ /**
55
+ * Creates M00x-CONTEXT.md with a valid YAML frontmatter block.
56
+ * frontmatter is the raw YAML lines between the --- delimiters.
57
+ */
58
+ function writeContext(base: string, mid: string, frontmatter: string): void {
59
+ const dir = join(base, ".kata", "milestones", mid);
60
+ mkdirSync(dir, { recursive: true });
61
+ writeFileSync(join(dir, `${mid}-CONTEXT.md`), `---\n${frontmatter}\n---\n`);
62
+ }
63
+
64
+ function writeSlicePlan(
65
+ base: string,
66
+ mid: string,
67
+ sid: string,
68
+ content: string,
69
+ ): void {
70
+ const dir = join(base, ".kata", "milestones", mid, "slices", sid);
71
+ mkdirSync(join(dir, "tasks"), { recursive: true });
72
+ writeFileSync(join(dir, `${sid}-PLAN.md`), content);
73
+ }
74
+
75
+ function cleanup(base: string): void {
76
+ rmSync(base, { recursive: true, force: true });
77
+ }
78
+
79
+ // ═══════════════════════════════════════════════════════════════════════════
80
+ // Test Groups
81
+ // ═══════════════════════════════════════════════════════════════════════════
82
+
83
+ async function main(): Promise<void> {
84
+ // ─── Test Group 1: blocked-deps ────────────────────────────────────────
85
+ // M001 is incomplete (no SUMMARY), M002 depends_on M001 → M002 is pending
86
+ console.log("\n=== blocked-deps ===");
87
+ {
88
+ const base = createFixtureBase();
89
+ try {
90
+ // M001: incomplete (one slice, no SUMMARY)
91
+ writeRoadmap(
92
+ base,
93
+ "M001",
94
+ `# M001: First Milestone
95
+
96
+ **Vision:** First milestone still in progress.
97
+
98
+ ## Slices
99
+
100
+ - [ ] **S01: Incomplete Slice** \`risk:low\` \`depends:[]\`
101
+ > After this: Done.
102
+ `,
103
+ );
104
+
105
+ // M001: add a slice plan with an active task so phase is 'executing'
106
+ writeSlicePlan(
107
+ base,
108
+ "M001",
109
+ "S01",
110
+ `# S01: Incomplete Slice
111
+
112
+ **Goal:** Verify dep-blocked milestone behavior.
113
+ **Demo:** Tests pass.
114
+
115
+ ## Tasks
116
+
117
+ - [ ] **T01: Do work** \`est:15m\`
118
+ First task still in progress.
119
+ `,
120
+ );
121
+
122
+ // M002: depends on M001, also incomplete
123
+ writeRoadmap(
124
+ base,
125
+ "M002",
126
+ `# M002: Second Milestone
127
+
128
+ **Vision:** Second milestone blocked by M001.
129
+
130
+ ## Slices
131
+
132
+ - [ ] **S01: Blocked Slice** \`risk:low\` \`depends:[]\`
133
+ > After this: Done.
134
+ `,
135
+ );
136
+ writeContext(base, "M002", "depends_on: [M001]");
137
+
138
+ const state = await deriveState(base);
139
+
140
+ assertEq(
141
+ state.registry[0]?.status,
142
+ "active",
143
+ "blocked-deps: M001 is active",
144
+ );
145
+ assertEq(
146
+ state.registry[1]?.status,
147
+ "pending",
148
+ "blocked-deps: M002 is pending (dep-blocked)",
149
+ );
150
+ assertEq(
151
+ state.phase,
152
+ "executing",
153
+ "blocked-deps: phase is executing (M001 is active)",
154
+ );
155
+ assertEq(
156
+ state.activeMilestone?.id,
157
+ "M001",
158
+ "blocked-deps: activeMilestone is M001",
159
+ );
160
+ } finally {
161
+ cleanup(base);
162
+ }
163
+ }
164
+
165
+ // ─── Test Group 2: unblocked-deps ──────────────────────────────────────
166
+ // M001 is complete (all slices [x] + SUMMARY), M002 depends_on M001 → M002 becomes active
167
+ console.log("\n=== unblocked-deps ===");
168
+ {
169
+ const base = createFixtureBase();
170
+ try {
171
+ // M001: complete (all slices done + SUMMARY present)
172
+ writeRoadmap(
173
+ base,
174
+ "M001",
175
+ `# M001: First Milestone
176
+
177
+ **Vision:** First milestone complete.
178
+
179
+ ## Slices
180
+
181
+ - [x] **S01: Done** \`risk:low\` \`depends:[]\`
182
+ > After this: Done.
183
+ `,
184
+ );
185
+ writeMilestoneSummary(
186
+ base,
187
+ "M001",
188
+ "# M001 Summary\n\nFirst milestone is complete.",
189
+ );
190
+
191
+ // M002: depends on M001, now unblocked
192
+ writeRoadmap(
193
+ base,
194
+ "M002",
195
+ `# M002: Second Milestone
196
+
197
+ **Vision:** Second milestone now active.
198
+
199
+ ## Slices
200
+
201
+ - [ ] **S01: Active Slice** \`risk:low\` \`depends:[]\`
202
+ > After this: Done.
203
+ `,
204
+ );
205
+ writeContext(base, "M002", "depends_on: [M001]");
206
+
207
+ const state = await deriveState(base);
208
+
209
+ assertEq(
210
+ state.registry[0]?.status,
211
+ "complete",
212
+ "unblocked-deps: M001 is complete",
213
+ );
214
+ assertEq(
215
+ state.registry[1]?.status,
216
+ "active",
217
+ "unblocked-deps: M002 is active",
218
+ );
219
+ assertEq(
220
+ state.activeMilestone?.id,
221
+ "M002",
222
+ "unblocked-deps: activeMilestone is M002",
223
+ );
224
+ assert(state.phase !== "blocked", "unblocked-deps: phase is not blocked");
225
+ } finally {
226
+ cleanup(base);
227
+ }
228
+ }
229
+
230
+ // ─── Test Group 3: all-blocked ─────────────────────────────────────────
231
+ // M001 depends_on M002, M002 depends_on M001 — circular dep, neither can activate
232
+ console.log("\n=== all-blocked ===");
233
+ {
234
+ const base = createFixtureBase();
235
+ try {
236
+ // M001: depends on M002
237
+ writeRoadmap(
238
+ base,
239
+ "M001",
240
+ `# M001: First Milestone
241
+
242
+ **Vision:** Circular dependency.
243
+
244
+ ## Slices
245
+
246
+ - [ ] **S01: Waiting** \`risk:low\` \`depends:[]\`
247
+ > After this: Done.
248
+ `,
249
+ );
250
+ writeContext(base, "M001", "depends_on: [M002]");
251
+
252
+ // M002: depends on M001
253
+ writeRoadmap(
254
+ base,
255
+ "M002",
256
+ `# M002: Second Milestone
257
+
258
+ **Vision:** Also in circular dependency.
259
+
260
+ ## Slices
261
+
262
+ - [ ] **S01: Also Waiting** \`risk:low\` \`depends:[]\`
263
+ > After this: Done.
264
+ `,
265
+ );
266
+ writeContext(base, "M002", "depends_on: [M001]");
267
+
268
+ const state = await deriveState(base);
269
+
270
+ assertEq(state.phase, "blocked", "all-blocked: phase is blocked");
271
+ assert(
272
+ state.activeMilestone === null || state.activeMilestone !== null,
273
+ "all-blocked: state is consistent",
274
+ );
275
+ assert(
276
+ state.blockers.length > 0,
277
+ "all-blocked: blockers array is non-empty",
278
+ );
279
+ } finally {
280
+ cleanup(base);
281
+ }
282
+ }
283
+
284
+ // ─── Test Group 4: absent-context ──────────────────────────────────────
285
+ // Neither M001 nor M002 has a CONTEXT.md → no dep constraints, normal sequential behavior
286
+ console.log("\n=== absent-context ===");
287
+ {
288
+ const base = createFixtureBase();
289
+ try {
290
+ // M001: incomplete, no CONTEXT.md
291
+ writeRoadmap(
292
+ base,
293
+ "M001",
294
+ `# M001: First Milestone
295
+
296
+ **Vision:** No context file, no deps.
297
+
298
+ ## Slices
299
+
300
+ - [ ] **S01: Incomplete** \`risk:low\` \`depends:[]\`
301
+ > After this: Done.
302
+ `,
303
+ );
304
+
305
+ // M002: incomplete, no CONTEXT.md
306
+ writeRoadmap(
307
+ base,
308
+ "M002",
309
+ `# M002: Second Milestone
310
+
311
+ **Vision:** Also no context file.
312
+
313
+ ## Slices
314
+
315
+ - [ ] **S01: Pending** \`risk:low\` \`depends:[]\`
316
+ > After this: Done.
317
+ `,
318
+ );
319
+
320
+ const state = await deriveState(base);
321
+
322
+ assertEq(
323
+ state.registry[0]?.status,
324
+ "active",
325
+ "absent-context: M001 is active",
326
+ );
327
+ assertEq(
328
+ state.registry[1]?.status,
329
+ "pending",
330
+ "absent-context: M002 is pending",
331
+ );
332
+ assertEq(
333
+ state.activeMilestone?.id,
334
+ "M001",
335
+ "absent-context: activeMilestone is M001",
336
+ );
337
+ assert(state.phase !== "blocked", "absent-context: phase is not blocked");
338
+ } finally {
339
+ cleanup(base);
340
+ }
341
+ }
342
+
343
+ // ─── Test Group 5: forward-dep ─────────────────────────────────────────
344
+ // M001 depends_on M002, but M002 is already complete → M001 can activate
345
+ console.log("\n=== forward-dep ===");
346
+ {
347
+ const base = createFixtureBase();
348
+ try {
349
+ // M001: depends on M002, but M002 is complete so M001 is unblocked
350
+ writeRoadmap(
351
+ base,
352
+ "M001",
353
+ `# M001: First Milestone
354
+
355
+ **Vision:** Depends on M002 which is already complete.
356
+
357
+ ## Slices
358
+
359
+ - [ ] **S01: Ready** \`risk:low\` \`depends:[]\`
360
+ > After this: Done.
361
+ `,
362
+ );
363
+ writeContext(base, "M001", "depends_on: [M002]");
364
+
365
+ // M002: complete (all slices [x] + SUMMARY)
366
+ writeRoadmap(
367
+ base,
368
+ "M002",
369
+ `# M002: Second Milestone
370
+
371
+ **Vision:** Already complete.
372
+
373
+ ## Slices
374
+
375
+ - [x] **S01: Done** \`risk:low\` \`depends:[]\`
376
+ > After this: Done.
377
+ `,
378
+ );
379
+ writeMilestoneSummary(
380
+ base,
381
+ "M002",
382
+ "# M002 Summary\n\nSecond milestone is complete.",
383
+ );
384
+
385
+ const state = await deriveState(base);
386
+
387
+ assertEq(
388
+ state.activeMilestone?.id,
389
+ "M001",
390
+ "forward-dep: activeMilestone is M001",
391
+ );
392
+ assertEq(
393
+ state.registry[1]?.status,
394
+ "complete",
395
+ "forward-dep: M002 is complete",
396
+ );
397
+ assert(state.phase !== "blocked", "forward-dep: phase is not blocked");
398
+ } finally {
399
+ cleanup(base);
400
+ }
401
+ }
402
+
403
+ // ─── Test Group 6: empty-deps-list ─────────────────────────────────────
404
+ // M002 has `depends_on: []` — empty list means no constraint, normal sequential behavior
405
+ console.log("\n=== empty-deps-list ===");
406
+ {
407
+ const base = createFixtureBase();
408
+ try {
409
+ // M001: incomplete, no context
410
+ writeRoadmap(
411
+ base,
412
+ "M001",
413
+ `# M001: First Milestone
414
+
415
+ **Vision:** First milestone still in progress.
416
+
417
+ ## Slices
418
+
419
+ - [ ] **S01: Incomplete** \`risk:low\` \`depends:[]\`
420
+ > After this: Done.
421
+ `,
422
+ );
423
+
424
+ // M002: empty deps list — no constraint from deps, but still sequential after M001
425
+ writeRoadmap(
426
+ base,
427
+ "M002",
428
+ `# M002: Second Milestone
429
+
430
+ **Vision:** Empty deps list, no blocking constraint.
431
+
432
+ ## Slices
433
+
434
+ - [ ] **S01: Waiting for M001** \`risk:low\` \`depends:[]\`
435
+ > After this: Done.
436
+ `,
437
+ );
438
+ writeContext(base, "M002", "depends_on: []");
439
+
440
+ const state = await deriveState(base);
441
+
442
+ assertEq(
443
+ state.registry[0]?.status,
444
+ "active",
445
+ "empty-deps-list: M001 is active",
446
+ );
447
+ assertEq(
448
+ state.registry[1]?.status,
449
+ "pending",
450
+ "empty-deps-list: M002 is pending (M001 not done yet)",
451
+ );
452
+ assert(
453
+ state.phase !== "blocked",
454
+ "empty-deps-list: phase is not blocked",
455
+ );
456
+ } finally {
457
+ cleanup(base);
458
+ }
459
+ }
460
+
461
+ // ═════════════════════════════════════════════════════════════════════════
462
+ // Results
463
+ // ═════════════════════════════════════════════════════════════════════════
464
+
465
+ console.log(`\n${"=".repeat(40)}`);
466
+ console.log(`Results: ${passed} passed, ${failed} failed`);
467
+ if (failed > 0) {
468
+ process.exit(1);
469
+ } else {
470
+ console.log("All tests passed ✓");
471
+ }
472
+ }
473
+
474
+ main().catch((error) => {
475
+ console.error(error);
476
+ process.exit(1);
477
+ });