@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,1013 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
|
|
5
|
+
import { deriveState, isSliceComplete, isMilestoneComplete } from "../state.ts";
|
|
6
|
+
|
|
7
|
+
let passed = 0;
|
|
8
|
+
let failed = 0;
|
|
9
|
+
|
|
10
|
+
function assert(condition: boolean, message: string): void {
|
|
11
|
+
if (condition) {
|
|
12
|
+
passed++;
|
|
13
|
+
} else {
|
|
14
|
+
failed++;
|
|
15
|
+
console.error(` FAIL: ${message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
20
|
+
if (JSON.stringify(actual) === JSON.stringify(expected)) {
|
|
21
|
+
passed++;
|
|
22
|
+
} else {
|
|
23
|
+
failed++;
|
|
24
|
+
console.error(
|
|
25
|
+
` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function createFixtureBase(): string {
|
|
33
|
+
const base = mkdtempSync(join(tmpdir(), "kata-state-test-"));
|
|
34
|
+
mkdirSync(join(base, ".kata", "milestones"), { recursive: true });
|
|
35
|
+
return base;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeRoadmap(base: string, mid: string, content: string): void {
|
|
39
|
+
const dir = join(base, ".kata", "milestones", mid);
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
writeFileSync(join(dir, `${mid}-ROADMAP.md`), content);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function writePlan(
|
|
45
|
+
base: string,
|
|
46
|
+
mid: string,
|
|
47
|
+
sid: string,
|
|
48
|
+
content: string,
|
|
49
|
+
): void {
|
|
50
|
+
const dir = join(base, ".kata", "milestones", mid, "slices", sid);
|
|
51
|
+
mkdirSync(join(dir, "tasks"), { recursive: true });
|
|
52
|
+
writeFileSync(join(dir, `${sid}-PLAN.md`), content);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeContinue(
|
|
56
|
+
base: string,
|
|
57
|
+
mid: string,
|
|
58
|
+
sid: string,
|
|
59
|
+
content: string,
|
|
60
|
+
): void {
|
|
61
|
+
const dir = join(base, ".kata", "milestones", mid, "slices", sid);
|
|
62
|
+
mkdirSync(dir, { recursive: true });
|
|
63
|
+
writeFileSync(join(dir, `${sid}-CONTINUE.md`), content);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeMilestoneSummary(
|
|
67
|
+
base: string,
|
|
68
|
+
mid: string,
|
|
69
|
+
content: string,
|
|
70
|
+
): void {
|
|
71
|
+
const dir = join(base, ".kata", "milestones", mid);
|
|
72
|
+
mkdirSync(dir, { recursive: true });
|
|
73
|
+
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeRequirements(base: string, content: string): void {
|
|
77
|
+
writeFileSync(join(base, ".kata", "REQUIREMENTS.md"), content);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cleanup(base: string): void {
|
|
81
|
+
rmSync(base, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
85
|
+
// Test Groups
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
87
|
+
|
|
88
|
+
async function main(): Promise<void> {
|
|
89
|
+
// ─── Test 1: empty milestones dir → pre-planning ───────────────────────
|
|
90
|
+
console.log("\n=== empty milestones dir → pre-planning ===");
|
|
91
|
+
{
|
|
92
|
+
const base = createFixtureBase();
|
|
93
|
+
try {
|
|
94
|
+
const state = await deriveState(base);
|
|
95
|
+
|
|
96
|
+
assertEq(state.phase, "pre-planning", "phase is pre-planning");
|
|
97
|
+
assertEq(state.activeMilestone, null, "activeMilestone is null");
|
|
98
|
+
assertEq(state.activeSlice, null, "activeSlice is null");
|
|
99
|
+
assertEq(state.activeTask, null, "activeTask is null");
|
|
100
|
+
assertEq(state.registry, [], "registry is empty");
|
|
101
|
+
assertEq(state.progress?.milestones?.done, 0, "milestones done = 0");
|
|
102
|
+
assertEq(state.progress?.milestones?.total, 0, "milestones total = 0");
|
|
103
|
+
} finally {
|
|
104
|
+
cleanup(base);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Test 2: milestone dir exists but no roadmap → pre-planning ────────
|
|
109
|
+
console.log("\n=== milestone dir exists but no roadmap → pre-planning ===");
|
|
110
|
+
{
|
|
111
|
+
const base = createFixtureBase();
|
|
112
|
+
try {
|
|
113
|
+
// Create M001 directory but no roadmap file
|
|
114
|
+
mkdirSync(join(base, ".kata", "milestones", "M001"), { recursive: true });
|
|
115
|
+
|
|
116
|
+
const state = await deriveState(base);
|
|
117
|
+
|
|
118
|
+
assertEq(state.phase, "pre-planning", "phase is pre-planning");
|
|
119
|
+
assert(state.activeMilestone !== null, "activeMilestone is not null");
|
|
120
|
+
assertEq(state.activeMilestone?.id, "M001", "activeMilestone id is M001");
|
|
121
|
+
assertEq(state.activeSlice, null, "activeSlice is null");
|
|
122
|
+
assertEq(state.activeTask, null, "activeTask is null");
|
|
123
|
+
assertEq(state.registry.length, 1, "registry has 1 entry");
|
|
124
|
+
assertEq(
|
|
125
|
+
state.registry[0]?.status,
|
|
126
|
+
"active",
|
|
127
|
+
"registry entry status is active",
|
|
128
|
+
);
|
|
129
|
+
} finally {
|
|
130
|
+
cleanup(base);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Test 3: roadmap with incomplete slice, no plan → planning ─────────
|
|
135
|
+
console.log("\n=== roadmap with incomplete slice, no plan → planning ===");
|
|
136
|
+
{
|
|
137
|
+
const base = createFixtureBase();
|
|
138
|
+
try {
|
|
139
|
+
writeRoadmap(
|
|
140
|
+
base,
|
|
141
|
+
"M001",
|
|
142
|
+
`# M001: Test Milestone
|
|
143
|
+
|
|
144
|
+
**Vision:** Test planning phase.
|
|
145
|
+
|
|
146
|
+
## Slices
|
|
147
|
+
|
|
148
|
+
- [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
|
|
149
|
+
> After this: Slice is done.
|
|
150
|
+
`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const state = await deriveState(base);
|
|
154
|
+
|
|
155
|
+
assertEq(state.phase, "planning", "phase is planning");
|
|
156
|
+
assert(state.activeSlice !== null, "activeSlice is not null");
|
|
157
|
+
assertEq(state.activeSlice?.id, "S01", "activeSlice id is S01");
|
|
158
|
+
assertEq(state.activeTask, null, "activeTask is null");
|
|
159
|
+
assertEq(state.progress?.slices?.done, 0, "slices done = 0");
|
|
160
|
+
assertEq(state.progress?.slices?.total, 1, "slices total = 1");
|
|
161
|
+
} finally {
|
|
162
|
+
cleanup(base);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ─── Test 4: roadmap + plan with incomplete tasks → executing ──────────
|
|
167
|
+
console.log("\n=== roadmap + plan with incomplete tasks → executing ===");
|
|
168
|
+
{
|
|
169
|
+
const base = createFixtureBase();
|
|
170
|
+
try {
|
|
171
|
+
writeRoadmap(
|
|
172
|
+
base,
|
|
173
|
+
"M001",
|
|
174
|
+
`# M001: Test Milestone
|
|
175
|
+
|
|
176
|
+
**Vision:** Test executing phase.
|
|
177
|
+
|
|
178
|
+
## Slices
|
|
179
|
+
|
|
180
|
+
- [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
|
|
181
|
+
> After this: Slice is done.
|
|
182
|
+
`,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
writePlan(
|
|
186
|
+
base,
|
|
187
|
+
"M001",
|
|
188
|
+
"S01",
|
|
189
|
+
`# S01: Test Slice
|
|
190
|
+
|
|
191
|
+
**Goal:** Test executing.
|
|
192
|
+
**Demo:** Tests pass.
|
|
193
|
+
|
|
194
|
+
## Tasks
|
|
195
|
+
|
|
196
|
+
- [ ] **T01: First** \`est:10m\`
|
|
197
|
+
First task description.
|
|
198
|
+
|
|
199
|
+
- [ ] **T02: Second** \`est:10m\`
|
|
200
|
+
Second task description.
|
|
201
|
+
`,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const state = await deriveState(base);
|
|
205
|
+
|
|
206
|
+
assertEq(state.phase, "executing", "phase is executing");
|
|
207
|
+
assert(state.activeTask !== null, "activeTask is not null");
|
|
208
|
+
assertEq(state.activeTask?.id, "T01", "activeTask id is T01");
|
|
209
|
+
assertEq(state.progress?.tasks?.done, 0, "tasks done = 0");
|
|
210
|
+
assertEq(state.progress?.tasks?.total, 2, "tasks total = 2");
|
|
211
|
+
} finally {
|
|
212
|
+
cleanup(base);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Test 5: executing + continue file → resume message ─────────────
|
|
217
|
+
console.log("\n=== executing + continue file → resume message ===");
|
|
218
|
+
{
|
|
219
|
+
const base = createFixtureBase();
|
|
220
|
+
try {
|
|
221
|
+
writeRoadmap(
|
|
222
|
+
base,
|
|
223
|
+
"M001",
|
|
224
|
+
`# M001: Test Milestone
|
|
225
|
+
|
|
226
|
+
**Vision:** Test interrupted resume.
|
|
227
|
+
|
|
228
|
+
## Slices
|
|
229
|
+
|
|
230
|
+
- [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
|
|
231
|
+
> After this: Slice is done.
|
|
232
|
+
`,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
writePlan(
|
|
236
|
+
base,
|
|
237
|
+
"M001",
|
|
238
|
+
"S01",
|
|
239
|
+
`# S01: Test Slice
|
|
240
|
+
|
|
241
|
+
**Goal:** Test interrupted.
|
|
242
|
+
**Demo:** Tests pass.
|
|
243
|
+
|
|
244
|
+
## Tasks
|
|
245
|
+
|
|
246
|
+
- [ ] **T01: First Task** \`est:10m\`
|
|
247
|
+
First task description.
|
|
248
|
+
`,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
writeContinue(
|
|
252
|
+
base,
|
|
253
|
+
"M001",
|
|
254
|
+
"S01",
|
|
255
|
+
`---
|
|
256
|
+
milestone: M001
|
|
257
|
+
slice: S01
|
|
258
|
+
task: T01
|
|
259
|
+
step: 2
|
|
260
|
+
totalSteps: 5
|
|
261
|
+
status: interrupted
|
|
262
|
+
savedAt: 2026-03-10T10:00:00Z
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
# Continue: T01
|
|
266
|
+
|
|
267
|
+
## Completed Work
|
|
268
|
+
Steps 1 done.
|
|
269
|
+
|
|
270
|
+
## Remaining Work
|
|
271
|
+
Steps 2-5.
|
|
272
|
+
|
|
273
|
+
## Next Action
|
|
274
|
+
Continue from step 2.
|
|
275
|
+
`,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const state = await deriveState(base);
|
|
279
|
+
|
|
280
|
+
assertEq(state.phase, "executing", "interrupted: phase is executing");
|
|
281
|
+
assert(state.activeTask !== null, "interrupted: activeTask is not null");
|
|
282
|
+
assertEq(
|
|
283
|
+
state.activeTask?.id,
|
|
284
|
+
"T01",
|
|
285
|
+
"interrupted: activeTask id is T01",
|
|
286
|
+
);
|
|
287
|
+
assert(
|
|
288
|
+
state.nextAction.includes("Resume") ||
|
|
289
|
+
state.nextAction.includes("resume") ||
|
|
290
|
+
state.nextAction.includes("continue.md"),
|
|
291
|
+
"interrupted: nextAction mentions Resume/resume/continue.md",
|
|
292
|
+
);
|
|
293
|
+
} finally {
|
|
294
|
+
cleanup(base);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Test 6: all tasks done, slice not [x] → summarizing ──────────────
|
|
299
|
+
console.log("\n=== all tasks done, slice not [x] → summarizing ===");
|
|
300
|
+
{
|
|
301
|
+
const base = createFixtureBase();
|
|
302
|
+
try {
|
|
303
|
+
writeRoadmap(
|
|
304
|
+
base,
|
|
305
|
+
"M001",
|
|
306
|
+
`# M001: Test Milestone
|
|
307
|
+
|
|
308
|
+
**Vision:** Test summarizing phase.
|
|
309
|
+
|
|
310
|
+
## Slices
|
|
311
|
+
|
|
312
|
+
- [ ] **S01: Test Slice** \`risk:low\` \`depends:[]\`
|
|
313
|
+
> After this: Slice is done.
|
|
314
|
+
`,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
writePlan(
|
|
318
|
+
base,
|
|
319
|
+
"M001",
|
|
320
|
+
"S01",
|
|
321
|
+
`# S01: Test Slice
|
|
322
|
+
|
|
323
|
+
**Goal:** Test summarizing.
|
|
324
|
+
**Demo:** Tests pass.
|
|
325
|
+
|
|
326
|
+
## Tasks
|
|
327
|
+
|
|
328
|
+
- [x] **T01: First Done** \`est:10m\`
|
|
329
|
+
Already completed.
|
|
330
|
+
|
|
331
|
+
- [x] **T02: Second Done** \`est:10m\`
|
|
332
|
+
Also completed.
|
|
333
|
+
`,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const state = await deriveState(base);
|
|
337
|
+
|
|
338
|
+
assertEq(state.phase, "summarizing", "summarizing: phase is summarizing");
|
|
339
|
+
assert(
|
|
340
|
+
state.activeSlice !== null,
|
|
341
|
+
"summarizing: activeSlice is not null",
|
|
342
|
+
);
|
|
343
|
+
assertEq(
|
|
344
|
+
state.activeSlice?.id,
|
|
345
|
+
"S01",
|
|
346
|
+
"summarizing: activeSlice id is S01",
|
|
347
|
+
);
|
|
348
|
+
assertEq(state.activeTask, null, "summarizing: activeTask is null");
|
|
349
|
+
assert(
|
|
350
|
+
state.nextAction.toLowerCase().includes("summary") ||
|
|
351
|
+
state.nextAction.toLowerCase().includes("complete"),
|
|
352
|
+
"summarizing: nextAction mentions summary or complete",
|
|
353
|
+
);
|
|
354
|
+
assertEq(state.progress?.tasks?.done, 2, "summarizing: tasks done = 2");
|
|
355
|
+
assertEq(state.progress?.tasks?.total, 2, "summarizing: tasks total = 2");
|
|
356
|
+
} finally {
|
|
357
|
+
cleanup(base);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ─── Test 7: all milestones complete → complete ────────────────────────
|
|
362
|
+
console.log("\n=== all milestones complete → complete ===");
|
|
363
|
+
{
|
|
364
|
+
const base = createFixtureBase();
|
|
365
|
+
try {
|
|
366
|
+
writeRoadmap(
|
|
367
|
+
base,
|
|
368
|
+
"M001",
|
|
369
|
+
`# M001: Test Milestone
|
|
370
|
+
|
|
371
|
+
**Vision:** Test complete phase.
|
|
372
|
+
|
|
373
|
+
## Slices
|
|
374
|
+
|
|
375
|
+
- [x] **S01: Done Slice** \`risk:low\` \`depends:[]\`
|
|
376
|
+
> After this: Done.
|
|
377
|
+
`,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
writeMilestoneSummary(
|
|
381
|
+
base,
|
|
382
|
+
"M001",
|
|
383
|
+
`# M001 Summary\n\nMilestone complete.`,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const state = await deriveState(base);
|
|
387
|
+
|
|
388
|
+
assertEq(state.phase, "complete", "complete: phase is complete");
|
|
389
|
+
assertEq(state.activeSlice, null, "complete: activeSlice is null");
|
|
390
|
+
assertEq(state.activeTask, null, "complete: activeTask is null");
|
|
391
|
+
assert(
|
|
392
|
+
state.nextAction.toLowerCase().includes("complete"),
|
|
393
|
+
"complete: nextAction mentions complete",
|
|
394
|
+
);
|
|
395
|
+
assertEq(state.registry.length, 1, "complete: registry has 1 entry");
|
|
396
|
+
assertEq(
|
|
397
|
+
state.registry[0]?.status,
|
|
398
|
+
"complete",
|
|
399
|
+
"complete: registry[0] status is complete",
|
|
400
|
+
);
|
|
401
|
+
} finally {
|
|
402
|
+
cleanup(base);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ─── Test 8: blocked dependencies ──────────────────────────────────────
|
|
407
|
+
console.log("\n=== blocked dependencies ===");
|
|
408
|
+
{
|
|
409
|
+
// Case A: S01 active (deps satisfied), S02 blocked on S01
|
|
410
|
+
const base1 = createFixtureBase();
|
|
411
|
+
try {
|
|
412
|
+
writeRoadmap(
|
|
413
|
+
base1,
|
|
414
|
+
"M001",
|
|
415
|
+
`# M001: Test Milestone
|
|
416
|
+
|
|
417
|
+
**Vision:** Test blocked deps.
|
|
418
|
+
|
|
419
|
+
## Slices
|
|
420
|
+
|
|
421
|
+
- [ ] **S01: First** \`risk:low\` \`depends:[]\`
|
|
422
|
+
> After this: S01 done.
|
|
423
|
+
|
|
424
|
+
- [ ] **S02: Second** \`risk:low\` \`depends:[S01]\`
|
|
425
|
+
> After this: S02 done.
|
|
426
|
+
`,
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
// S01 has a plan with incomplete task — it's the active slice
|
|
430
|
+
writePlan(
|
|
431
|
+
base1,
|
|
432
|
+
"M001",
|
|
433
|
+
"S01",
|
|
434
|
+
`# S01: First
|
|
435
|
+
|
|
436
|
+
**Goal:** First slice.
|
|
437
|
+
**Demo:** Tests pass.
|
|
438
|
+
|
|
439
|
+
## Tasks
|
|
440
|
+
|
|
441
|
+
- [ ] **T01: Incomplete** \`est:10m\`
|
|
442
|
+
Still working.
|
|
443
|
+
`,
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const state1 = await deriveState(base1);
|
|
447
|
+
|
|
448
|
+
assertEq(
|
|
449
|
+
state1.phase,
|
|
450
|
+
"executing",
|
|
451
|
+
"blocked-A: phase is executing (S01 active)",
|
|
452
|
+
);
|
|
453
|
+
assertEq(state1.activeSlice?.id, "S01", "blocked-A: activeSlice is S01");
|
|
454
|
+
} finally {
|
|
455
|
+
cleanup(base1);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Case B: S01 depends on nonexistent S99 → truly blocked
|
|
459
|
+
const base2 = createFixtureBase();
|
|
460
|
+
try {
|
|
461
|
+
writeRoadmap(
|
|
462
|
+
base2,
|
|
463
|
+
"M001",
|
|
464
|
+
`# M001: Test Milestone
|
|
465
|
+
|
|
466
|
+
**Vision:** Test truly blocked.
|
|
467
|
+
|
|
468
|
+
## Slices
|
|
469
|
+
|
|
470
|
+
- [ ] **S01: Blocked** \`risk:low\` \`depends:[S99]\`
|
|
471
|
+
> After this: Done.
|
|
472
|
+
`,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const state2 = await deriveState(base2);
|
|
476
|
+
|
|
477
|
+
assertEq(state2.phase, "blocked", "blocked-B: phase is blocked");
|
|
478
|
+
assertEq(state2.activeSlice, null, "blocked-B: activeSlice is null");
|
|
479
|
+
assert(
|
|
480
|
+
state2.blockers.length > 0,
|
|
481
|
+
"blocked-B: blockers array is non-empty",
|
|
482
|
+
);
|
|
483
|
+
} finally {
|
|
484
|
+
cleanup(base2);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ─── Test 9: multi-milestone registry ──────────────────────────────────
|
|
489
|
+
console.log("\n=== multi-milestone registry ===");
|
|
490
|
+
{
|
|
491
|
+
const base = createFixtureBase();
|
|
492
|
+
try {
|
|
493
|
+
// M001: complete (all slices done)
|
|
494
|
+
writeRoadmap(
|
|
495
|
+
base,
|
|
496
|
+
"M001",
|
|
497
|
+
`# M001: First Milestone
|
|
498
|
+
|
|
499
|
+
**Vision:** Already done.
|
|
500
|
+
|
|
501
|
+
## Slices
|
|
502
|
+
|
|
503
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
504
|
+
> After this: Done.
|
|
505
|
+
`,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
writeMilestoneSummary(
|
|
509
|
+
base,
|
|
510
|
+
"M001",
|
|
511
|
+
`# M001 Summary\n\nFirst milestone complete.`,
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// M002: active (has incomplete slices)
|
|
515
|
+
writeRoadmap(
|
|
516
|
+
base,
|
|
517
|
+
"M002",
|
|
518
|
+
`# M002: Second Milestone
|
|
519
|
+
|
|
520
|
+
**Vision:** Currently active.
|
|
521
|
+
|
|
522
|
+
## Slices
|
|
523
|
+
|
|
524
|
+
- [ ] **S01: In Progress** \`risk:low\` \`depends:[]\`
|
|
525
|
+
> After this: Done.
|
|
526
|
+
`,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// M003: just a dir (no roadmap → pending since M002 is already active)
|
|
530
|
+
mkdirSync(join(base, ".kata", "milestones", "M003"), { recursive: true });
|
|
531
|
+
|
|
532
|
+
const state = await deriveState(base);
|
|
533
|
+
|
|
534
|
+
assertEq(state.registry.length, 3, "multi-ms: registry has 3 entries");
|
|
535
|
+
assertEq(state.registry[0]?.id, "M001", "multi-ms: registry[0] is M001");
|
|
536
|
+
assertEq(
|
|
537
|
+
state.registry[0]?.status,
|
|
538
|
+
"complete",
|
|
539
|
+
"multi-ms: M001 is complete",
|
|
540
|
+
);
|
|
541
|
+
assertEq(state.registry[1]?.id, "M002", "multi-ms: registry[1] is M002");
|
|
542
|
+
assertEq(state.registry[1]?.status, "active", "multi-ms: M002 is active");
|
|
543
|
+
assertEq(state.registry[2]?.id, "M003", "multi-ms: registry[2] is M003");
|
|
544
|
+
assertEq(
|
|
545
|
+
state.registry[2]?.status,
|
|
546
|
+
"pending",
|
|
547
|
+
"multi-ms: M003 is pending",
|
|
548
|
+
);
|
|
549
|
+
assertEq(
|
|
550
|
+
state.activeMilestone?.id,
|
|
551
|
+
"M002",
|
|
552
|
+
"multi-ms: activeMilestone is M002",
|
|
553
|
+
);
|
|
554
|
+
assertEq(
|
|
555
|
+
state.progress?.milestones?.done,
|
|
556
|
+
1,
|
|
557
|
+
"multi-ms: milestones done = 1",
|
|
558
|
+
);
|
|
559
|
+
assertEq(
|
|
560
|
+
state.progress?.milestones?.total,
|
|
561
|
+
3,
|
|
562
|
+
"multi-ms: milestones total = 3",
|
|
563
|
+
);
|
|
564
|
+
} finally {
|
|
565
|
+
cleanup(base);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ─── Test 10: requirements integration ─────────────────────────────────
|
|
570
|
+
console.log("\n=== requirements integration ===");
|
|
571
|
+
{
|
|
572
|
+
const base = createFixtureBase();
|
|
573
|
+
try {
|
|
574
|
+
writeRequirements(
|
|
575
|
+
base,
|
|
576
|
+
`# Requirements
|
|
577
|
+
|
|
578
|
+
## Active
|
|
579
|
+
|
|
580
|
+
### R001 — First Active Requirement
|
|
581
|
+
- Status: active
|
|
582
|
+
- Description: Something active.
|
|
583
|
+
|
|
584
|
+
### R002 — Second Active Requirement
|
|
585
|
+
- Status: active
|
|
586
|
+
- Description: Another active one.
|
|
587
|
+
|
|
588
|
+
## Validated
|
|
589
|
+
|
|
590
|
+
### R003 — Validated Requirement
|
|
591
|
+
- Status: validated
|
|
592
|
+
- Description: Already validated.
|
|
593
|
+
|
|
594
|
+
## Deferred
|
|
595
|
+
|
|
596
|
+
### R004 — Deferred Requirement
|
|
597
|
+
- Status: deferred
|
|
598
|
+
- Description: Pushed back.
|
|
599
|
+
|
|
600
|
+
### R005 — Another Deferred
|
|
601
|
+
- Status: deferred
|
|
602
|
+
- Description: Also deferred.
|
|
603
|
+
|
|
604
|
+
## Out of Scope
|
|
605
|
+
|
|
606
|
+
### R006 — Out of Scope Requirement
|
|
607
|
+
- Status: out-of-scope
|
|
608
|
+
- Description: Not doing this.
|
|
609
|
+
`,
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Need at least an empty milestones dir for deriveState
|
|
613
|
+
const state = await deriveState(base);
|
|
614
|
+
|
|
615
|
+
assert(
|
|
616
|
+
state.requirements !== undefined,
|
|
617
|
+
"requirements: requirements object exists",
|
|
618
|
+
);
|
|
619
|
+
assertEq(state.requirements?.active, 2, "requirements: active = 2");
|
|
620
|
+
assertEq(state.requirements?.validated, 1, "requirements: validated = 1");
|
|
621
|
+
assertEq(state.requirements?.deferred, 2, "requirements: deferred = 2");
|
|
622
|
+
assertEq(
|
|
623
|
+
state.requirements?.outOfScope,
|
|
624
|
+
1,
|
|
625
|
+
"requirements: outOfScope = 1",
|
|
626
|
+
);
|
|
627
|
+
assertEq(
|
|
628
|
+
state.requirements?.total,
|
|
629
|
+
6,
|
|
630
|
+
"requirements: total = 6 (sum of all)",
|
|
631
|
+
);
|
|
632
|
+
} finally {
|
|
633
|
+
cleanup(base);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ─── Test 11: all slices [x], no summary → completing-milestone ────────
|
|
638
|
+
console.log("\n=== all slices [x], no summary → completing-milestone ===");
|
|
639
|
+
{
|
|
640
|
+
const base = createFixtureBase();
|
|
641
|
+
try {
|
|
642
|
+
writeRoadmap(
|
|
643
|
+
base,
|
|
644
|
+
"M001",
|
|
645
|
+
`# M001: Test Milestone
|
|
646
|
+
|
|
647
|
+
**Vision:** Test completing-milestone phase.
|
|
648
|
+
|
|
649
|
+
## Slices
|
|
650
|
+
|
|
651
|
+
- [x] **S01: First Done** \`risk:low\` \`depends:[]\`
|
|
652
|
+
> After this: S01 complete.
|
|
653
|
+
|
|
654
|
+
- [x] **S02: Second Done** \`risk:low\` \`depends:[S01]\`
|
|
655
|
+
> After this: S02 complete.
|
|
656
|
+
`,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
const state = await deriveState(base);
|
|
660
|
+
|
|
661
|
+
assertEq(
|
|
662
|
+
state.phase,
|
|
663
|
+
"completing-milestone",
|
|
664
|
+
"completing-ms: phase is completing-milestone",
|
|
665
|
+
);
|
|
666
|
+
assert(
|
|
667
|
+
state.activeMilestone !== null,
|
|
668
|
+
"completing-ms: activeMilestone is not null",
|
|
669
|
+
);
|
|
670
|
+
assertEq(
|
|
671
|
+
state.activeMilestone?.id,
|
|
672
|
+
"M001",
|
|
673
|
+
"completing-ms: activeMilestone id is M001",
|
|
674
|
+
);
|
|
675
|
+
assertEq(state.activeSlice, null, "completing-ms: activeSlice is null");
|
|
676
|
+
assertEq(state.activeTask, null, "completing-ms: activeTask is null");
|
|
677
|
+
assertEq(state.registry.length, 1, "completing-ms: registry has 1 entry");
|
|
678
|
+
assertEq(
|
|
679
|
+
state.registry[0]?.status,
|
|
680
|
+
"active",
|
|
681
|
+
"completing-ms: registry[0] status is active (not complete)",
|
|
682
|
+
);
|
|
683
|
+
assertEq(
|
|
684
|
+
state.progress?.slices?.done,
|
|
685
|
+
2,
|
|
686
|
+
"completing-ms: slices done = 2",
|
|
687
|
+
);
|
|
688
|
+
assertEq(
|
|
689
|
+
state.progress?.slices?.total,
|
|
690
|
+
2,
|
|
691
|
+
"completing-ms: slices total = 2",
|
|
692
|
+
);
|
|
693
|
+
assert(
|
|
694
|
+
state.nextAction.toLowerCase().includes("summary") ||
|
|
695
|
+
state.nextAction.toLowerCase().includes("complete"),
|
|
696
|
+
"completing-ms: nextAction mentions summary or complete",
|
|
697
|
+
);
|
|
698
|
+
} finally {
|
|
699
|
+
cleanup(base);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// ─── Test 12: all slices [x], summary exists → complete ───────────────
|
|
704
|
+
console.log("\n=== all slices [x], summary exists → complete ===");
|
|
705
|
+
{
|
|
706
|
+
const base = createFixtureBase();
|
|
707
|
+
try {
|
|
708
|
+
writeRoadmap(
|
|
709
|
+
base,
|
|
710
|
+
"M001",
|
|
711
|
+
`# M001: Test Milestone
|
|
712
|
+
|
|
713
|
+
**Vision:** Test that summary presence means complete.
|
|
714
|
+
|
|
715
|
+
## Slices
|
|
716
|
+
|
|
717
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
718
|
+
> After this: Done.
|
|
719
|
+
`,
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
writeMilestoneSummary(
|
|
723
|
+
base,
|
|
724
|
+
"M001",
|
|
725
|
+
`# M001 Summary\n\nMilestone is complete.`,
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
const state = await deriveState(base);
|
|
729
|
+
|
|
730
|
+
assertEq(state.phase, "complete", "summary-exists: phase is complete");
|
|
731
|
+
assertEq(
|
|
732
|
+
state.registry.length,
|
|
733
|
+
1,
|
|
734
|
+
"summary-exists: registry has 1 entry",
|
|
735
|
+
);
|
|
736
|
+
assertEq(
|
|
737
|
+
state.registry[0]?.status,
|
|
738
|
+
"complete",
|
|
739
|
+
"summary-exists: registry[0] status is complete",
|
|
740
|
+
);
|
|
741
|
+
assertEq(state.activeSlice, null, "summary-exists: activeSlice is null");
|
|
742
|
+
assertEq(state.activeTask, null, "summary-exists: activeTask is null");
|
|
743
|
+
} finally {
|
|
744
|
+
cleanup(base);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ─── Test 13: multi-milestone completing-milestone ─────────────────────
|
|
749
|
+
console.log("\n=== multi-milestone completing-milestone ===");
|
|
750
|
+
{
|
|
751
|
+
const base = createFixtureBase();
|
|
752
|
+
try {
|
|
753
|
+
// M001: all slices done + summary exists → complete
|
|
754
|
+
writeRoadmap(
|
|
755
|
+
base,
|
|
756
|
+
"M001",
|
|
757
|
+
`# M001: First Milestone
|
|
758
|
+
|
|
759
|
+
**Vision:** Already complete with summary.
|
|
760
|
+
|
|
761
|
+
## Slices
|
|
762
|
+
|
|
763
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
764
|
+
> After this: Done.
|
|
765
|
+
`,
|
|
766
|
+
);
|
|
767
|
+
writeMilestoneSummary(
|
|
768
|
+
base,
|
|
769
|
+
"M001",
|
|
770
|
+
`# M001 Summary\n\nFirst milestone complete.`,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
// M002: all slices done, no summary → completing-milestone
|
|
774
|
+
writeRoadmap(
|
|
775
|
+
base,
|
|
776
|
+
"M002",
|
|
777
|
+
`# M002: Second Milestone
|
|
778
|
+
|
|
779
|
+
**Vision:** All slices done but no summary.
|
|
780
|
+
|
|
781
|
+
## Slices
|
|
782
|
+
|
|
783
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
784
|
+
> After this: Done.
|
|
785
|
+
|
|
786
|
+
- [x] **S02: Also Done** \`risk:low\` \`depends:[S01]\`
|
|
787
|
+
> After this: Done.
|
|
788
|
+
`,
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
// M003: has incomplete slices → pending (M002 is active)
|
|
792
|
+
writeRoadmap(
|
|
793
|
+
base,
|
|
794
|
+
"M003",
|
|
795
|
+
`# M003: Third Milestone
|
|
796
|
+
|
|
797
|
+
**Vision:** Not yet started.
|
|
798
|
+
|
|
799
|
+
## Slices
|
|
800
|
+
|
|
801
|
+
- [ ] **S01: Not Started** \`risk:low\` \`depends:[]\`
|
|
802
|
+
> After this: Done.
|
|
803
|
+
`,
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
const state = await deriveState(base);
|
|
807
|
+
|
|
808
|
+
assertEq(
|
|
809
|
+
state.phase,
|
|
810
|
+
"completing-milestone",
|
|
811
|
+
"multi-completing: phase is completing-milestone",
|
|
812
|
+
);
|
|
813
|
+
assertEq(
|
|
814
|
+
state.activeMilestone?.id,
|
|
815
|
+
"M002",
|
|
816
|
+
"multi-completing: activeMilestone is M002",
|
|
817
|
+
);
|
|
818
|
+
assertEq(
|
|
819
|
+
state.activeSlice,
|
|
820
|
+
null,
|
|
821
|
+
"multi-completing: activeSlice is null",
|
|
822
|
+
);
|
|
823
|
+
assertEq(state.activeTask, null, "multi-completing: activeTask is null");
|
|
824
|
+
assertEq(
|
|
825
|
+
state.registry.length,
|
|
826
|
+
3,
|
|
827
|
+
"multi-completing: registry has 3 entries",
|
|
828
|
+
);
|
|
829
|
+
assertEq(
|
|
830
|
+
state.registry[0]?.id,
|
|
831
|
+
"M001",
|
|
832
|
+
"multi-completing: registry[0] is M001",
|
|
833
|
+
);
|
|
834
|
+
assertEq(
|
|
835
|
+
state.registry[0]?.status,
|
|
836
|
+
"complete",
|
|
837
|
+
"multi-completing: M001 is complete",
|
|
838
|
+
);
|
|
839
|
+
assertEq(
|
|
840
|
+
state.registry[1]?.id,
|
|
841
|
+
"M002",
|
|
842
|
+
"multi-completing: registry[1] is M002",
|
|
843
|
+
);
|
|
844
|
+
assertEq(
|
|
845
|
+
state.registry[1]?.status,
|
|
846
|
+
"active",
|
|
847
|
+
"multi-completing: M002 is active (completing-milestone)",
|
|
848
|
+
);
|
|
849
|
+
assertEq(
|
|
850
|
+
state.registry[2]?.id,
|
|
851
|
+
"M003",
|
|
852
|
+
"multi-completing: registry[2] is M003",
|
|
853
|
+
);
|
|
854
|
+
assertEq(
|
|
855
|
+
state.registry[2]?.status,
|
|
856
|
+
"pending",
|
|
857
|
+
"multi-completing: M003 is pending",
|
|
858
|
+
);
|
|
859
|
+
assertEq(
|
|
860
|
+
state.progress?.milestones?.done,
|
|
861
|
+
1,
|
|
862
|
+
"multi-completing: milestones done = 1",
|
|
863
|
+
);
|
|
864
|
+
assertEq(
|
|
865
|
+
state.progress?.milestones?.total,
|
|
866
|
+
3,
|
|
867
|
+
"multi-completing: milestones total = 3",
|
|
868
|
+
);
|
|
869
|
+
assertEq(
|
|
870
|
+
state.progress?.slices?.done,
|
|
871
|
+
2,
|
|
872
|
+
"multi-completing: slices done = 2",
|
|
873
|
+
);
|
|
874
|
+
assertEq(
|
|
875
|
+
state.progress?.slices?.total,
|
|
876
|
+
2,
|
|
877
|
+
"multi-completing: slices total = 2",
|
|
878
|
+
);
|
|
879
|
+
} finally {
|
|
880
|
+
cleanup(base);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ═══ Milestone with summary but no roadmap → complete ═══════════════════
|
|
885
|
+
{
|
|
886
|
+
console.log("\n=== milestone with summary and no roadmap → complete ===");
|
|
887
|
+
const base = createFixtureBase();
|
|
888
|
+
try {
|
|
889
|
+
// M001, M002: completed milestones with summaries but no roadmaps
|
|
890
|
+
const m1dir = join(base, ".kata", "milestones", "M001");
|
|
891
|
+
mkdirSync(m1dir, { recursive: true });
|
|
892
|
+
writeFileSync(
|
|
893
|
+
join(m1dir, "M001-SUMMARY.md"),
|
|
894
|
+
"---\nid: M001\n---\n# Bootstrap\nDone.",
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
const m2dir = join(base, ".kata", "milestones", "M002");
|
|
898
|
+
mkdirSync(m2dir, { recursive: true });
|
|
899
|
+
writeFileSync(
|
|
900
|
+
join(m2dir, "M002-SUMMARY.md"),
|
|
901
|
+
"---\nid: M002\n---\n# Core Features\nDone.",
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
// M003: active milestone with a roadmap
|
|
905
|
+
writeRoadmap(
|
|
906
|
+
base,
|
|
907
|
+
"M003",
|
|
908
|
+
"# M003: Polish\n## Slices\n- [ ] **S01: Cleanup**",
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
const state = await deriveState(base);
|
|
912
|
+
|
|
913
|
+
assertEq(
|
|
914
|
+
state.phase,
|
|
915
|
+
"planning",
|
|
916
|
+
"summary-no-roadmap: phase is planning (active is M003)",
|
|
917
|
+
);
|
|
918
|
+
assertEq(
|
|
919
|
+
state.activeMilestone?.id,
|
|
920
|
+
"M003",
|
|
921
|
+
"summary-no-roadmap: active milestone is M003",
|
|
922
|
+
);
|
|
923
|
+
assertEq(
|
|
924
|
+
state.activeMilestone?.title,
|
|
925
|
+
"Polish",
|
|
926
|
+
"summary-no-roadmap: active title is Polish",
|
|
927
|
+
);
|
|
928
|
+
assertEq(
|
|
929
|
+
state.registry.length,
|
|
930
|
+
3,
|
|
931
|
+
"summary-no-roadmap: registry has 3 entries",
|
|
932
|
+
);
|
|
933
|
+
assertEq(
|
|
934
|
+
state.registry[0]?.status,
|
|
935
|
+
"complete",
|
|
936
|
+
"summary-no-roadmap: M001 is complete",
|
|
937
|
+
);
|
|
938
|
+
assertEq(
|
|
939
|
+
state.registry[0]?.title,
|
|
940
|
+
"Bootstrap",
|
|
941
|
+
"summary-no-roadmap: M001 title from summary",
|
|
942
|
+
);
|
|
943
|
+
assertEq(
|
|
944
|
+
state.registry[1]?.status,
|
|
945
|
+
"complete",
|
|
946
|
+
"summary-no-roadmap: M002 is complete",
|
|
947
|
+
);
|
|
948
|
+
assertEq(
|
|
949
|
+
state.registry[1]?.title,
|
|
950
|
+
"Core Features",
|
|
951
|
+
"summary-no-roadmap: M002 title from summary",
|
|
952
|
+
);
|
|
953
|
+
assertEq(
|
|
954
|
+
state.registry[2]?.status,
|
|
955
|
+
"active",
|
|
956
|
+
"summary-no-roadmap: M003 is active",
|
|
957
|
+
);
|
|
958
|
+
assertEq(
|
|
959
|
+
state.progress?.milestones?.done,
|
|
960
|
+
2,
|
|
961
|
+
"summary-no-roadmap: milestones done = 2",
|
|
962
|
+
);
|
|
963
|
+
assertEq(
|
|
964
|
+
state.progress?.milestones?.total,
|
|
965
|
+
3,
|
|
966
|
+
"summary-no-roadmap: milestones total = 3",
|
|
967
|
+
);
|
|
968
|
+
} finally {
|
|
969
|
+
cleanup(base);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// ═══ All milestones have summary but no roadmap → complete ═════════════
|
|
974
|
+
{
|
|
975
|
+
console.log("\n=== all milestones summary-only → complete ===");
|
|
976
|
+
const base = createFixtureBase();
|
|
977
|
+
try {
|
|
978
|
+
const m1dir = join(base, ".kata", "milestones", "M001");
|
|
979
|
+
mkdirSync(m1dir, { recursive: true });
|
|
980
|
+
writeFileSync(
|
|
981
|
+
join(m1dir, "M001-SUMMARY.md"),
|
|
982
|
+
"---\ntitle: Done\n---\nAll done.",
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
const state = await deriveState(base);
|
|
986
|
+
assertEq(state.phase, "complete", "all-summary-only: phase is complete");
|
|
987
|
+
assertEq(
|
|
988
|
+
state.registry[0]?.status,
|
|
989
|
+
"complete",
|
|
990
|
+
"all-summary-only: M001 is complete",
|
|
991
|
+
);
|
|
992
|
+
} finally {
|
|
993
|
+
cleanup(base);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
998
|
+
// Results
|
|
999
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
1000
|
+
|
|
1001
|
+
console.log(`\n${"=".repeat(40)}`);
|
|
1002
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
1003
|
+
if (failed > 0) {
|
|
1004
|
+
process.exit(1);
|
|
1005
|
+
} else {
|
|
1006
|
+
console.log("All tests passed ✓");
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
main().catch((error) => {
|
|
1011
|
+
console.error(error);
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
});
|