@kata-sh/cli 0.1.0 → 0.1.1

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,686 @@
1
+ import {
2
+ mkdtempSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { join, dirname } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ import { parseSummary } from "../files.ts";
13
+ import { deriveState } from "../state.ts";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const worktreePromptsDir = join(__dirname, "..", "prompts");
17
+
18
+ /**
19
+ * Load a prompt template from the worktree prompts directory
20
+ * and apply variable substitution (mirrors loadPrompt logic).
21
+ */
22
+ function loadPromptFromWorktree(
23
+ name: string,
24
+ vars: Record<string, string> = {},
25
+ ): string {
26
+ const path = join(worktreePromptsDir, `${name}.md`);
27
+ let content = readFileSync(path, "utf-8");
28
+ for (const [key, value] of Object.entries(vars)) {
29
+ content = content.replaceAll(`{{${key}}}`, value);
30
+ }
31
+ return content.trim();
32
+ }
33
+
34
+ let passed = 0;
35
+ let failed = 0;
36
+
37
+ function assert(condition: boolean, message: string): void {
38
+ if (condition) {
39
+ passed++;
40
+ } else {
41
+ failed++;
42
+ console.error(` FAIL: ${message}`);
43
+ }
44
+ }
45
+
46
+ function assertEq<T>(actual: T, expected: T, message: string): void {
47
+ if (JSON.stringify(actual) === JSON.stringify(expected)) {
48
+ passed++;
49
+ } else {
50
+ failed++;
51
+ console.error(
52
+ ` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
53
+ );
54
+ }
55
+ }
56
+
57
+ // ─── Fixture Helpers ───────────────────────────────────────────────────────
58
+
59
+ function createFixtureBase(): string {
60
+ const base = mkdtempSync(join(tmpdir(), "kata-replan-test-"));
61
+ mkdirSync(join(base, ".kata", "milestones"), { recursive: true });
62
+ return base;
63
+ }
64
+
65
+ function writeRoadmap(base: string, mid: string, content: string): void {
66
+ const dir = join(base, ".kata", "milestones", mid);
67
+ mkdirSync(dir, { recursive: true });
68
+ writeFileSync(join(dir, `${mid}-ROADMAP.md`), content);
69
+ }
70
+
71
+ function writePlan(
72
+ base: string,
73
+ mid: string,
74
+ sid: string,
75
+ content: string,
76
+ ): void {
77
+ const dir = join(base, ".kata", "milestones", mid, "slices", sid);
78
+ mkdirSync(join(dir, "tasks"), { recursive: true });
79
+ writeFileSync(join(dir, `${sid}-PLAN.md`), content);
80
+ }
81
+
82
+ function writeTaskSummary(
83
+ base: string,
84
+ mid: string,
85
+ sid: string,
86
+ tid: string,
87
+ content: string,
88
+ ): void {
89
+ const dir = join(base, ".kata", "milestones", mid, "slices", sid, "tasks");
90
+ mkdirSync(dir, { recursive: true });
91
+ writeFileSync(join(dir, `${tid}-SUMMARY.md`), content);
92
+ }
93
+
94
+ function writeReplanFile(
95
+ base: string,
96
+ mid: string,
97
+ sid: string,
98
+ content: string,
99
+ ): void {
100
+ const dir = join(base, ".kata", "milestones", mid, "slices", sid);
101
+ mkdirSync(dir, { recursive: true });
102
+ writeFileSync(join(dir, `${sid}-REPLAN.md`), content);
103
+ }
104
+
105
+ /** Standard roadmap with one slice having no dependencies */
106
+ const ROADMAP_ONE_SLICE = `# M001: Test Milestone
107
+
108
+ **Vision:** Test vision.
109
+
110
+ ## Slices
111
+
112
+ - [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
113
+ > After this: stuff works
114
+ `;
115
+
116
+ /** Plan with T01 done, T02 not done */
117
+ function makePlanT01DoneT02Pending(): string {
118
+ return `# S01: Test Slice
119
+
120
+ **Goal:** Do things.
121
+ **Demo:** It works.
122
+
123
+ ## Tasks
124
+
125
+ - [x] **T01: First task** \`est:15m\`
126
+ First task description.
127
+
128
+ - [ ] **T02: Second task** \`est:15m\`
129
+ Second task description.
130
+ `;
131
+ }
132
+
133
+ /** Plan with T01 and T02 done, T03 not done */
134
+ function makePlanT01T02DoneT03Pending(): string {
135
+ return `# S01: Test Slice
136
+
137
+ **Goal:** Do things.
138
+ **Demo:** It works.
139
+
140
+ ## Tasks
141
+
142
+ - [x] **T01: First task** \`est:15m\`
143
+ First task description.
144
+
145
+ - [x] **T02: Second task** \`est:15m\`
146
+ Second task description.
147
+
148
+ - [ ] **T03: Third task** \`est:15m\`
149
+ Third task description.
150
+ `;
151
+ }
152
+
153
+ /** Minimal task summary with blocker_discovered flag */
154
+ function makeTaskSummary(tid: string, blockerDiscovered: boolean): string {
155
+ return `---
156
+ id: ${tid}
157
+ parent: S01
158
+ milestone: M001
159
+ provides: []
160
+ key_files: []
161
+ key_decisions: []
162
+ patterns_established: []
163
+ observability_surfaces: []
164
+ duration: 15min
165
+ verification_result: passed
166
+ completed_at: 2025-03-10T12:00:00Z
167
+ blocker_discovered: ${blockerDiscovered}
168
+ ---
169
+
170
+ # ${tid}: Test Task
171
+
172
+ **Did something.**
173
+
174
+ ## What Happened
175
+
176
+ Work was done.
177
+ `;
178
+ }
179
+
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+ // Parser Extraction: blocker_discovered
182
+ // ═══════════════════════════════════════════════════════════════════════════
183
+
184
+ console.log("\n=== parseSummary: blocker_discovered true (string) ===");
185
+ {
186
+ const content = `---
187
+ id: T01
188
+ parent: S03
189
+ milestone: M002
190
+ blocker_discovered: true
191
+ completed_at: 2025-03-10T12:00:00Z
192
+ ---
193
+
194
+ # T01: Test Task
195
+
196
+ **One-liner.**
197
+
198
+ ## What Happened
199
+
200
+ Found a blocker.
201
+ `;
202
+
203
+ const s = parseSummary(content);
204
+ assertEq(
205
+ s.frontmatter.blocker_discovered,
206
+ true,
207
+ "blocker_discovered: true (string) extracts as true",
208
+ );
209
+ }
210
+
211
+ console.log("\n=== parseSummary: blocker_discovered false (string) ===");
212
+ {
213
+ const content = `---
214
+ id: T02
215
+ parent: S03
216
+ milestone: M002
217
+ blocker_discovered: false
218
+ completed_at: 2025-03-10T12:00:00Z
219
+ ---
220
+
221
+ # T02: Normal Task
222
+
223
+ **One-liner.**
224
+
225
+ ## What Happened
226
+
227
+ No blocker.
228
+ `;
229
+
230
+ const s = parseSummary(content);
231
+ assertEq(
232
+ s.frontmatter.blocker_discovered,
233
+ false,
234
+ "blocker_discovered: false extracts as false",
235
+ );
236
+ }
237
+
238
+ console.log(
239
+ "\n=== parseSummary: blocker_discovered missing (defaults to false) ===",
240
+ );
241
+ {
242
+ const content = `---
243
+ id: T03
244
+ parent: S03
245
+ milestone: M002
246
+ completed_at: 2025-03-10T12:00:00Z
247
+ ---
248
+
249
+ # T03: No Blocker Field
250
+
251
+ **One-liner.**
252
+
253
+ ## What Happened
254
+
255
+ No blocker field at all.
256
+ `;
257
+
258
+ const s = parseSummary(content);
259
+ assertEq(
260
+ s.frontmatter.blocker_discovered,
261
+ false,
262
+ "blocker_discovered missing defaults to false",
263
+ );
264
+ }
265
+
266
+ console.log(
267
+ "\n=== parseSummary: blocker_discovered true (boolean from YAML) ===",
268
+ );
269
+ {
270
+ // YAML parsers may deliver `true` as a boolean rather than the string "true"
271
+ // We test this via a summary that has blocker_discovered: true with no quotes
272
+ // The YAML parser in parseFrontmatterMap may return boolean true directly
273
+ const content = `---
274
+ id: T04
275
+ parent: S03
276
+ milestone: M002
277
+ blocker_discovered: true
278
+ completed_at: 2025-03-10T12:00:00Z
279
+ ---
280
+
281
+ # T04: Boolean True
282
+
283
+ **One-liner.**
284
+
285
+ ## What Happened
286
+
287
+ Blocker as boolean.
288
+ `;
289
+
290
+ const s = parseSummary(content);
291
+ assertEq(
292
+ s.frontmatter.blocker_discovered,
293
+ true,
294
+ "blocker_discovered: true (YAML boolean) extracts as true",
295
+ );
296
+ }
297
+
298
+ console.log("\n=== parseSummary: blocker_discovered with full frontmatter ===");
299
+ {
300
+ const content = `---
301
+ id: T05
302
+ parent: S03
303
+ milestone: M002
304
+ provides:
305
+ - something
306
+ requires: []
307
+ affects: []
308
+ key_files:
309
+ - files.ts
310
+ key_decisions: []
311
+ patterns_established: []
312
+ drill_down_paths: []
313
+ observability_surfaces: []
314
+ duration: 15min
315
+ verification_result: passed
316
+ completed_at: 2025-03-10T12:00:00Z
317
+ blocker_discovered: true
318
+ ---
319
+
320
+ # T05: Full Frontmatter With Blocker
321
+
322
+ **Found an architectural mismatch.**
323
+
324
+ ## What Happened
325
+
326
+ The API doesn't support what we assumed.
327
+
328
+ ## Deviations
329
+
330
+ Major deviation from plan.
331
+
332
+ ## Files Created/Modified
333
+
334
+ - \`files.ts\` — attempted changes
335
+ `;
336
+
337
+ const s = parseSummary(content);
338
+ assertEq(
339
+ s.frontmatter.blocker_discovered,
340
+ true,
341
+ "blocker_discovered true with full frontmatter",
342
+ );
343
+ assertEq(
344
+ s.frontmatter.id,
345
+ "T05",
346
+ "other fields still parse correctly alongside blocker_discovered",
347
+ );
348
+ assertEq(s.frontmatter.duration, "15min", "duration still parsed");
349
+ assertEq(s.frontmatter.provides[0], "something", "provides still parsed");
350
+ }
351
+
352
+ // ═══════════════════════════════════════════════════════════════════════════
353
+ // State Detection: replanning-slice phase
354
+ // ═══════════════════════════════════════════════════════════════════════════
355
+
356
+ // (a) blocker found + no REPLAN.md → replanning-slice
357
+ console.log(
358
+ "\n=== deriveState: blocker found, no REPLAN → replanning-slice ===",
359
+ );
360
+ {
361
+ const base = createFixtureBase();
362
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
363
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
364
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", true));
365
+
366
+ const state = await deriveState(base);
367
+ assertEq(
368
+ state.phase,
369
+ "replanning-slice",
370
+ "phase is replanning-slice when blocker found and no REPLAN.md",
371
+ );
372
+ assert(
373
+ state.nextAction.includes("T01"),
374
+ "nextAction mentions blocker task T01",
375
+ );
376
+ assert(
377
+ state.nextAction.includes("blocker_discovered"),
378
+ "nextAction mentions blocker_discovered",
379
+ );
380
+ assertEq(
381
+ state.activeTask?.id,
382
+ "T02",
383
+ "activeTask is still T02 (the next incomplete task)",
384
+ );
385
+ assert(state.blockers.length > 0, "blockers array is non-empty");
386
+ rmSync(base, { recursive: true, force: true });
387
+ }
388
+
389
+ // (b) blocker found + REPLAN.md exists → executing (loop protection)
390
+ console.log(
391
+ "\n=== deriveState: blocker found + REPLAN exists → executing (loop protection) ===",
392
+ );
393
+ {
394
+ const base = createFixtureBase();
395
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
396
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
397
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", true));
398
+ writeReplanFile(base, "M001", "S01", "# Replan\n\nAlready replanned.");
399
+
400
+ const state = await deriveState(base);
401
+ assertEq(
402
+ state.phase,
403
+ "executing",
404
+ "phase is executing when REPLAN.md exists (loop protection)",
405
+ );
406
+ assertEq(state.activeTask?.id, "T02", "activeTask is T02");
407
+ rmSync(base, { recursive: true, force: true });
408
+ }
409
+
410
+ // (c) no blocker → executing
411
+ console.log("\n=== deriveState: no blocker in completed tasks → executing ===");
412
+ {
413
+ const base = createFixtureBase();
414
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
415
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
416
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", false));
417
+
418
+ const state = await deriveState(base);
419
+ assertEq(
420
+ state.phase,
421
+ "executing",
422
+ "phase is executing when no blocker found",
423
+ );
424
+ assertEq(state.activeTask?.id, "T02", "activeTask is T02");
425
+ rmSync(base, { recursive: true, force: true });
426
+ }
427
+
428
+ // (d) multiple completed tasks, one with blocker → replanning-slice
429
+ console.log(
430
+ "\n=== deriveState: multiple completed tasks, one blocker → replanning-slice ===",
431
+ );
432
+ {
433
+ const base = createFixtureBase();
434
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
435
+ writePlan(base, "M001", "S01", makePlanT01T02DoneT03Pending());
436
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", false));
437
+ writeTaskSummary(base, "M001", "S01", "T02", makeTaskSummary("T02", true));
438
+
439
+ const state = await deriveState(base);
440
+ assertEq(
441
+ state.phase,
442
+ "replanning-slice",
443
+ "phase is replanning-slice when T02 has blocker",
444
+ );
445
+ assert(
446
+ state.nextAction.includes("T02"),
447
+ "nextAction mentions blocker task T02",
448
+ );
449
+ assertEq(state.activeTask?.id, "T03", "activeTask is T03 (next incomplete)");
450
+ rmSync(base, { recursive: true, force: true });
451
+ }
452
+
453
+ // (e) completed task with no summary file → executing (gracefully skipped)
454
+ console.log(
455
+ "\n=== deriveState: completed task with no summary file → executing ===",
456
+ );
457
+ {
458
+ const base = createFixtureBase();
459
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
460
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
461
+ // No summary file written for T01
462
+
463
+ const state = await deriveState(base);
464
+ assertEq(
465
+ state.phase,
466
+ "executing",
467
+ "phase is executing when completed task has no summary",
468
+ );
469
+ rmSync(base, { recursive: true, force: true });
470
+ }
471
+
472
+ // ═══════════════════════════════════════════════════════════════════════════
473
+ // Prompt: replan-slice template loading and substitution
474
+ // ═══════════════════════════════════════════════════════════════════════════
475
+
476
+ console.log(
477
+ "\n=== prompt: replan-slice template loads and substitutes variables ===",
478
+ );
479
+ {
480
+ const prompt = loadPromptFromWorktree("replan-slice", {
481
+ milestoneId: "M001",
482
+ sliceId: "S01",
483
+ sliceTitle: "Test Slice",
484
+ slicePath: ".kata/milestones/M001/slices/S01",
485
+ planPath: ".kata/milestones/M001/slices/S01/S01-PLAN.md",
486
+ blockerTaskId: "T02",
487
+ inlinedContext: "## Inlined Context\n\nTest context here.",
488
+ });
489
+
490
+ assert(prompt.includes("M001"), "prompt contains milestoneId");
491
+ assert(prompt.includes("S01"), "prompt contains sliceId");
492
+ assert(prompt.includes("Test Slice"), "prompt contains sliceTitle");
493
+ assert(
494
+ prompt.includes(".kata/milestones/M001/slices/S01/S01-PLAN.md"),
495
+ "prompt contains planPath",
496
+ );
497
+ assert(prompt.includes("T02"), "prompt contains blockerTaskId");
498
+ assert(
499
+ prompt.includes("Test context here"),
500
+ "prompt contains inlined context",
501
+ );
502
+ }
503
+
504
+ console.log(
505
+ "\n=== prompt: replan-slice contains preserve-completed-tasks instruction ===",
506
+ );
507
+ {
508
+ const prompt = loadPromptFromWorktree("replan-slice", {
509
+ milestoneId: "M001",
510
+ sliceId: "S01",
511
+ sliceTitle: "Test Slice",
512
+ slicePath: ".kata/milestones/M001/slices/S01",
513
+ planPath: ".kata/milestones/M001/slices/S01/S01-PLAN.md",
514
+ blockerTaskId: "T01",
515
+ inlinedContext: "",
516
+ });
517
+
518
+ assert(
519
+ prompt.includes("Do NOT renumber or remove completed tasks"),
520
+ "prompt contains preserve-completed-tasks instruction",
521
+ );
522
+ assert(prompt.includes("[x]"), "prompt mentions [x] checkmarks");
523
+ assert(
524
+ prompt.includes("replanAbsPath") || prompt.includes("REPLAN"),
525
+ "prompt references replan output path",
526
+ );
527
+ assert(
528
+ prompt.includes("blocker_discovered"),
529
+ "prompt mentions blocker_discovered",
530
+ );
531
+ }
532
+
533
+ // ═══════════════════════════════════════════════════════════════════════════
534
+ // Dispatch: diagnoseExpectedArtifact for replan-slice
535
+ // ═══════════════════════════════════════════════════════════════════════════
536
+
537
+ console.log(
538
+ "\n=== dispatch: diagnoseExpectedArtifact returns REPLAN.md path ===",
539
+ );
540
+ {
541
+ // We can't import diagnoseExpectedArtifact directly (it's not exported),
542
+ // but we can verify the prompt template has the right structure and
543
+ // the state machine routes correctly. The diagnose function is integration-tested
544
+ // via the dispatch chain. We verify indirectly via state phase detection.
545
+
546
+ // Verify state correctly routes to replanning-slice phase
547
+ const base = createFixtureBase();
548
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
549
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
550
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", true));
551
+
552
+ const state = await deriveState(base);
553
+ assertEq(
554
+ state.phase,
555
+ "replanning-slice",
556
+ "dispatch: state routes to replanning-slice when blocker found",
557
+ );
558
+ assert(state.activeSlice?.id === "S01", "dispatch: activeSlice is S01");
559
+ rmSync(base, { recursive: true, force: true });
560
+ }
561
+
562
+ // ═══════════════════════════════════════════════════════════════════════════
563
+ // Display Functions: unitVerb, unitPhaseLabel, peekNext entries
564
+ // ═══════════════════════════════════════════════════════════════════════════
565
+
566
+ console.log(
567
+ "\n=== display: replan-slice prompt template has correct unit header ===",
568
+ );
569
+ {
570
+ const prompt = loadPromptFromWorktree("replan-slice", {
571
+ milestoneId: "M001",
572
+ sliceId: "S01",
573
+ sliceTitle: "Test Slice",
574
+ slicePath: ".kata/milestones/M001/slices/S01",
575
+ planPath: ".kata/milestones/M001/slices/S01/S01-PLAN.md",
576
+ blockerTaskId: "T01",
577
+ inlinedContext: "",
578
+ });
579
+
580
+ assert(
581
+ prompt.includes("UNIT: Replan Slice"),
582
+ "prompt has Replan Slice unit header",
583
+ );
584
+ assert(
585
+ prompt.includes("Slice S01 replanned"),
586
+ "prompt has completion message",
587
+ );
588
+ }
589
+
590
+ // ═══════════════════════════════════════════════════════════════════════════
591
+ // Doctor: blocker_discovered_no_replan diagnostics
592
+ // ═══════════════════════════════════════════════════════════════════════════
593
+
594
+ import { runKataDoctor } from "../doctor.ts";
595
+
596
+ // (a) blocker + no REPLAN.md → issue emitted
597
+ console.log(
598
+ "\n=== doctor: blocker + no REPLAN.md → blocker_discovered_no_replan issue ===",
599
+ );
600
+ {
601
+ const base = createFixtureBase();
602
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
603
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
604
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", true));
605
+
606
+ const report = await runKataDoctor(base, { fix: false, scope: "M001/S01" });
607
+ const blockerIssues = report.issues.filter(
608
+ (i) => i.code === "blocker_discovered_no_replan",
609
+ );
610
+ assert(
611
+ blockerIssues.length > 0,
612
+ "doctor emits blocker_discovered_no_replan when blocker + no REPLAN",
613
+ );
614
+ assert(
615
+ blockerIssues[0]?.message.includes("T01"),
616
+ "issue message mentions the blocker task T01",
617
+ );
618
+ assertEq(
619
+ blockerIssues[0]?.severity,
620
+ "warning",
621
+ "blocker_discovered_no_replan is warning severity",
622
+ );
623
+ assertEq(
624
+ blockerIssues[0]?.scope,
625
+ "slice",
626
+ "blocker_discovered_no_replan has slice scope",
627
+ );
628
+ rmSync(base, { recursive: true, force: true });
629
+ }
630
+
631
+ // (b) blocker + REPLAN.md exists → no issue
632
+ console.log(
633
+ "\n=== doctor: blocker + REPLAN.md exists → no blocker_discovered_no_replan issue ===",
634
+ );
635
+ {
636
+ const base = createFixtureBase();
637
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
638
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
639
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", true));
640
+ writeReplanFile(base, "M001", "S01", "# Replan\n\nAlready replanned.");
641
+
642
+ const report = await runKataDoctor(base, { fix: false, scope: "M001/S01" });
643
+ const blockerIssues = report.issues.filter(
644
+ (i) => i.code === "blocker_discovered_no_replan",
645
+ );
646
+ assertEq(
647
+ blockerIssues.length,
648
+ 0,
649
+ "no blocker_discovered_no_replan when REPLAN.md exists",
650
+ );
651
+ rmSync(base, { recursive: true, force: true });
652
+ }
653
+
654
+ // (c) no blocker → no issue
655
+ console.log(
656
+ "\n=== doctor: no blocker → no blocker_discovered_no_replan issue ===",
657
+ );
658
+ {
659
+ const base = createFixtureBase();
660
+ writeRoadmap(base, "M001", ROADMAP_ONE_SLICE);
661
+ writePlan(base, "M001", "S01", makePlanT01DoneT02Pending());
662
+ writeTaskSummary(base, "M001", "S01", "T01", makeTaskSummary("T01", false));
663
+
664
+ const report = await runKataDoctor(base, { fix: false, scope: "M001/S01" });
665
+ const blockerIssues = report.issues.filter(
666
+ (i) => i.code === "blocker_discovered_no_replan",
667
+ );
668
+ assertEq(
669
+ blockerIssues.length,
670
+ 0,
671
+ "no blocker_discovered_no_replan when no blocker",
672
+ );
673
+ rmSync(base, { recursive: true, force: true });
674
+ }
675
+
676
+ // ═══════════════════════════════════════════════════════════════════════════
677
+ // Results
678
+ // ═══════════════════════════════════════════════════════════════════════════
679
+
680
+ console.log(`\n${"=".repeat(40)}`);
681
+ console.log(`Results: ${passed} passed, ${failed} failed`);
682
+ if (failed > 0) {
683
+ process.exit(1);
684
+ } else {
685
+ console.log("All tests passed ✓");
686
+ }