@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.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/app-paths.d.ts +4 -0
- package/dist/app-paths.js +6 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +56 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +95 -0
- package/dist/resource-loader.d.ts +18 -0
- package/dist/resource-loader.js +50 -0
- package/dist/wizard.d.ts +15 -0
- package/dist/wizard.js +159 -0
- package/package.json +50 -21
- package/pkg/dist/modes/interactive/theme/dark.json +85 -0
- package/pkg/dist/modes/interactive/theme/light.json +84 -0
- package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.js +949 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
- package/pkg/package.json +8 -0
- package/scripts/postinstall.js +45 -0
- package/src/resources/AGENTS.md +108 -0
- package/src/resources/KATA-WORKFLOW.md +661 -0
- package/src/resources/agents/researcher.md +29 -0
- package/src/resources/agents/scout.md +56 -0
- package/src/resources/agents/worker.md +31 -0
- package/src/resources/extensions/ask-user-questions.ts +200 -0
- package/src/resources/extensions/bg-shell/index.ts +2758 -0
- package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
- package/src/resources/extensions/browser-tools/core.js +1057 -0
- package/src/resources/extensions/browser-tools/index.ts +4916 -0
- package/src/resources/extensions/browser-tools/package.json +20 -0
- package/src/resources/extensions/context7/index.ts +428 -0
- package/src/resources/extensions/context7/package.json +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +352 -0
- package/src/resources/extensions/github/formatters.ts +207 -0
- package/src/resources/extensions/github/gh-api.ts +537 -0
- package/src/resources/extensions/github/index.ts +778 -0
- package/src/resources/extensions/kata/activity-log.ts +88 -0
- package/src/resources/extensions/kata/auto.ts +2786 -0
- package/src/resources/extensions/kata/commands.ts +355 -0
- package/src/resources/extensions/kata/crash-recovery.ts +85 -0
- package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
- package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
- package/src/resources/extensions/kata/doctor.ts +683 -0
- package/src/resources/extensions/kata/files.ts +730 -0
- package/src/resources/extensions/kata/gitignore.ts +165 -0
- package/src/resources/extensions/kata/guided-flow.ts +976 -0
- package/src/resources/extensions/kata/index.ts +556 -0
- package/src/resources/extensions/kata/metrics.ts +397 -0
- package/src/resources/extensions/kata/observability-validator.ts +408 -0
- package/src/resources/extensions/kata/package.json +11 -0
- package/src/resources/extensions/kata/paths.ts +346 -0
- package/src/resources/extensions/kata/preferences.ts +695 -0
- package/src/resources/extensions/kata/prompt-loader.ts +50 -0
- package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
- package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
- package/src/resources/extensions/kata/prompts/discuss.md +151 -0
- package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
- package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
- package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
- package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
- package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
- package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
- package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
- package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
- package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
- package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
- package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
- package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
- package/src/resources/extensions/kata/prompts/queue.md +85 -0
- package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
- package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
- package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
- package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
- package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
- package/src/resources/extensions/kata/prompts/system.md +341 -0
- package/src/resources/extensions/kata/session-forensics.ts +550 -0
- package/src/resources/extensions/kata/skill-discovery.ts +137 -0
- package/src/resources/extensions/kata/state.ts +509 -0
- package/src/resources/extensions/kata/templates/context.md +76 -0
- package/src/resources/extensions/kata/templates/decisions.md +8 -0
- package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
- package/src/resources/extensions/kata/templates/plan.md +133 -0
- package/src/resources/extensions/kata/templates/preferences.md +15 -0
- package/src/resources/extensions/kata/templates/project.md +31 -0
- package/src/resources/extensions/kata/templates/reassessment.md +28 -0
- package/src/resources/extensions/kata/templates/requirements.md +81 -0
- package/src/resources/extensions/kata/templates/research.md +46 -0
- package/src/resources/extensions/kata/templates/roadmap.md +118 -0
- package/src/resources/extensions/kata/templates/slice-context.md +58 -0
- package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
- package/src/resources/extensions/kata/templates/state.md +19 -0
- package/src/resources/extensions/kata/templates/task-plan.md +52 -0
- package/src/resources/extensions/kata/templates/task-summary.md +57 -0
- package/src/resources/extensions/kata/templates/uat.md +54 -0
- package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
- package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
- package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
- package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
- package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
- package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
- package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
- package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
- package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
- package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
- package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
- package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
- package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
- package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
- package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
- package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
- package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
- package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
- package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
- package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
- package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
- package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
- package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
- package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
- package/src/resources/extensions/kata/types.ts +159 -0
- package/src/resources/extensions/kata/unit-runtime.ts +163 -0
- package/src/resources/extensions/kata/workspace-index.ts +203 -0
- package/src/resources/extensions/kata/worktree.ts +182 -0
- package/src/resources/extensions/mac-tools/index.ts +852 -0
- package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
- package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
- package/src/resources/extensions/search-the-web/cache.ts +78 -0
- package/src/resources/extensions/search-the-web/format.ts +258 -0
- package/src/resources/extensions/search-the-web/http.ts +238 -0
- package/src/resources/extensions/search-the-web/index.ts +68 -0
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
- package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
- package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
- package/src/resources/extensions/shared/confirm-ui.ts +126 -0
- package/src/resources/extensions/shared/interview-ui.ts +822 -0
- package/src/resources/extensions/shared/next-action-ui.ts +235 -0
- package/src/resources/extensions/shared/progress-widget.ts +282 -0
- package/src/resources/extensions/shared/thinking-widget.ts +107 -0
- package/src/resources/extensions/shared/ui.ts +400 -0
- package/src/resources/extensions/shared/wizard-ui.ts +551 -0
- package/src/resources/extensions/slash-commands/audit.ts +92 -0
- package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
- package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
- package/src/resources/extensions/slash-commands/index.ts +12 -0
- package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
- package/src/resources/extensions/subagent/agents.ts +126 -0
- package/src/resources/extensions/subagent/index.ts +1293 -0
- package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
- package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
- package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
- package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
- package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
- package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
- package/src/resources/skills/frontend-design/SKILL.md +45 -0
- package/src/resources/skills/swiftui/SKILL.md +208 -0
- package/src/resources/skills/swiftui/references/animations.md +921 -0
- package/src/resources/skills/swiftui/references/architecture.md +1561 -0
- package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
- package/src/resources/skills/swiftui/references/navigation.md +1492 -0
- package/src/resources/skills/swiftui/references/networking-async.md +214 -0
- package/src/resources/skills/swiftui/references/performance.md +1706 -0
- package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
- package/src/resources/skills/swiftui/references/state-management.md +1443 -0
- package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
- package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
- package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
- package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
- package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
- package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
- package/dist/commands/task.d.ts +0 -9
- package/dist/commands/task.d.ts.map +0 -1
- package/dist/commands/task.js +0 -129
- package/dist/commands/task.js.map +0 -1
- package/dist/commands/task.test.d.ts +0 -2
- package/dist/commands/task.test.d.ts.map +0 -1
- package/dist/commands/task.test.js +0 -169
- package/dist/commands/task.test.js.map +0 -1
- package/dist/e2e/task-e2e.test.d.ts +0 -2
- package/dist/e2e/task-e2e.test.d.ts.map +0 -1
- package/dist/e2e/task-e2e.test.js +0 -173
- package/dist/e2e/task-e2e.test.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -93
- package/dist/index.js.map +0 -1
- package/dist/slug.d.ts +0 -2
- package/dist/slug.d.ts.map +0 -1
- package/dist/slug.js +0 -12
- package/dist/slug.js.map +0 -1
- package/dist/slug.test.d.ts +0 -2
- package/dist/slug.test.d.ts.map +0 -1
- package/dist/slug.test.js +0 -32
- 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
|
+
}
|