@kata-sh/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,477 @@
|
|
|
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 } 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-deps-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 writeMilestoneSummary(
|
|
45
|
+
base: string,
|
|
46
|
+
mid: string,
|
|
47
|
+
content: string,
|
|
48
|
+
): void {
|
|
49
|
+
const dir = join(base, ".kata", "milestones", mid);
|
|
50
|
+
mkdirSync(dir, { recursive: true });
|
|
51
|
+
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates M00x-CONTEXT.md with a valid YAML frontmatter block.
|
|
56
|
+
* frontmatter is the raw YAML lines between the --- delimiters.
|
|
57
|
+
*/
|
|
58
|
+
function writeContext(base: string, mid: string, frontmatter: string): void {
|
|
59
|
+
const dir = join(base, ".kata", "milestones", mid);
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
writeFileSync(join(dir, `${mid}-CONTEXT.md`), `---\n${frontmatter}\n---\n`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeSlicePlan(
|
|
65
|
+
base: string,
|
|
66
|
+
mid: string,
|
|
67
|
+
sid: string,
|
|
68
|
+
content: string,
|
|
69
|
+
): void {
|
|
70
|
+
const dir = join(base, ".kata", "milestones", mid, "slices", sid);
|
|
71
|
+
mkdirSync(join(dir, "tasks"), { recursive: true });
|
|
72
|
+
writeFileSync(join(dir, `${sid}-PLAN.md`), content);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function cleanup(base: string): void {
|
|
76
|
+
rmSync(base, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
|
+
// Test Groups
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
82
|
+
|
|
83
|
+
async function main(): Promise<void> {
|
|
84
|
+
// ─── Test Group 1: blocked-deps ────────────────────────────────────────
|
|
85
|
+
// M001 is incomplete (no SUMMARY), M002 depends_on M001 → M002 is pending
|
|
86
|
+
console.log("\n=== blocked-deps ===");
|
|
87
|
+
{
|
|
88
|
+
const base = createFixtureBase();
|
|
89
|
+
try {
|
|
90
|
+
// M001: incomplete (one slice, no SUMMARY)
|
|
91
|
+
writeRoadmap(
|
|
92
|
+
base,
|
|
93
|
+
"M001",
|
|
94
|
+
`# M001: First Milestone
|
|
95
|
+
|
|
96
|
+
**Vision:** First milestone still in progress.
|
|
97
|
+
|
|
98
|
+
## Slices
|
|
99
|
+
|
|
100
|
+
- [ ] **S01: Incomplete Slice** \`risk:low\` \`depends:[]\`
|
|
101
|
+
> After this: Done.
|
|
102
|
+
`,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// M001: add a slice plan with an active task so phase is 'executing'
|
|
106
|
+
writeSlicePlan(
|
|
107
|
+
base,
|
|
108
|
+
"M001",
|
|
109
|
+
"S01",
|
|
110
|
+
`# S01: Incomplete Slice
|
|
111
|
+
|
|
112
|
+
**Goal:** Verify dep-blocked milestone behavior.
|
|
113
|
+
**Demo:** Tests pass.
|
|
114
|
+
|
|
115
|
+
## Tasks
|
|
116
|
+
|
|
117
|
+
- [ ] **T01: Do work** \`est:15m\`
|
|
118
|
+
First task still in progress.
|
|
119
|
+
`,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// M002: depends on M001, also incomplete
|
|
123
|
+
writeRoadmap(
|
|
124
|
+
base,
|
|
125
|
+
"M002",
|
|
126
|
+
`# M002: Second Milestone
|
|
127
|
+
|
|
128
|
+
**Vision:** Second milestone blocked by M001.
|
|
129
|
+
|
|
130
|
+
## Slices
|
|
131
|
+
|
|
132
|
+
- [ ] **S01: Blocked Slice** \`risk:low\` \`depends:[]\`
|
|
133
|
+
> After this: Done.
|
|
134
|
+
`,
|
|
135
|
+
);
|
|
136
|
+
writeContext(base, "M002", "depends_on: [M001]");
|
|
137
|
+
|
|
138
|
+
const state = await deriveState(base);
|
|
139
|
+
|
|
140
|
+
assertEq(
|
|
141
|
+
state.registry[0]?.status,
|
|
142
|
+
"active",
|
|
143
|
+
"blocked-deps: M001 is active",
|
|
144
|
+
);
|
|
145
|
+
assertEq(
|
|
146
|
+
state.registry[1]?.status,
|
|
147
|
+
"pending",
|
|
148
|
+
"blocked-deps: M002 is pending (dep-blocked)",
|
|
149
|
+
);
|
|
150
|
+
assertEq(
|
|
151
|
+
state.phase,
|
|
152
|
+
"executing",
|
|
153
|
+
"blocked-deps: phase is executing (M001 is active)",
|
|
154
|
+
);
|
|
155
|
+
assertEq(
|
|
156
|
+
state.activeMilestone?.id,
|
|
157
|
+
"M001",
|
|
158
|
+
"blocked-deps: activeMilestone is M001",
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
cleanup(base);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Test Group 2: unblocked-deps ──────────────────────────────────────
|
|
166
|
+
// M001 is complete (all slices [x] + SUMMARY), M002 depends_on M001 → M002 becomes active
|
|
167
|
+
console.log("\n=== unblocked-deps ===");
|
|
168
|
+
{
|
|
169
|
+
const base = createFixtureBase();
|
|
170
|
+
try {
|
|
171
|
+
// M001: complete (all slices done + SUMMARY present)
|
|
172
|
+
writeRoadmap(
|
|
173
|
+
base,
|
|
174
|
+
"M001",
|
|
175
|
+
`# M001: First Milestone
|
|
176
|
+
|
|
177
|
+
**Vision:** First milestone complete.
|
|
178
|
+
|
|
179
|
+
## Slices
|
|
180
|
+
|
|
181
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
182
|
+
> After this: Done.
|
|
183
|
+
`,
|
|
184
|
+
);
|
|
185
|
+
writeMilestoneSummary(
|
|
186
|
+
base,
|
|
187
|
+
"M001",
|
|
188
|
+
"# M001 Summary\n\nFirst milestone is complete.",
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// M002: depends on M001, now unblocked
|
|
192
|
+
writeRoadmap(
|
|
193
|
+
base,
|
|
194
|
+
"M002",
|
|
195
|
+
`# M002: Second Milestone
|
|
196
|
+
|
|
197
|
+
**Vision:** Second milestone now active.
|
|
198
|
+
|
|
199
|
+
## Slices
|
|
200
|
+
|
|
201
|
+
- [ ] **S01: Active Slice** \`risk:low\` \`depends:[]\`
|
|
202
|
+
> After this: Done.
|
|
203
|
+
`,
|
|
204
|
+
);
|
|
205
|
+
writeContext(base, "M002", "depends_on: [M001]");
|
|
206
|
+
|
|
207
|
+
const state = await deriveState(base);
|
|
208
|
+
|
|
209
|
+
assertEq(
|
|
210
|
+
state.registry[0]?.status,
|
|
211
|
+
"complete",
|
|
212
|
+
"unblocked-deps: M001 is complete",
|
|
213
|
+
);
|
|
214
|
+
assertEq(
|
|
215
|
+
state.registry[1]?.status,
|
|
216
|
+
"active",
|
|
217
|
+
"unblocked-deps: M002 is active",
|
|
218
|
+
);
|
|
219
|
+
assertEq(
|
|
220
|
+
state.activeMilestone?.id,
|
|
221
|
+
"M002",
|
|
222
|
+
"unblocked-deps: activeMilestone is M002",
|
|
223
|
+
);
|
|
224
|
+
assert(state.phase !== "blocked", "unblocked-deps: phase is not blocked");
|
|
225
|
+
} finally {
|
|
226
|
+
cleanup(base);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Test Group 3: all-blocked ─────────────────────────────────────────
|
|
231
|
+
// M001 depends_on M002, M002 depends_on M001 — circular dep, neither can activate
|
|
232
|
+
console.log("\n=== all-blocked ===");
|
|
233
|
+
{
|
|
234
|
+
const base = createFixtureBase();
|
|
235
|
+
try {
|
|
236
|
+
// M001: depends on M002
|
|
237
|
+
writeRoadmap(
|
|
238
|
+
base,
|
|
239
|
+
"M001",
|
|
240
|
+
`# M001: First Milestone
|
|
241
|
+
|
|
242
|
+
**Vision:** Circular dependency.
|
|
243
|
+
|
|
244
|
+
## Slices
|
|
245
|
+
|
|
246
|
+
- [ ] **S01: Waiting** \`risk:low\` \`depends:[]\`
|
|
247
|
+
> After this: Done.
|
|
248
|
+
`,
|
|
249
|
+
);
|
|
250
|
+
writeContext(base, "M001", "depends_on: [M002]");
|
|
251
|
+
|
|
252
|
+
// M002: depends on M001
|
|
253
|
+
writeRoadmap(
|
|
254
|
+
base,
|
|
255
|
+
"M002",
|
|
256
|
+
`# M002: Second Milestone
|
|
257
|
+
|
|
258
|
+
**Vision:** Also in circular dependency.
|
|
259
|
+
|
|
260
|
+
## Slices
|
|
261
|
+
|
|
262
|
+
- [ ] **S01: Also Waiting** \`risk:low\` \`depends:[]\`
|
|
263
|
+
> After this: Done.
|
|
264
|
+
`,
|
|
265
|
+
);
|
|
266
|
+
writeContext(base, "M002", "depends_on: [M001]");
|
|
267
|
+
|
|
268
|
+
const state = await deriveState(base);
|
|
269
|
+
|
|
270
|
+
assertEq(state.phase, "blocked", "all-blocked: phase is blocked");
|
|
271
|
+
assert(
|
|
272
|
+
state.activeMilestone === null || state.activeMilestone !== null,
|
|
273
|
+
"all-blocked: state is consistent",
|
|
274
|
+
);
|
|
275
|
+
assert(
|
|
276
|
+
state.blockers.length > 0,
|
|
277
|
+
"all-blocked: blockers array is non-empty",
|
|
278
|
+
);
|
|
279
|
+
} finally {
|
|
280
|
+
cleanup(base);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ─── Test Group 4: absent-context ──────────────────────────────────────
|
|
285
|
+
// Neither M001 nor M002 has a CONTEXT.md → no dep constraints, normal sequential behavior
|
|
286
|
+
console.log("\n=== absent-context ===");
|
|
287
|
+
{
|
|
288
|
+
const base = createFixtureBase();
|
|
289
|
+
try {
|
|
290
|
+
// M001: incomplete, no CONTEXT.md
|
|
291
|
+
writeRoadmap(
|
|
292
|
+
base,
|
|
293
|
+
"M001",
|
|
294
|
+
`# M001: First Milestone
|
|
295
|
+
|
|
296
|
+
**Vision:** No context file, no deps.
|
|
297
|
+
|
|
298
|
+
## Slices
|
|
299
|
+
|
|
300
|
+
- [ ] **S01: Incomplete** \`risk:low\` \`depends:[]\`
|
|
301
|
+
> After this: Done.
|
|
302
|
+
`,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// M002: incomplete, no CONTEXT.md
|
|
306
|
+
writeRoadmap(
|
|
307
|
+
base,
|
|
308
|
+
"M002",
|
|
309
|
+
`# M002: Second Milestone
|
|
310
|
+
|
|
311
|
+
**Vision:** Also no context file.
|
|
312
|
+
|
|
313
|
+
## Slices
|
|
314
|
+
|
|
315
|
+
- [ ] **S01: Pending** \`risk:low\` \`depends:[]\`
|
|
316
|
+
> After this: Done.
|
|
317
|
+
`,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const state = await deriveState(base);
|
|
321
|
+
|
|
322
|
+
assertEq(
|
|
323
|
+
state.registry[0]?.status,
|
|
324
|
+
"active",
|
|
325
|
+
"absent-context: M001 is active",
|
|
326
|
+
);
|
|
327
|
+
assertEq(
|
|
328
|
+
state.registry[1]?.status,
|
|
329
|
+
"pending",
|
|
330
|
+
"absent-context: M002 is pending",
|
|
331
|
+
);
|
|
332
|
+
assertEq(
|
|
333
|
+
state.activeMilestone?.id,
|
|
334
|
+
"M001",
|
|
335
|
+
"absent-context: activeMilestone is M001",
|
|
336
|
+
);
|
|
337
|
+
assert(state.phase !== "blocked", "absent-context: phase is not blocked");
|
|
338
|
+
} finally {
|
|
339
|
+
cleanup(base);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ─── Test Group 5: forward-dep ─────────────────────────────────────────
|
|
344
|
+
// M001 depends_on M002, but M002 is already complete → M001 can activate
|
|
345
|
+
console.log("\n=== forward-dep ===");
|
|
346
|
+
{
|
|
347
|
+
const base = createFixtureBase();
|
|
348
|
+
try {
|
|
349
|
+
// M001: depends on M002, but M002 is complete so M001 is unblocked
|
|
350
|
+
writeRoadmap(
|
|
351
|
+
base,
|
|
352
|
+
"M001",
|
|
353
|
+
`# M001: First Milestone
|
|
354
|
+
|
|
355
|
+
**Vision:** Depends on M002 which is already complete.
|
|
356
|
+
|
|
357
|
+
## Slices
|
|
358
|
+
|
|
359
|
+
- [ ] **S01: Ready** \`risk:low\` \`depends:[]\`
|
|
360
|
+
> After this: Done.
|
|
361
|
+
`,
|
|
362
|
+
);
|
|
363
|
+
writeContext(base, "M001", "depends_on: [M002]");
|
|
364
|
+
|
|
365
|
+
// M002: complete (all slices [x] + SUMMARY)
|
|
366
|
+
writeRoadmap(
|
|
367
|
+
base,
|
|
368
|
+
"M002",
|
|
369
|
+
`# M002: Second Milestone
|
|
370
|
+
|
|
371
|
+
**Vision:** Already complete.
|
|
372
|
+
|
|
373
|
+
## Slices
|
|
374
|
+
|
|
375
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
376
|
+
> After this: Done.
|
|
377
|
+
`,
|
|
378
|
+
);
|
|
379
|
+
writeMilestoneSummary(
|
|
380
|
+
base,
|
|
381
|
+
"M002",
|
|
382
|
+
"# M002 Summary\n\nSecond milestone is complete.",
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const state = await deriveState(base);
|
|
386
|
+
|
|
387
|
+
assertEq(
|
|
388
|
+
state.activeMilestone?.id,
|
|
389
|
+
"M001",
|
|
390
|
+
"forward-dep: activeMilestone is M001",
|
|
391
|
+
);
|
|
392
|
+
assertEq(
|
|
393
|
+
state.registry[1]?.status,
|
|
394
|
+
"complete",
|
|
395
|
+
"forward-dep: M002 is complete",
|
|
396
|
+
);
|
|
397
|
+
assert(state.phase !== "blocked", "forward-dep: phase is not blocked");
|
|
398
|
+
} finally {
|
|
399
|
+
cleanup(base);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ─── Test Group 6: empty-deps-list ─────────────────────────────────────
|
|
404
|
+
// M002 has `depends_on: []` — empty list means no constraint, normal sequential behavior
|
|
405
|
+
console.log("\n=== empty-deps-list ===");
|
|
406
|
+
{
|
|
407
|
+
const base = createFixtureBase();
|
|
408
|
+
try {
|
|
409
|
+
// M001: incomplete, no context
|
|
410
|
+
writeRoadmap(
|
|
411
|
+
base,
|
|
412
|
+
"M001",
|
|
413
|
+
`# M001: First Milestone
|
|
414
|
+
|
|
415
|
+
**Vision:** First milestone still in progress.
|
|
416
|
+
|
|
417
|
+
## Slices
|
|
418
|
+
|
|
419
|
+
- [ ] **S01: Incomplete** \`risk:low\` \`depends:[]\`
|
|
420
|
+
> After this: Done.
|
|
421
|
+
`,
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// M002: empty deps list — no constraint from deps, but still sequential after M001
|
|
425
|
+
writeRoadmap(
|
|
426
|
+
base,
|
|
427
|
+
"M002",
|
|
428
|
+
`# M002: Second Milestone
|
|
429
|
+
|
|
430
|
+
**Vision:** Empty deps list, no blocking constraint.
|
|
431
|
+
|
|
432
|
+
## Slices
|
|
433
|
+
|
|
434
|
+
- [ ] **S01: Waiting for M001** \`risk:low\` \`depends:[]\`
|
|
435
|
+
> After this: Done.
|
|
436
|
+
`,
|
|
437
|
+
);
|
|
438
|
+
writeContext(base, "M002", "depends_on: []");
|
|
439
|
+
|
|
440
|
+
const state = await deriveState(base);
|
|
441
|
+
|
|
442
|
+
assertEq(
|
|
443
|
+
state.registry[0]?.status,
|
|
444
|
+
"active",
|
|
445
|
+
"empty-deps-list: M001 is active",
|
|
446
|
+
);
|
|
447
|
+
assertEq(
|
|
448
|
+
state.registry[1]?.status,
|
|
449
|
+
"pending",
|
|
450
|
+
"empty-deps-list: M002 is pending (M001 not done yet)",
|
|
451
|
+
);
|
|
452
|
+
assert(
|
|
453
|
+
state.phase !== "blocked",
|
|
454
|
+
"empty-deps-list: phase is not blocked",
|
|
455
|
+
);
|
|
456
|
+
} finally {
|
|
457
|
+
cleanup(base);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
462
|
+
// Results
|
|
463
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
464
|
+
|
|
465
|
+
console.log(`\n${"=".repeat(40)}`);
|
|
466
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
467
|
+
if (failed > 0) {
|
|
468
|
+
process.exit(1);
|
|
469
|
+
} else {
|
|
470
|
+
console.log("All tests passed ✓");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
main().catch((error) => {
|
|
475
|
+
console.error(error);
|
|
476
|
+
process.exit(1);
|
|
477
|
+
});
|