@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,718 @@
1
+ import {
2
+ mkdtempSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ existsSync,
8
+ } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+
12
+ import {
13
+ formatDoctorReport,
14
+ runKataDoctor,
15
+ summarizeDoctorIssues,
16
+ filterDoctorIssues,
17
+ selectDoctorScope,
18
+ } from "../doctor.js";
19
+
20
+ let passed = 0;
21
+ let failed = 0;
22
+
23
+ function assert(condition: boolean, message: string): void {
24
+ if (condition) {
25
+ passed++;
26
+ } else {
27
+ failed++;
28
+ console.error(` FAIL: ${message}`);
29
+ }
30
+ }
31
+
32
+ function assertEq<T>(actual: T, expected: T, message: string): void {
33
+ if (JSON.stringify(actual) === JSON.stringify(expected)) {
34
+ passed++;
35
+ } else {
36
+ failed++;
37
+ console.error(
38
+ ` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
39
+ );
40
+ }
41
+ }
42
+
43
+ const tmpBase = mkdtempSync(join(tmpdir(), "kata-doctor-test-"));
44
+ const kata = join(tmpBase, ".kata");
45
+ const mDir = join(kata, "milestones", "M001");
46
+ const sDir = join(mDir, "slices", "S01");
47
+ const tDir = join(sDir, "tasks");
48
+ mkdirSync(tDir, { recursive: true });
49
+
50
+ writeFileSync(
51
+ join(mDir, "M001-ROADMAP.md"),
52
+ `# M001: Test Milestone
53
+
54
+ ## Slices
55
+ - [ ] **S01: Demo Slice** \`risk:low\` \`depends:[]\`
56
+ > After this: demo works
57
+ `,
58
+ );
59
+
60
+ writeFileSync(
61
+ join(sDir, "S01-PLAN.md"),
62
+ `# S01: Demo Slice
63
+
64
+ **Goal:** Demo
65
+ **Demo:** Demo
66
+
67
+ ## Must-Haves
68
+ - done
69
+
70
+ ## Tasks
71
+ - [x] **T01: Implement thing** \`est:10m\`
72
+ Task is complete.
73
+ `,
74
+ );
75
+
76
+ writeFileSync(
77
+ join(tDir, "T01-SUMMARY.md"),
78
+ `---
79
+ id: T01
80
+ parent: S01
81
+ milestone: M001
82
+ provides: []
83
+ requires: []
84
+ affects: []
85
+ key_files: []
86
+ key_decisions: []
87
+ patterns_established: []
88
+ observability_surfaces: []
89
+ drill_down_paths: []
90
+ duration: 10m
91
+ verification_result: passed
92
+ completed_at: 2026-03-09T00:00:00Z
93
+ ---
94
+
95
+ # T01: Implement thing
96
+
97
+ **Done**
98
+
99
+ ## What Happened
100
+ Implemented.
101
+
102
+ ## Diagnostics
103
+ - log
104
+ `,
105
+ );
106
+
107
+ async function main(): Promise<void> {
108
+ console.log("\n=== doctor diagnose ===");
109
+ {
110
+ const report = await runKataDoctor(tmpBase, { fix: false });
111
+ assert(
112
+ !report.ok,
113
+ "report is not ok when completion artifacts are missing",
114
+ );
115
+ assert(
116
+ report.issues.some(
117
+ (issue) => issue.code === "all_tasks_done_missing_slice_summary",
118
+ ),
119
+ "detects missing slice summary",
120
+ );
121
+ assert(
122
+ report.issues.some(
123
+ (issue) => issue.code === "all_tasks_done_missing_slice_uat",
124
+ ),
125
+ "detects missing slice UAT",
126
+ );
127
+ }
128
+
129
+ console.log("\n=== doctor formatting ===");
130
+ {
131
+ const report = await runKataDoctor(tmpBase, { fix: false });
132
+ const summary = summarizeDoctorIssues(report.issues);
133
+ assertEq(summary.errors, 2, "two blocking errors in summary");
134
+ const scoped = filterDoctorIssues(report.issues, {
135
+ scope: "M001/S01",
136
+ includeWarnings: true,
137
+ });
138
+ assert(scoped.length >= 2, "scope filter keeps slice issues");
139
+ const text = formatDoctorReport(report, {
140
+ scope: "M001/S01",
141
+ includeWarnings: true,
142
+ maxIssues: 5,
143
+ });
144
+ assert(text.includes("Scope: M001/S01"), "formatted report shows scope");
145
+ assert(
146
+ text.includes("Top issue types:"),
147
+ "formatted report shows grouped issue types",
148
+ );
149
+ }
150
+
151
+ console.log("\n=== doctor default scope ===");
152
+ {
153
+ const scope = await selectDoctorScope(tmpBase);
154
+ assertEq(
155
+ scope,
156
+ "M001/S01",
157
+ "default doctor scope targets the active slice",
158
+ );
159
+ }
160
+
161
+ console.log("\n=== doctor fix ===");
162
+ {
163
+ const report = await runKataDoctor(tmpBase, { fix: true });
164
+ if (report.fixesApplied.length < 3) console.error(report);
165
+ assert(report.fixesApplied.length >= 3, "applies multiple fixes");
166
+ assert(
167
+ existsSync(join(sDir, "S01-SUMMARY.md")),
168
+ "creates placeholder slice summary",
169
+ );
170
+ assert(existsSync(join(sDir, "S01-UAT.md")), "creates placeholder UAT");
171
+
172
+ const plan = readFileSync(join(sDir, "S01-PLAN.md"), "utf-8");
173
+ assert(plan.includes("- [x] **T01:"), "marks task checkbox done");
174
+
175
+ const roadmap = readFileSync(join(mDir, "M001-ROADMAP.md"), "utf-8");
176
+ assert(roadmap.includes("- [x] **S01:"), "marks slice checkbox done");
177
+
178
+ const state = readFileSync(join(kata, "STATE.md"), "utf-8");
179
+ assert(state.includes("# Kata State"), "writes state file");
180
+ }
181
+
182
+ rmSync(tmpBase, { recursive: true, force: true });
183
+
184
+ // ─── Milestone summary detection: missing summary ──────────────────────
185
+ console.log("\n=== doctor detects missing milestone summary ===");
186
+ {
187
+ const msBase = mkdtempSync(join(tmpdir(), "kata-doctor-ms-test-"));
188
+ const msKata = join(msBase, ".kata");
189
+ const msMDir = join(msKata, "milestones", "M001");
190
+ const msSDir = join(msMDir, "slices", "S01");
191
+ const msTDir = join(msSDir, "tasks");
192
+ mkdirSync(msTDir, { recursive: true });
193
+
194
+ // Roadmap with ALL slices [x] — milestone is complete by slice status
195
+ writeFileSync(
196
+ join(msMDir, "M001-ROADMAP.md"),
197
+ `# M001: Test Milestone
198
+
199
+ ## Slices
200
+ - [x] **S01: Done Slice** \`risk:low\` \`depends:[]\`
201
+ > After this: done
202
+ `,
203
+ );
204
+
205
+ // Slice has plan with all tasks done
206
+ writeFileSync(
207
+ join(msSDir, "S01-PLAN.md"),
208
+ `# S01: Done Slice
209
+
210
+ **Goal:** Done
211
+ **Demo:** Done
212
+
213
+ ## Tasks
214
+ - [x] **T01: Done Task** \`est:10m\`
215
+ Done.
216
+ `,
217
+ );
218
+
219
+ // Task summary exists
220
+ writeFileSync(
221
+ join(msTDir, "T01-SUMMARY.md"),
222
+ `---
223
+ id: T01
224
+ parent: S01
225
+ milestone: M001
226
+ ---
227
+ # T01: Done
228
+ **Done**
229
+ ## What Happened
230
+ Done.
231
+ `,
232
+ );
233
+
234
+ // Slice summary exists (so slice-level checks pass)
235
+ writeFileSync(
236
+ join(msSDir, "S01-SUMMARY.md"),
237
+ `---
238
+ id: S01
239
+ parent: M001
240
+ ---
241
+ # S01: Done
242
+ `,
243
+ );
244
+
245
+ // Slice UAT exists (so slice-level checks pass)
246
+ writeFileSync(join(msSDir, "S01-UAT.md"), `# S01 UAT\nDone.\n`);
247
+
248
+ // NO milestone summary — this is the condition we're detecting
249
+
250
+ const report = await runKataDoctor(msBase, { fix: false });
251
+ assert(
252
+ report.issues.some(
253
+ (issue) => issue.code === "all_slices_done_missing_milestone_summary",
254
+ ),
255
+ "detects missing milestone summary when all slices are done",
256
+ );
257
+ const msIssue = report.issues.find(
258
+ (issue) => issue.code === "all_slices_done_missing_milestone_summary",
259
+ );
260
+ assertEq(
261
+ msIssue?.scope,
262
+ "milestone",
263
+ "milestone summary issue has scope 'milestone'",
264
+ );
265
+ assertEq(
266
+ msIssue?.severity,
267
+ "warning",
268
+ "milestone summary issue has severity 'warning'",
269
+ );
270
+ assertEq(
271
+ msIssue?.unitId,
272
+ "M001",
273
+ "milestone summary issue unitId is 'M001'",
274
+ );
275
+ assert(
276
+ msIssue?.message?.includes("SUMMARY") ?? false,
277
+ "milestone summary issue message mentions SUMMARY",
278
+ );
279
+
280
+ rmSync(msBase, { recursive: true, force: true });
281
+ }
282
+
283
+ // ─── Milestone summary detection: summary present (no false positive) ──
284
+ console.log("\n=== doctor does NOT flag milestone with summary ===");
285
+ {
286
+ const msBase = mkdtempSync(join(tmpdir(), "kata-doctor-ms-ok-test-"));
287
+ const msKata = join(msBase, ".kata");
288
+ const msMDir = join(msKata, "milestones", "M001");
289
+ const msSDir = join(msMDir, "slices", "S01");
290
+ const msTDir = join(msSDir, "tasks");
291
+ mkdirSync(msTDir, { recursive: true });
292
+
293
+ // Roadmap with ALL slices [x]
294
+ writeFileSync(
295
+ join(msMDir, "M001-ROADMAP.md"),
296
+ `# M001: Test Milestone
297
+
298
+ ## Slices
299
+ - [x] **S01: Done Slice** \`risk:low\` \`depends:[]\`
300
+ > After this: done
301
+ `,
302
+ );
303
+
304
+ writeFileSync(
305
+ join(msSDir, "S01-PLAN.md"),
306
+ `# S01: Done Slice
307
+
308
+ **Goal:** Done
309
+ **Demo:** Done
310
+
311
+ ## Tasks
312
+ - [x] **T01: Done Task** \`est:10m\`
313
+ Done.
314
+ `,
315
+ );
316
+
317
+ writeFileSync(
318
+ join(msTDir, "T01-SUMMARY.md"),
319
+ `---
320
+ id: T01
321
+ parent: S01
322
+ milestone: M001
323
+ ---
324
+ # T01: Done
325
+ **Done**
326
+ ## What Happened
327
+ Done.
328
+ `,
329
+ );
330
+
331
+ writeFileSync(
332
+ join(msSDir, "S01-SUMMARY.md"),
333
+ `---
334
+ id: S01
335
+ parent: M001
336
+ ---
337
+ # S01: Done
338
+ `,
339
+ );
340
+
341
+ writeFileSync(join(msSDir, "S01-UAT.md"), `# S01 UAT\nDone.\n`);
342
+
343
+ // Milestone summary EXISTS
344
+ writeFileSync(
345
+ join(msMDir, "M001-SUMMARY.md"),
346
+ `# M001 Summary\n\nMilestone complete.`,
347
+ );
348
+
349
+ const report = await runKataDoctor(msBase, { fix: false });
350
+ assert(
351
+ !report.issues.some(
352
+ (issue) => issue.code === "all_slices_done_missing_milestone_summary",
353
+ ),
354
+ "does NOT report missing milestone summary when summary exists",
355
+ );
356
+
357
+ rmSync(msBase, { recursive: true, force: true });
358
+ }
359
+
360
+ // ─── blocker_discovered_no_replan detection ────────────────────────────
361
+ console.log("\n=== doctor detects blocker_discovered_no_replan ===");
362
+ {
363
+ const bBase = mkdtempSync(join(tmpdir(), "kata-doctor-blocker-test-"));
364
+ const bKata = join(bBase, ".kata");
365
+ const bMDir = join(bKata, "milestones", "M001");
366
+ const bSDir = join(bMDir, "slices", "S01");
367
+ const bTDir = join(bSDir, "tasks");
368
+ mkdirSync(bTDir, { recursive: true });
369
+
370
+ writeFileSync(
371
+ join(bMDir, "M001-ROADMAP.md"),
372
+ `# M001: Test Milestone
373
+
374
+ ## Slices
375
+ - [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
376
+ > After this: stuff works
377
+ `,
378
+ );
379
+
380
+ writeFileSync(
381
+ join(bSDir, "S01-PLAN.md"),
382
+ `# S01: Test Slice
383
+
384
+ **Goal:** Test
385
+ **Demo:** Test
386
+
387
+ ## Tasks
388
+ - [x] **T01: First task** \`est:10m\`
389
+ First task.
390
+
391
+ - [ ] **T02: Second task** \`est:10m\`
392
+ Second task.
393
+ `,
394
+ );
395
+
396
+ // Task summary with blocker_discovered: true
397
+ writeFileSync(
398
+ join(bTDir, "T01-SUMMARY.md"),
399
+ `---
400
+ id: T01
401
+ parent: S01
402
+ milestone: M001
403
+ provides: []
404
+ key_files: []
405
+ key_decisions: []
406
+ patterns_established: []
407
+ observability_surfaces: []
408
+ duration: 10m
409
+ verification_result: passed
410
+ completed_at: 2026-03-10T00:00:00Z
411
+ blocker_discovered: true
412
+ ---
413
+
414
+ # T01: First task
415
+
416
+ **Found a blocker.**
417
+
418
+ ## What Happened
419
+
420
+ Discovered an issue.
421
+ `,
422
+ );
423
+
424
+ // No REPLAN.md — should trigger the issue
425
+ const report = await runKataDoctor(bBase, { fix: false });
426
+ const blockerIssues = report.issues.filter(
427
+ (i) => i.code === "blocker_discovered_no_replan",
428
+ );
429
+ assert(blockerIssues.length > 0, "detects blocker_discovered_no_replan");
430
+ assertEq(
431
+ blockerIssues[0]?.severity,
432
+ "warning",
433
+ "blocker issue has warning severity",
434
+ );
435
+ assertEq(blockerIssues[0]?.scope, "slice", "blocker issue has slice scope");
436
+ assert(
437
+ blockerIssues[0]?.message?.includes("T01") ?? false,
438
+ "blocker issue message mentions T01",
439
+ );
440
+ assert(
441
+ blockerIssues[0]?.message?.includes("S01") ?? false,
442
+ "blocker issue message mentions S01",
443
+ );
444
+
445
+ rmSync(bBase, { recursive: true, force: true });
446
+ }
447
+
448
+ // ─── blocker_discovered with REPLAN.md (no false positive) ─────────────
449
+ console.log("\n=== doctor does NOT flag blocker when REPLAN.md exists ===");
450
+ {
451
+ const bBase = mkdtempSync(join(tmpdir(), "kata-doctor-blocker-ok-test-"));
452
+ const bKata = join(bBase, ".kata");
453
+ const bMDir = join(bKata, "milestones", "M001");
454
+ const bSDir = join(bMDir, "slices", "S01");
455
+ const bTDir = join(bSDir, "tasks");
456
+ mkdirSync(bTDir, { recursive: true });
457
+
458
+ writeFileSync(
459
+ join(bMDir, "M001-ROADMAP.md"),
460
+ `# M001: Test Milestone
461
+
462
+ ## Slices
463
+ - [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
464
+ > After this: stuff works
465
+ `,
466
+ );
467
+
468
+ writeFileSync(
469
+ join(bSDir, "S01-PLAN.md"),
470
+ `# S01: Test Slice
471
+
472
+ **Goal:** Test
473
+ **Demo:** Test
474
+
475
+ ## Tasks
476
+ - [x] **T01: First task** \`est:10m\`
477
+ First task.
478
+
479
+ - [ ] **T02: Second task** \`est:10m\`
480
+ Second task.
481
+ `,
482
+ );
483
+
484
+ writeFileSync(
485
+ join(bTDir, "T01-SUMMARY.md"),
486
+ `---
487
+ id: T01
488
+ parent: S01
489
+ milestone: M001
490
+ blocker_discovered: true
491
+ completed_at: 2026-03-10T00:00:00Z
492
+ ---
493
+
494
+ # T01: First task
495
+
496
+ **Found a blocker.**
497
+
498
+ ## What Happened
499
+
500
+ Discovered an issue.
501
+ `,
502
+ );
503
+
504
+ // REPLAN.md exists — should NOT trigger
505
+ writeFileSync(
506
+ join(bSDir, "S01-REPLAN.md"),
507
+ `# Replan\n\nAlready replanned.`,
508
+ );
509
+
510
+ const report = await runKataDoctor(bBase, { fix: false });
511
+ const blockerIssues = report.issues.filter(
512
+ (i) => i.code === "blocker_discovered_no_replan",
513
+ );
514
+ assertEq(
515
+ blockerIssues.length,
516
+ 0,
517
+ "no blocker_discovered_no_replan when REPLAN.md exists",
518
+ );
519
+
520
+ rmSync(bBase, { recursive: true, force: true });
521
+ }
522
+
523
+ // ─── Must-have verification: all addressed → no issue ─────────────────
524
+ console.log(
525
+ "\n=== doctor: done task with must-haves all addressed → no issue ===",
526
+ );
527
+ {
528
+ const mhBase = mkdtempSync(join(tmpdir(), "kata-doctor-mh-ok-"));
529
+ const mhKata = join(mhBase, ".kata");
530
+ const mhMDir = join(mhKata, "milestones", "M001");
531
+ const mhSDir = join(mhMDir, "slices", "S01");
532
+ const mhTDir = join(mhSDir, "tasks");
533
+ mkdirSync(mhTDir, { recursive: true });
534
+
535
+ writeFileSync(
536
+ join(mhMDir, "M001-ROADMAP.md"),
537
+ `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`,
538
+ );
539
+ writeFileSync(
540
+ join(mhSDir, "S01-PLAN.md"),
541
+ `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`,
542
+ );
543
+
544
+ // Task plan with must-haves
545
+ writeFileSync(
546
+ join(mhTDir, "T01-PLAN.md"),
547
+ `# T01: Implement\n\n## Must-Haves\n\n- [ ] \`parseWidgets\` function exported\n- [ ] Unit tests pass with zero failures\n`,
548
+ );
549
+
550
+ // Summary mentioning both must-haves
551
+ writeFileSync(
552
+ join(mhTDir, "T01-SUMMARY.md"),
553
+ `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nAdded parseWidgets function. Unit tests pass with zero failures.\n`,
554
+ );
555
+
556
+ const report = await runKataDoctor(mhBase, { fix: false });
557
+ assert(
558
+ !report.issues.some(
559
+ (i) => i.code === "task_done_must_haves_not_verified",
560
+ ),
561
+ "no must-have issue when all must-haves are addressed",
562
+ );
563
+
564
+ rmSync(mhBase, { recursive: true, force: true });
565
+ }
566
+
567
+ // ─── Must-have verification: not addressed → warning fired ───────────
568
+ console.log(
569
+ "\n=== doctor: done task with must-haves NOT addressed → warning ===",
570
+ );
571
+ {
572
+ const mhBase = mkdtempSync(join(tmpdir(), "kata-doctor-mh-fail-"));
573
+ const mhKata = join(mhBase, ".kata");
574
+ const mhMDir = join(mhKata, "milestones", "M001");
575
+ const mhSDir = join(mhMDir, "slices", "S01");
576
+ const mhTDir = join(mhSDir, "tasks");
577
+ mkdirSync(mhTDir, { recursive: true });
578
+
579
+ writeFileSync(
580
+ join(mhMDir, "M001-ROADMAP.md"),
581
+ `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`,
582
+ );
583
+ writeFileSync(
584
+ join(mhSDir, "S01-PLAN.md"),
585
+ `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`,
586
+ );
587
+
588
+ // Task plan with 3 must-haves
589
+ writeFileSync(
590
+ join(mhTDir, "T01-PLAN.md"),
591
+ `# T01: Implement\n\n## Must-Haves\n\n- [ ] \`parseWidgets\` function exported\n- [ ] \`countWidgets\` utility added\n- [ ] Full regression suite passes\n`,
592
+ );
593
+
594
+ // Summary mentions only parseWidgets — the other two are missing
595
+ writeFileSync(
596
+ join(mhTDir, "T01-SUMMARY.md"),
597
+ `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nAdded parseWidgets function.\n`,
598
+ );
599
+
600
+ const report = await runKataDoctor(mhBase, { fix: false });
601
+ const mhIssue = report.issues.find(
602
+ (i) => i.code === "task_done_must_haves_not_verified",
603
+ );
604
+ assert(
605
+ !!mhIssue,
606
+ "must-have issue is fired when summary doesn't address all must-haves",
607
+ );
608
+ assertEq(
609
+ mhIssue?.severity,
610
+ "warning",
611
+ "must-have issue is warning severity",
612
+ );
613
+ assertEq(mhIssue?.scope, "task", "must-have issue scope is task");
614
+ assert(
615
+ mhIssue?.message?.includes("3 must-haves") ?? false,
616
+ "message mentions total must-have count",
617
+ );
618
+ assert(
619
+ mhIssue?.message?.includes("only 1") ?? false,
620
+ "message mentions addressed count",
621
+ );
622
+ assertEq(mhIssue?.fixable, false, "must-have issue is not fixable");
623
+
624
+ rmSync(mhBase, { recursive: true, force: true });
625
+ }
626
+
627
+ // ─── Must-have verification: no task plan → no issue ─────────────────
628
+ console.log("\n=== doctor: done task with no task plan file → no issue ===");
629
+ {
630
+ const mhBase = mkdtempSync(join(tmpdir(), "kata-doctor-mh-noplan-"));
631
+ const mhKata = join(mhBase, ".kata");
632
+ const mhMDir = join(mhKata, "milestones", "M001");
633
+ const mhSDir = join(mhMDir, "slices", "S01");
634
+ const mhTDir = join(mhSDir, "tasks");
635
+ mkdirSync(mhTDir, { recursive: true });
636
+
637
+ writeFileSync(
638
+ join(mhMDir, "M001-ROADMAP.md"),
639
+ `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`,
640
+ );
641
+ writeFileSync(
642
+ join(mhSDir, "S01-PLAN.md"),
643
+ `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`,
644
+ );
645
+
646
+ // NO task plan file — just a summary
647
+ writeFileSync(
648
+ join(mhTDir, "T01-SUMMARY.md"),
649
+ `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nDone.\n`,
650
+ );
651
+
652
+ const report = await runKataDoctor(mhBase, { fix: false });
653
+ assert(
654
+ !report.issues.some(
655
+ (i) => i.code === "task_done_must_haves_not_verified",
656
+ ),
657
+ "no must-have issue when task plan file doesn't exist",
658
+ );
659
+
660
+ rmSync(mhBase, { recursive: true, force: true });
661
+ }
662
+
663
+ // ─── Must-have verification: plan exists but no Must-Haves section → no issue
664
+ console.log(
665
+ "\n=== doctor: done task with plan but no Must-Haves section → no issue ===",
666
+ );
667
+ {
668
+ const mhBase = mkdtempSync(join(tmpdir(), "kata-doctor-mh-nosect-"));
669
+ const mhKata = join(mhBase, ".kata");
670
+ const mhMDir = join(mhKata, "milestones", "M001");
671
+ const mhSDir = join(mhMDir, "slices", "S01");
672
+ const mhTDir = join(mhSDir, "tasks");
673
+ mkdirSync(mhTDir, { recursive: true });
674
+
675
+ writeFileSync(
676
+ join(mhMDir, "M001-ROADMAP.md"),
677
+ `# M001: Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`,
678
+ );
679
+ writeFileSync(
680
+ join(mhSDir, "S01-PLAN.md"),
681
+ `# S01: Slice\n\n**Goal:** Demo\n**Demo:** Demo\n\n## Tasks\n- [x] **T01: Implement** \`est:10m\`\n Done.\n`,
682
+ );
683
+
684
+ // Task plan with NO Must-Haves section
685
+ writeFileSync(
686
+ join(mhTDir, "T01-PLAN.md"),
687
+ `# T01: Implement\n\n## Steps\n\n1. Do the thing.\n\n## Verification\n\n- Run tests.\n`,
688
+ );
689
+
690
+ writeFileSync(
691
+ join(mhTDir, "T01-SUMMARY.md"),
692
+ `---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01: Implement\n\n## What Happened\nDone.\n`,
693
+ );
694
+
695
+ const report = await runKataDoctor(mhBase, { fix: false });
696
+ assert(
697
+ !report.issues.some(
698
+ (i) => i.code === "task_done_must_haves_not_verified",
699
+ ),
700
+ "no must-have issue when task plan has no Must-Haves section",
701
+ );
702
+
703
+ rmSync(mhBase, { recursive: true, force: true });
704
+ }
705
+
706
+ console.log(`\n${"=".repeat(40)}`);
707
+ console.log(`Results: ${passed} passed, ${failed} failed`);
708
+ if (failed > 0) {
709
+ process.exit(1);
710
+ } else {
711
+ console.log("All tests passed ✓");
712
+ }
713
+ }
714
+
715
+ main().catch((error) => {
716
+ console.error(error);
717
+ process.exit(1);
718
+ });