@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,317 @@
|
|
|
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
|
+
// loadPrompt reads from ~/.kata/agent/extensions/kata/prompts/ (main checkout).
|
|
13
|
+
// In a worktree the file may not exist there yet, so we resolve prompts
|
|
14
|
+
// relative to this test file's location (the worktree copy).
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const worktreePromptsDir = join(__dirname, "..", "prompts");
|
|
17
|
+
|
|
18
|
+
let passed = 0;
|
|
19
|
+
let failed = 0;
|
|
20
|
+
|
|
21
|
+
function assert(condition: boolean, message: string): void {
|
|
22
|
+
if (condition) {
|
|
23
|
+
passed++;
|
|
24
|
+
} else {
|
|
25
|
+
failed++;
|
|
26
|
+
console.error(` FAIL: ${message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
31
|
+
if (JSON.stringify(actual) === JSON.stringify(expected)) {
|
|
32
|
+
passed++;
|
|
33
|
+
} else {
|
|
34
|
+
failed++;
|
|
35
|
+
console.error(
|
|
36
|
+
` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Load a prompt template from the worktree prompts directory
|
|
43
|
+
* and apply variable substitution (mirrors loadPrompt logic).
|
|
44
|
+
*/
|
|
45
|
+
function loadPromptFromWorktree(
|
|
46
|
+
name: string,
|
|
47
|
+
vars: Record<string, string> = {},
|
|
48
|
+
): string {
|
|
49
|
+
const path = join(worktreePromptsDir, `${name}.md`);
|
|
50
|
+
let content = readFileSync(path, "utf-8");
|
|
51
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
52
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
53
|
+
}
|
|
54
|
+
return content.trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function createFixtureBase(): string {
|
|
60
|
+
const base = mkdtempSync(join(tmpdir(), "kata-complete-ms-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 writeMilestoneSummary(
|
|
72
|
+
base: string,
|
|
73
|
+
mid: string,
|
|
74
|
+
content: string,
|
|
75
|
+
): void {
|
|
76
|
+
const dir = join(base, ".kata", "milestones", mid);
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cleanup(base: string): void {
|
|
82
|
+
rmSync(base, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
86
|
+
// Tests
|
|
87
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
88
|
+
|
|
89
|
+
async function main(): Promise<void> {
|
|
90
|
+
// ─── Prompt Template Loading ───────────────────────────────────────────
|
|
91
|
+
console.log("\n=== complete-milestone prompt template exists ===");
|
|
92
|
+
{
|
|
93
|
+
let result: string;
|
|
94
|
+
let threw = false;
|
|
95
|
+
try {
|
|
96
|
+
result = loadPromptFromWorktree("complete-milestone", {
|
|
97
|
+
milestoneId: "M001",
|
|
98
|
+
milestoneTitle: "Test Milestone",
|
|
99
|
+
roadmapPath: ".kata/milestones/M001/M001-ROADMAP.md",
|
|
100
|
+
inlinedContext: "test context block",
|
|
101
|
+
});
|
|
102
|
+
} catch (err) {
|
|
103
|
+
threw = true;
|
|
104
|
+
result = "";
|
|
105
|
+
console.error(` ERROR: loadPrompt threw: ${err}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
assert(!threw, "loadPrompt does not throw for complete-milestone");
|
|
109
|
+
assert(
|
|
110
|
+
typeof result === "string" && result.length > 0,
|
|
111
|
+
"loadPrompt returns a non-empty string",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Variable Substitution ─────────────────────────────────────────────
|
|
116
|
+
console.log("\n=== prompt variable substitution ===");
|
|
117
|
+
{
|
|
118
|
+
const prompt = loadPromptFromWorktree("complete-milestone", {
|
|
119
|
+
milestoneId: "M001",
|
|
120
|
+
milestoneTitle: "Integration Feature",
|
|
121
|
+
roadmapPath: ".kata/milestones/M001/M001-ROADMAP.md",
|
|
122
|
+
inlinedContext: "--- inlined slice summaries and context ---",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
assert(prompt.includes("M001"), "prompt contains milestoneId 'M001'");
|
|
126
|
+
assert(
|
|
127
|
+
prompt.includes("Integration Feature"),
|
|
128
|
+
"prompt contains milestoneTitle",
|
|
129
|
+
);
|
|
130
|
+
assert(
|
|
131
|
+
prompt.includes(".kata/milestones/M001/M001-ROADMAP.md"),
|
|
132
|
+
"prompt contains roadmapPath",
|
|
133
|
+
);
|
|
134
|
+
assert(
|
|
135
|
+
prompt.includes("--- inlined slice summaries and context ---"),
|
|
136
|
+
"prompt contains inlinedContext",
|
|
137
|
+
);
|
|
138
|
+
assert(
|
|
139
|
+
!prompt.includes("{{milestoneId}}"),
|
|
140
|
+
"no un-substituted {{milestoneId}}",
|
|
141
|
+
);
|
|
142
|
+
assert(
|
|
143
|
+
!prompt.includes("{{milestoneTitle}}"),
|
|
144
|
+
"no un-substituted {{milestoneTitle}}",
|
|
145
|
+
);
|
|
146
|
+
assert(
|
|
147
|
+
!prompt.includes("{{roadmapPath}}"),
|
|
148
|
+
"no un-substituted {{roadmapPath}}",
|
|
149
|
+
);
|
|
150
|
+
assert(
|
|
151
|
+
!prompt.includes("{{inlinedContext}}"),
|
|
152
|
+
"no un-substituted {{inlinedContext}}",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Prompt Content Integrity ──────────────────────────────────────────
|
|
157
|
+
console.log("\n=== prompt content integrity ===");
|
|
158
|
+
{
|
|
159
|
+
const prompt = loadPromptFromWorktree("complete-milestone", {
|
|
160
|
+
milestoneId: "M002",
|
|
161
|
+
milestoneTitle: "Completion Workflow",
|
|
162
|
+
roadmapPath: ".kata/milestones/M002/M002-ROADMAP.md",
|
|
163
|
+
inlinedContext: "context",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
assert(
|
|
167
|
+
prompt.includes("Complete Milestone"),
|
|
168
|
+
"prompt contains 'Complete Milestone' heading",
|
|
169
|
+
);
|
|
170
|
+
assert(
|
|
171
|
+
prompt.includes("success criter") || prompt.includes("success criteria"),
|
|
172
|
+
"prompt mentions success criteria verification",
|
|
173
|
+
);
|
|
174
|
+
assert(
|
|
175
|
+
prompt.includes("milestone-summary") ||
|
|
176
|
+
prompt.includes("milestoneSummary"),
|
|
177
|
+
"prompt references milestone summary artifact",
|
|
178
|
+
);
|
|
179
|
+
assert(
|
|
180
|
+
prompt.includes("Milestone M002 complete"),
|
|
181
|
+
"prompt contains completion sentinel for M002",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── diagnoseExpectedArtifact behavior ─────────────────────────────────
|
|
186
|
+
// Since diagnoseExpectedArtifact is not exported from auto.ts, we test
|
|
187
|
+
// the same logic by reimplementing the switch case for complete-milestone
|
|
188
|
+
// and verifying against known path patterns.
|
|
189
|
+
console.log(
|
|
190
|
+
"\n=== diagnoseExpectedArtifact logic for complete-milestone ===",
|
|
191
|
+
);
|
|
192
|
+
{
|
|
193
|
+
// Import the path helpers used by diagnoseExpectedArtifact
|
|
194
|
+
const { relMilestoneFile } = await import("../paths.ts");
|
|
195
|
+
|
|
196
|
+
// Simulate diagnoseExpectedArtifact("complete-milestone", "M001", base) logic
|
|
197
|
+
const base = createFixtureBase();
|
|
198
|
+
try {
|
|
199
|
+
writeRoadmap(
|
|
200
|
+
base,
|
|
201
|
+
"M001",
|
|
202
|
+
`# M001\n\n## Slices\n- [x] **S01: Done** \`risk:low\` \`depends:[]\`\n > After this: done\n`,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const unitType = "complete-milestone";
|
|
206
|
+
const unitId = "M001";
|
|
207
|
+
const parts = unitId.split("/");
|
|
208
|
+
const mid = parts[0]!;
|
|
209
|
+
|
|
210
|
+
// This is the exact logic from diagnoseExpectedArtifact for "complete-milestone"
|
|
211
|
+
const result = `${relMilestoneFile(base, mid, "SUMMARY")} (milestone summary)`;
|
|
212
|
+
|
|
213
|
+
assert(typeof result === "string", "diagnose returns a string");
|
|
214
|
+
assert(result.includes("SUMMARY"), "diagnose result mentions SUMMARY");
|
|
215
|
+
assert(
|
|
216
|
+
result.includes("milestone"),
|
|
217
|
+
"diagnose result mentions milestone",
|
|
218
|
+
);
|
|
219
|
+
assert(
|
|
220
|
+
result.includes("M001"),
|
|
221
|
+
"diagnose result includes the milestone ID",
|
|
222
|
+
);
|
|
223
|
+
} finally {
|
|
224
|
+
cleanup(base);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── deriveState integration: completing-milestone dispatches correctly ─
|
|
229
|
+
console.log("\n=== deriveState completing-milestone integration ===");
|
|
230
|
+
{
|
|
231
|
+
const { deriveState, isMilestoneComplete } = await import("../state.ts");
|
|
232
|
+
const { parseRoadmap } = await import("../files.ts");
|
|
233
|
+
|
|
234
|
+
const base = createFixtureBase();
|
|
235
|
+
try {
|
|
236
|
+
writeRoadmap(
|
|
237
|
+
base,
|
|
238
|
+
"M001",
|
|
239
|
+
`# M001: Integration Test
|
|
240
|
+
|
|
241
|
+
**Vision:** Test completing-milestone flow.
|
|
242
|
+
|
|
243
|
+
## Slices
|
|
244
|
+
|
|
245
|
+
- [x] **S01: Slice One** \`risk:low\` \`depends:[]\`
|
|
246
|
+
> After this: done.
|
|
247
|
+
|
|
248
|
+
- [x] **S02: Slice Two** \`risk:low\` \`depends:[S01]\`
|
|
249
|
+
> After this: done.
|
|
250
|
+
`,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Verify isMilestoneComplete returns true
|
|
254
|
+
const { loadFile } = await import("../files.ts");
|
|
255
|
+
const roadmapPath = join(
|
|
256
|
+
base,
|
|
257
|
+
".kata",
|
|
258
|
+
"milestones",
|
|
259
|
+
"M001",
|
|
260
|
+
"M001-ROADMAP.md",
|
|
261
|
+
);
|
|
262
|
+
const roadmapContent = await loadFile(roadmapPath);
|
|
263
|
+
const roadmap = parseRoadmap(roadmapContent!);
|
|
264
|
+
assert(
|
|
265
|
+
isMilestoneComplete(roadmap),
|
|
266
|
+
"isMilestoneComplete returns true when all slices are [x]",
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Verify deriveState returns completing-milestone phase
|
|
270
|
+
const state = await deriveState(base);
|
|
271
|
+
assertEq(
|
|
272
|
+
state.phase,
|
|
273
|
+
"completing-milestone",
|
|
274
|
+
"deriveState returns completing-milestone when all slices done, no summary",
|
|
275
|
+
);
|
|
276
|
+
assertEq(state.activeMilestone?.id, "M001", "active milestone is M001");
|
|
277
|
+
assertEq(
|
|
278
|
+
state.activeSlice,
|
|
279
|
+
null,
|
|
280
|
+
"no active slice in completing-milestone",
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// Now add the summary and verify it transitions to complete
|
|
284
|
+
writeMilestoneSummary(base, "M001", "# M001 Summary\n\nDone.");
|
|
285
|
+
const stateAfter = await deriveState(base);
|
|
286
|
+
assertEq(
|
|
287
|
+
stateAfter.phase,
|
|
288
|
+
"complete",
|
|
289
|
+
"deriveState returns complete after summary exists",
|
|
290
|
+
);
|
|
291
|
+
assertEq(
|
|
292
|
+
stateAfter.registry[0]?.status,
|
|
293
|
+
"complete",
|
|
294
|
+
"registry shows complete status",
|
|
295
|
+
);
|
|
296
|
+
} finally {
|
|
297
|
+
cleanup(base);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
302
|
+
// Results
|
|
303
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
304
|
+
|
|
305
|
+
console.log(`\n${"=".repeat(40)}`);
|
|
306
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
307
|
+
if (failed > 0) {
|
|
308
|
+
process.exit(1);
|
|
309
|
+
} else {
|
|
310
|
+
console.log("All tests passed ✓");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
main().catch((error) => {
|
|
315
|
+
console.error(error);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract tests for `formatCostProjection`.
|
|
3
|
+
* Tests the pure function — no file I/O, no extension context.
|
|
4
|
+
*
|
|
5
|
+
* This test intentionally fails at import time (or on first assertion)
|
|
6
|
+
* because `formatCostProjection` does not yet exist in metrics.ts.
|
|
7
|
+
* That failure confirms the test runs against real code. (T01 state)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
type SliceAggregate,
|
|
12
|
+
formatCostProjection,
|
|
13
|
+
} from "../metrics.js";
|
|
14
|
+
|
|
15
|
+
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
function makeSliceAggregate(sliceId: string, cost: number): SliceAggregate {
|
|
18
|
+
return {
|
|
19
|
+
sliceId,
|
|
20
|
+
units: 1,
|
|
21
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
22
|
+
cost,
|
|
23
|
+
duration: 1000,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let passed = 0;
|
|
28
|
+
let failed = 0;
|
|
29
|
+
|
|
30
|
+
function assert(condition: boolean, message: string): void {
|
|
31
|
+
if (condition) {
|
|
32
|
+
passed++;
|
|
33
|
+
} else {
|
|
34
|
+
failed++;
|
|
35
|
+
console.error(` FAIL: ${message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
40
|
+
if (actual === expected) {
|
|
41
|
+
passed++;
|
|
42
|
+
} else {
|
|
43
|
+
failed++;
|
|
44
|
+
console.error(` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── formatCostProjection ─────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
console.log("\n=== formatCostProjection ===");
|
|
51
|
+
|
|
52
|
+
// 1. Zero completed slices → empty result
|
|
53
|
+
{
|
|
54
|
+
const result = formatCostProjection([], 3);
|
|
55
|
+
assertEq(result.length, 0, "zero slices → empty array");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. One slice → suppressed (need ≥2 to project reliably)
|
|
59
|
+
{
|
|
60
|
+
const result = formatCostProjection([makeSliceAggregate("M001/S01", 0.10)], 3);
|
|
61
|
+
assertEq(result.length, 0, "one slice → suppressed (no projection shown)");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 3. Two slices → projection shown (result.length > 0)
|
|
65
|
+
{
|
|
66
|
+
const slices = [
|
|
67
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
68
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
69
|
+
];
|
|
70
|
+
const result = formatCostProjection(slices, 5);
|
|
71
|
+
assert(result.length > 0, "two slices → projection shown");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 4. Two-slice result: result[0] contains "$" (cost is formatted)
|
|
75
|
+
{
|
|
76
|
+
const slices = [
|
|
77
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
78
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
79
|
+
];
|
|
80
|
+
const result = formatCostProjection(slices, 5);
|
|
81
|
+
assert(result.length > 0 && result[0].includes("$"), "projection line contains \"$\"");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 5. Budget ceiling hit: total $0.20 >= ceiling $0.05 → line contains "ceiling"
|
|
85
|
+
{
|
|
86
|
+
const slices = [
|
|
87
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
88
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
89
|
+
];
|
|
90
|
+
const result = formatCostProjection(slices, 5, 0.05);
|
|
91
|
+
const hasCeilingLine = result.some(
|
|
92
|
+
line => line.toLowerCase().includes("ceiling")
|
|
93
|
+
);
|
|
94
|
+
assert(hasCeilingLine, "ceiling warning appears when total ($0.20) >= ceiling ($0.05)");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 6. Budget ceiling not hit: total $0.20 < ceiling $100.00 → no ceiling line
|
|
98
|
+
{
|
|
99
|
+
const slices = [
|
|
100
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
101
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
102
|
+
];
|
|
103
|
+
const result = formatCostProjection(slices, 5, 100.00);
|
|
104
|
+
const hasCeilingLine = result.some(
|
|
105
|
+
line => line.toLowerCase().includes("ceiling")
|
|
106
|
+
);
|
|
107
|
+
assert(!hasCeilingLine, "no ceiling warning when total ($0.20) < ceiling ($100.00)");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 7. No ceiling arg → no ceiling line
|
|
111
|
+
{
|
|
112
|
+
const slices = [
|
|
113
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
114
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
115
|
+
];
|
|
116
|
+
const result = formatCostProjection(slices, 5);
|
|
117
|
+
const hasCeilingLine = result.some(
|
|
118
|
+
line => line.toLowerCase().includes("ceiling")
|
|
119
|
+
);
|
|
120
|
+
assert(!hasCeilingLine, "no ceiling warning when no ceiling is set");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 8. Rounding: avg $0.10 × 5 remaining = $0.50 → result[0] contains "$0.50"
|
|
124
|
+
{
|
|
125
|
+
const slices = [
|
|
126
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
127
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
128
|
+
];
|
|
129
|
+
const result = formatCostProjection(slices, 5);
|
|
130
|
+
const hasRoundedCost = result.some(line => line.includes("$0.50"));
|
|
131
|
+
assert(hasRoundedCost, "projected cost $0.50 (avg $0.10 × 5 remaining) appears in output");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 9. Bare milestone entries excluded from average:
|
|
135
|
+
// makeSliceAggregate('M001', 5.00) has no "/" in sliceId → excluded from avg calc.
|
|
136
|
+
// Only M001/S01 ($0.10) and M001/S02 ($0.10) count → avg $0.10 × 3 remaining = $0.30
|
|
137
|
+
{
|
|
138
|
+
const slices = [
|
|
139
|
+
makeSliceAggregate("M001", 5.00), // bare milestone — must be excluded
|
|
140
|
+
makeSliceAggregate("M001/S01", 0.10),
|
|
141
|
+
makeSliceAggregate("M001/S02", 0.10),
|
|
142
|
+
];
|
|
143
|
+
const result = formatCostProjection(slices, 3);
|
|
144
|
+
const hasCorrectProjection = result.some(line => line.includes("$0.30"));
|
|
145
|
+
assert(
|
|
146
|
+
hasCorrectProjection,
|
|
147
|
+
"bare milestone entry excluded from avg: projection shows $0.30 (avg $0.10 × 3), not $1.83 (including $5.00 entry)"
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
console.log(`\n${"=".repeat(40)}`);
|
|
154
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
155
|
+
if (failed > 0) {
|
|
156
|
+
console.error(`${failed} test(s) failed`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
} else {
|
|
159
|
+
console.log("All tests passed ✓");
|
|
160
|
+
}
|