@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,388 @@
1
+ import {
2
+ mkdtempSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+ import {
11
+ clearUnitRuntimeRecord,
12
+ formatExecuteTaskRecoveryStatus,
13
+ inspectExecuteTaskDurability,
14
+ readUnitRuntimeRecord,
15
+ writeUnitRuntimeRecord,
16
+ } from "../unit-runtime.ts";
17
+
18
+ let passed = 0;
19
+ let failed = 0;
20
+
21
+ function assert(condition: boolean, message: string): void {
22
+ if (condition) passed++;
23
+ else {
24
+ failed++;
25
+ console.error(` FAIL: ${message}`);
26
+ }
27
+ }
28
+
29
+ function assertEq<T>(actual: T, expected: T, message: string): void {
30
+ if (JSON.stringify(actual) === JSON.stringify(expected)) passed++;
31
+ else {
32
+ failed++;
33
+ console.error(
34
+ ` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
35
+ );
36
+ }
37
+ }
38
+
39
+ const base = mkdtempSync(join(tmpdir(), "kata-unit-runtime-test-"));
40
+ const tasksDir = join(
41
+ base,
42
+ ".kata",
43
+ "milestones",
44
+ "M100",
45
+ "slices",
46
+ "S02",
47
+ "tasks",
48
+ );
49
+ mkdirSync(tasksDir, { recursive: true });
50
+ writeFileSync(
51
+ join(base, ".kata", "STATE.md"),
52
+ "## Next Action\nExecute T09 for S02: do the thing\n",
53
+ "utf-8",
54
+ );
55
+ writeFileSync(
56
+ join(base, ".kata", "milestones", "M100", "slices", "S02", "S02-PLAN.md"),
57
+ "# S02: Test Slice\n\n## Tasks\n\n- [ ] **T09: Do the thing** `est:10m`\n Description.\n",
58
+ "utf-8",
59
+ );
60
+
61
+ console.log("\n=== runtime record write/read/update ===");
62
+ {
63
+ const first = writeUnitRuntimeRecord(
64
+ base,
65
+ "execute-task",
66
+ "M100/S02/T09",
67
+ 1000,
68
+ { phase: "dispatched" },
69
+ );
70
+ assertEq(first.phase, "dispatched", "initial phase");
71
+ const second = writeUnitRuntimeRecord(
72
+ base,
73
+ "execute-task",
74
+ "M100/S02/T09",
75
+ 1000,
76
+ { phase: "wrapup-warning-sent", wrapupWarningSent: true },
77
+ );
78
+ assertEq(second.wrapupWarningSent, true, "warning persisted");
79
+ const loaded = readUnitRuntimeRecord(base, "execute-task", "M100/S02/T09");
80
+ assert(loaded !== null, "record readable");
81
+ assertEq(loaded!.phase, "wrapup-warning-sent", "updated phase readable");
82
+ }
83
+
84
+ console.log("\n=== execute-task durability inspection ===");
85
+ {
86
+ let status = await inspectExecuteTaskDurability(base, "M100/S02/T09");
87
+ assert(status !== null, "status exists");
88
+ assertEq(status!.summaryExists, false, "summary initially missing");
89
+ assertEq(status!.taskChecked, false, "task initially unchecked");
90
+ assertEq(status!.nextActionAdvanced, false, "next action initially stale");
91
+ assert(
92
+ /summary missing/i.test(formatExecuteTaskRecoveryStatus(status!)),
93
+ "diagnostic mentions summary",
94
+ );
95
+
96
+ writeFileSync(join(tasksDir, "T09-SUMMARY.md"), "# done\n", "utf-8");
97
+ writeFileSync(
98
+ join(base, ".kata", "milestones", "M100", "slices", "S02", "S02-PLAN.md"),
99
+ "# S02: Test Slice\n\n## Tasks\n\n- [x] **T09: Do the thing** `est:10m`\n Description.\n",
100
+ "utf-8",
101
+ );
102
+ writeFileSync(
103
+ join(base, ".kata", "STATE.md"),
104
+ "## Next Action\nExecute T10 for S02: next thing\n",
105
+ "utf-8",
106
+ );
107
+
108
+ status = await inspectExecuteTaskDurability(base, "M100/S02/T09");
109
+ assertEq(status!.summaryExists, true, "summary found after write");
110
+ assertEq(status!.taskChecked, true, "task checked after update");
111
+ assertEq(
112
+ status!.nextActionAdvanced,
113
+ true,
114
+ "next action advanced after update",
115
+ );
116
+ assertEq(
117
+ formatExecuteTaskRecoveryStatus(status!),
118
+ "all durable task artifacts present",
119
+ "clean diagnostic when complete",
120
+ );
121
+ }
122
+
123
+ console.log("\n=== runtime record cleanup ===");
124
+ {
125
+ clearUnitRuntimeRecord(base, "execute-task", "M100/S02/T09");
126
+ const loaded = readUnitRuntimeRecord(base, "execute-task", "M100/S02/T09");
127
+ assertEq(loaded, null, "record removed");
128
+ }
129
+
130
+ // ─── Must-have durability integration tests ───────────────────────────────
131
+
132
+ // Create a separate temp base for must-have tests to avoid interference
133
+ const mhBase = mkdtempSync(join(tmpdir(), "kata-unit-runtime-mh-test-"));
134
+
135
+ console.log("\n=== must-haves: all mentioned in summary ===");
136
+ {
137
+ const tasksDir2 = join(
138
+ mhBase,
139
+ ".kata",
140
+ "milestones",
141
+ "M200",
142
+ "slices",
143
+ "S01",
144
+ "tasks",
145
+ );
146
+ mkdirSync(tasksDir2, { recursive: true });
147
+
148
+ // Slice plan with T01 checked
149
+ writeFileSync(
150
+ join(mhBase, ".kata", "milestones", "M200", "slices", "S01", "S01-PLAN.md"),
151
+ "# S01: Test\n\n## Tasks\n\n- [x] **T01: Build parser** `est:10m`\n Build the parser.\n",
152
+ "utf-8",
153
+ );
154
+ // Task plan with must-haves containing backtick code tokens
155
+ writeFileSync(
156
+ join(tasksDir2, "T01-PLAN.md"),
157
+ "# T01: Build parser\n\n## Must-Haves\n\n- [ ] `parseWidget` function is exported\n- [ ] `formatWidget` handles edge cases\n- [ ] All existing tests pass\n\n## Steps\n\n1. Do stuff\n",
158
+ "utf-8",
159
+ );
160
+ // Summary that mentions all must-haves
161
+ writeFileSync(
162
+ join(tasksDir2, "T01-SUMMARY.md"),
163
+ "# T01: Build parser\n\nAdded parseWidget function and formatWidget with edge case handling. All existing tests pass without regression.\n",
164
+ "utf-8",
165
+ );
166
+ // STATE.md with next action advanced past T01
167
+ writeFileSync(
168
+ join(mhBase, ".kata", "STATE.md"),
169
+ "## Next Action\nExecute T02 for S01: next thing\n",
170
+ "utf-8",
171
+ );
172
+
173
+ const status = await inspectExecuteTaskDurability(mhBase, "M200/S01/T01");
174
+ assert(status !== null, "mh-all: status exists");
175
+ assertEq(status!.mustHaveCount, 3, "mh-all: mustHaveCount is 3");
176
+ assertEq(
177
+ status!.mustHavesMentionedInSummary,
178
+ 3,
179
+ "mh-all: all 3 must-haves mentioned",
180
+ );
181
+ assertEq(status!.summaryExists, true, "mh-all: summary exists");
182
+ assertEq(status!.taskChecked, true, "mh-all: task checked");
183
+ const diag = formatExecuteTaskRecoveryStatus(status!);
184
+ assertEq(
185
+ diag,
186
+ "all durable task artifacts present",
187
+ "mh-all: diagnostic is clean when all must-haves met",
188
+ );
189
+ }
190
+
191
+ console.log("\n=== must-haves: partially mentioned in summary ===");
192
+ {
193
+ const tasksDir3 = join(
194
+ mhBase,
195
+ ".kata",
196
+ "milestones",
197
+ "M200",
198
+ "slices",
199
+ "S02",
200
+ "tasks",
201
+ );
202
+ mkdirSync(tasksDir3, { recursive: true });
203
+
204
+ writeFileSync(
205
+ join(mhBase, ".kata", "milestones", "M200", "slices", "S02", "S02-PLAN.md"),
206
+ "# S02: Test\n\n## Tasks\n\n- [x] **T01: Build thing** `est:10m`\n Build.\n",
207
+ "utf-8",
208
+ );
209
+ // Task plan with 3 must-haves, summary will only mention 1
210
+ writeFileSync(
211
+ join(tasksDir3, "T01-PLAN.md"),
212
+ "# T01: Build thing\n\n## Must-Haves\n\n- [ ] `computeScore` function is exported\n- [ ] `validateInput` rejects invalid data\n- [ ] `renderOutput` handles empty arrays\n\n## Steps\n\n1. Do stuff\n",
213
+ "utf-8",
214
+ );
215
+ // Summary only mentions computeScore
216
+ writeFileSync(
217
+ join(tasksDir3, "T01-SUMMARY.md"),
218
+ "# T01: Build thing\n\nAdded computeScore function with full test coverage.\n",
219
+ "utf-8",
220
+ );
221
+ writeFileSync(
222
+ join(mhBase, ".kata", "STATE.md"),
223
+ "## Next Action\nExecute T02 for S02: next thing\n",
224
+ "utf-8",
225
+ );
226
+
227
+ const status = await inspectExecuteTaskDurability(mhBase, "M200/S02/T01");
228
+ assert(status !== null, "mh-partial: status exists");
229
+ assertEq(status!.mustHaveCount, 3, "mh-partial: mustHaveCount is 3");
230
+ assertEq(
231
+ status!.mustHavesMentionedInSummary,
232
+ 1,
233
+ "mh-partial: only 1 must-have mentioned",
234
+ );
235
+ const diag = formatExecuteTaskRecoveryStatus(status!);
236
+ assert(
237
+ diag.includes("must-have gap"),
238
+ "mh-partial: diagnostic includes 'must-have gap'",
239
+ );
240
+ assert(diag.includes("1 of 3"), "mh-partial: diagnostic includes '1 of 3'");
241
+ }
242
+
243
+ console.log("\n=== must-haves: no task plan file ===");
244
+ {
245
+ const tasksDir4 = join(
246
+ mhBase,
247
+ ".kata",
248
+ "milestones",
249
+ "M200",
250
+ "slices",
251
+ "S03",
252
+ "tasks",
253
+ );
254
+ mkdirSync(tasksDir4, { recursive: true });
255
+
256
+ writeFileSync(
257
+ join(mhBase, ".kata", "milestones", "M200", "slices", "S03", "S03-PLAN.md"),
258
+ "# S03: Test\n\n## Tasks\n\n- [x] **T01: Quick fix** `est:5m`\n Fix.\n",
259
+ "utf-8",
260
+ );
261
+ // No T01-PLAN.md — only summary
262
+ writeFileSync(
263
+ join(tasksDir4, "T01-SUMMARY.md"),
264
+ "# T01: Quick fix\n\nFixed the thing.\n",
265
+ "utf-8",
266
+ );
267
+ writeFileSync(
268
+ join(mhBase, ".kata", "STATE.md"),
269
+ "## Next Action\nExecute T02 for S03: next thing\n",
270
+ "utf-8",
271
+ );
272
+
273
+ const status = await inspectExecuteTaskDurability(mhBase, "M200/S03/T01");
274
+ assert(status !== null, "mh-noplan: status exists");
275
+ assertEq(
276
+ status!.mustHaveCount,
277
+ 0,
278
+ "mh-noplan: mustHaveCount is 0 when no task plan",
279
+ );
280
+ assertEq(
281
+ status!.mustHavesMentionedInSummary,
282
+ 0,
283
+ "mh-noplan: mustHavesMentionedInSummary is 0",
284
+ );
285
+ }
286
+
287
+ console.log("\n=== must-haves: present but no summary file ===");
288
+ {
289
+ const tasksDir5 = join(
290
+ mhBase,
291
+ ".kata",
292
+ "milestones",
293
+ "M200",
294
+ "slices",
295
+ "S04",
296
+ "tasks",
297
+ );
298
+ mkdirSync(tasksDir5, { recursive: true });
299
+
300
+ writeFileSync(
301
+ join(mhBase, ".kata", "milestones", "M200", "slices", "S04", "S04-PLAN.md"),
302
+ "# S04: Test\n\n## Tasks\n\n- [ ] **T01: Build parser** `est:10m`\n Build.\n",
303
+ "utf-8",
304
+ );
305
+ // Task plan with must-haves but NO summary file
306
+ writeFileSync(
307
+ join(tasksDir5, "T01-PLAN.md"),
308
+ "# T01: Build parser\n\n## Must-Haves\n\n- [ ] `parseData` function exported\n- [ ] Error handling covers edge cases\n\n## Steps\n\n1. Do stuff\n",
309
+ "utf-8",
310
+ );
311
+ writeFileSync(
312
+ join(mhBase, ".kata", "STATE.md"),
313
+ "## Next Action\nExecute T01 for S04: build parser\n",
314
+ "utf-8",
315
+ );
316
+
317
+ const status = await inspectExecuteTaskDurability(mhBase, "M200/S04/T01");
318
+ assert(status !== null, "mh-nosummary: status exists");
319
+ assertEq(status!.mustHaveCount, 2, "mh-nosummary: mustHaveCount is 2");
320
+ assertEq(
321
+ status!.mustHavesMentionedInSummary,
322
+ 0,
323
+ "mh-nosummary: mustHavesMentionedInSummary is 0 with no summary",
324
+ );
325
+ assertEq(status!.summaryExists, false, "mh-nosummary: summary doesn't exist");
326
+ }
327
+
328
+ console.log("\n=== must-haves: substring matching (no backtick tokens) ===");
329
+ {
330
+ const tasksDir6 = join(
331
+ mhBase,
332
+ ".kata",
333
+ "milestones",
334
+ "M200",
335
+ "slices",
336
+ "S05",
337
+ "tasks",
338
+ );
339
+ mkdirSync(tasksDir6, { recursive: true });
340
+
341
+ writeFileSync(
342
+ join(mhBase, ".kata", "milestones", "M200", "slices", "S05", "S05-PLAN.md"),
343
+ "# S05: Test\n\n## Tasks\n\n- [x] **T01: Add diagnostics** `est:10m`\n Add.\n",
344
+ "utf-8",
345
+ );
346
+ // Must-haves with no backtick tokens — falls back to substring matching
347
+ writeFileSync(
348
+ join(tasksDir6, "T01-PLAN.md"),
349
+ "# T01: Add diagnostics\n\n## Must-Haves\n\n- [ ] Heuristic matching prioritizes backtick-enclosed code tokens\n- [ ] Recovery diagnostic string shows gap count\n- [ ] All assertions pass\n\n## Steps\n\n1. Do stuff\n",
350
+ "utf-8",
351
+ );
352
+ // Summary mentions "heuristic" and "diagnostic" but not "assertions"
353
+ writeFileSync(
354
+ join(tasksDir6, "T01-SUMMARY.md"),
355
+ "# T01: Add diagnostics\n\nImplemented heuristic matching for must-have items. Recovery diagnostic string now includes gap counts.\n",
356
+ "utf-8",
357
+ );
358
+ writeFileSync(
359
+ join(mhBase, ".kata", "STATE.md"),
360
+ "## Next Action\nExecute T02 for S05: next thing\n",
361
+ "utf-8",
362
+ );
363
+
364
+ const status = await inspectExecuteTaskDurability(mhBase, "M200/S05/T01");
365
+ assert(status !== null, "mh-substr: status exists");
366
+ assertEq(status!.mustHaveCount, 3, "mh-substr: mustHaveCount is 3");
367
+ // "heuristic" appears in summary for item 1, "diagnostic" for item 2,
368
+ // "assertions" appears in summary? No — let's check
369
+ // Item 3: "All assertions pass" — words: "assertions", "pass" (<4 chars excluded)
370
+ // summary doesn't contain "assertions" → not matched
371
+ assertEq(
372
+ status!.mustHavesMentionedInSummary,
373
+ 2,
374
+ "mh-substr: 2 of 3 matched via substring",
375
+ );
376
+ const diag = formatExecuteTaskRecoveryStatus(status!);
377
+ assert(
378
+ diag.includes("must-have gap"),
379
+ "mh-substr: diagnostic includes gap info",
380
+ );
381
+ assert(diag.includes("2 of 3"), "mh-substr: diagnostic includes '2 of 3'");
382
+ }
383
+
384
+ rmSync(mhBase, { recursive: true, force: true });
385
+ rmSync(base, { recursive: true, force: true });
386
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
387
+ if (failed > 0) process.exit(1);
388
+ console.log("All tests passed ✓");
@@ -0,0 +1,118 @@
1
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+
5
+ import {
6
+ getSuggestedNextCommands,
7
+ indexWorkspace,
8
+ listDoctorScopeSuggestions,
9
+ } from "../workspace-index.ts";
10
+
11
+ let passed = 0;
12
+ let failed = 0;
13
+
14
+ function assert(condition: boolean, message: string): void {
15
+ if (condition) passed++;
16
+ else {
17
+ failed++;
18
+ console.error(` FAIL: ${message}`);
19
+ }
20
+ }
21
+
22
+ function assertEq<T>(actual: T, expected: T, message: string): void {
23
+ if (JSON.stringify(actual) === JSON.stringify(expected)) passed++;
24
+ else {
25
+ failed++;
26
+ console.error(
27
+ ` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
28
+ );
29
+ }
30
+ }
31
+
32
+ const base = mkdtempSync(join(tmpdir(), "kata-workspace-index-test-"));
33
+ const kata = join(base, ".kata");
34
+ const mDir = join(kata, "milestones", "M001");
35
+ const sDir = join(mDir, "slices", "S01");
36
+ const tDir = join(sDir, "tasks");
37
+ mkdirSync(tDir, { recursive: true });
38
+
39
+ writeFileSync(
40
+ join(mDir, "M001-ROADMAP.md"),
41
+ `# M001: Demo Milestone
42
+
43
+ ## Slices
44
+ - [ ] **S01: Demo Slice** \`risk:low\` \`depends:[]\`
45
+ > After this: demo works
46
+ `,
47
+ );
48
+
49
+ writeFileSync(
50
+ join(sDir, "S01-PLAN.md"),
51
+ `# S01: Demo Slice
52
+
53
+ **Goal:** Demo
54
+ **Demo:** Demo
55
+
56
+ ## Must-Haves
57
+ - done
58
+
59
+ ## Tasks
60
+ - [ ] **T01: Implement thing** \`est:10m\`
61
+ Task is in progress.
62
+ `,
63
+ );
64
+
65
+ writeFileSync(
66
+ join(tDir, "T01-PLAN.md"),
67
+ `# T01: Implement thing
68
+
69
+ ## Steps
70
+ - do it
71
+ `,
72
+ );
73
+
74
+ async function main(): Promise<void> {
75
+ console.log("\n=== workspace index ===");
76
+ {
77
+ const index = await indexWorkspace(base);
78
+ assertEq(index.active.milestoneId, "M001", "active milestone indexed");
79
+ assertEq(index.active.sliceId, "S01", "active slice indexed");
80
+ assertEq(index.active.taskId, "T01", "active task indexed");
81
+ assert(
82
+ index.scopes.some((scope) => scope.scope === "M001/S01"),
83
+ "slice scope listed",
84
+ );
85
+ assert(
86
+ index.scopes.some((scope) => scope.scope === "M001/S01/T01"),
87
+ "task scope listed",
88
+ );
89
+ }
90
+
91
+ console.log("\n=== doctor scope suggestions ===");
92
+ {
93
+ const suggestions = await listDoctorScopeSuggestions(base);
94
+ assertEq(suggestions[0].value, "M001/S01", "active slice suggested first");
95
+ assert(
96
+ suggestions.some((item) => item.value === "M001/S01/T01"),
97
+ "task scope suggested",
98
+ );
99
+ }
100
+
101
+ console.log("\n=== next command suggestions ===");
102
+ {
103
+ const commands = await getSuggestedNextCommands(base);
104
+ assert(commands.includes("/kata auto"), "suggests auto during execution");
105
+ assert(commands.includes("/kata doctor M001/S01"), "suggests scoped doctor");
106
+ assert(commands.includes("/kata status"), "suggests status");
107
+ }
108
+
109
+ rmSync(base, { recursive: true, force: true });
110
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
111
+ if (failed > 0) process.exit(1);
112
+ console.log("All tests passed ✓");
113
+ }
114
+
115
+ main().catch((error) => {
116
+ console.error(error);
117
+ process.exit(1);
118
+ });