@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,151 @@
|
|
|
1
|
+
import { parseRequirementCounts } from "../files.ts";
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { deriveState } from "../state.ts";
|
|
6
|
+
import { runKataDoctor } from "../doctor.ts";
|
|
7
|
+
|
|
8
|
+
let passed = 0;
|
|
9
|
+
let failed = 0;
|
|
10
|
+
|
|
11
|
+
function assert(condition: boolean, message: string): void {
|
|
12
|
+
if (condition) 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)) passed++;
|
|
21
|
+
else {
|
|
22
|
+
failed++;
|
|
23
|
+
console.error(
|
|
24
|
+
` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log("\n=== requirement counts parser ===");
|
|
30
|
+
{
|
|
31
|
+
const counts = parseRequirementCounts(`# Requirements
|
|
32
|
+
|
|
33
|
+
## Active
|
|
34
|
+
|
|
35
|
+
### R001 — Foo
|
|
36
|
+
- Status: active
|
|
37
|
+
|
|
38
|
+
### R002 — Bar
|
|
39
|
+
- Status: blocked
|
|
40
|
+
|
|
41
|
+
## Validated
|
|
42
|
+
|
|
43
|
+
### R010 — Baz
|
|
44
|
+
- Status: validated
|
|
45
|
+
|
|
46
|
+
## Deferred
|
|
47
|
+
|
|
48
|
+
### R020 — Qux
|
|
49
|
+
- Status: deferred
|
|
50
|
+
|
|
51
|
+
## Out of Scope
|
|
52
|
+
|
|
53
|
+
### R030 — No
|
|
54
|
+
- Status: out-of-scope
|
|
55
|
+
`);
|
|
56
|
+
assertEq(counts.active, 2, "counts active requirements by section");
|
|
57
|
+
assertEq(counts.validated, 1, "counts validated requirements");
|
|
58
|
+
assertEq(counts.deferred, 1, "counts deferred requirements");
|
|
59
|
+
assertEq(counts.outOfScope, 1, "counts out of scope requirements");
|
|
60
|
+
assertEq(counts.blocked, 1, "counts blocked statuses");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const base = mkdtempSync(join(tmpdir(), "kata-requirements-test-"));
|
|
64
|
+
const kata = join(base, ".kata");
|
|
65
|
+
const mDir = join(kata, "milestones", "M001");
|
|
66
|
+
const sDir = join(mDir, "slices", "S01");
|
|
67
|
+
const tDir = join(sDir, "tasks");
|
|
68
|
+
mkdirSync(tDir, { recursive: true });
|
|
69
|
+
writeFileSync(
|
|
70
|
+
join(kata, "REQUIREMENTS.md"),
|
|
71
|
+
`# Requirements
|
|
72
|
+
|
|
73
|
+
## Active
|
|
74
|
+
|
|
75
|
+
### R001 — Missing owner
|
|
76
|
+
- Class: core-capability
|
|
77
|
+
- Status: active
|
|
78
|
+
- Description: thing
|
|
79
|
+
- Why it matters: thing
|
|
80
|
+
- Source: user
|
|
81
|
+
- Primary owning slice: none yet
|
|
82
|
+
- Supporting slices: none
|
|
83
|
+
- Validation: unmapped
|
|
84
|
+
- Notes: none
|
|
85
|
+
|
|
86
|
+
## Validated
|
|
87
|
+
|
|
88
|
+
## Deferred
|
|
89
|
+
|
|
90
|
+
## Out of Scope
|
|
91
|
+
|
|
92
|
+
## Traceability
|
|
93
|
+
`,
|
|
94
|
+
"utf-8",
|
|
95
|
+
);
|
|
96
|
+
writeFileSync(
|
|
97
|
+
join(mDir, "M001-ROADMAP.md"),
|
|
98
|
+
`# M001: Demo
|
|
99
|
+
|
|
100
|
+
## Slices
|
|
101
|
+
- [ ] **S01: Demo Slice** \`risk:low\` \`depends:[]\`
|
|
102
|
+
> After this: demo works
|
|
103
|
+
`,
|
|
104
|
+
"utf-8",
|
|
105
|
+
);
|
|
106
|
+
writeFileSync(
|
|
107
|
+
join(sDir, "S01-PLAN.md"),
|
|
108
|
+
`# S01: Demo Slice
|
|
109
|
+
|
|
110
|
+
**Goal:** Demo
|
|
111
|
+
**Demo:** Demo
|
|
112
|
+
|
|
113
|
+
## Must-Haves
|
|
114
|
+
- done
|
|
115
|
+
|
|
116
|
+
## Tasks
|
|
117
|
+
- [ ] **T01: Implement thing** \`est:10m\`
|
|
118
|
+
Task is in progress.
|
|
119
|
+
`,
|
|
120
|
+
"utf-8",
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
console.log("\n=== deriveState includes requirements counts ===");
|
|
124
|
+
{
|
|
125
|
+
const state = await deriveState(base);
|
|
126
|
+
assert(
|
|
127
|
+
state.requirements !== undefined,
|
|
128
|
+
"state includes requirements summary",
|
|
129
|
+
);
|
|
130
|
+
assertEq(
|
|
131
|
+
state.requirements?.active,
|
|
132
|
+
1,
|
|
133
|
+
"state reports active requirement count",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log("\n=== doctor flags orphaned active requirement ===");
|
|
138
|
+
{
|
|
139
|
+
const report = await runKataDoctor(base);
|
|
140
|
+
assert(
|
|
141
|
+
report.issues.some(
|
|
142
|
+
(issue) => issue.code === "active_requirement_missing_owner",
|
|
143
|
+
),
|
|
144
|
+
"doctor flags missing owner",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
rmSync(base, { recursive: true, force: true });
|
|
149
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
150
|
+
if (failed > 0) process.exit(1);
|
|
151
|
+
console.log("All tests passed ✓");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ESM resolve hook: .js → .ts rewriting for test environments.
|
|
2
|
+
// Only rewrites relative imports from our own source files — not from node_modules.
|
|
3
|
+
|
|
4
|
+
export function resolve(specifier, context, nextResolve) {
|
|
5
|
+
const parentURL = context.parentURL || '';
|
|
6
|
+
const isFromNodeModules = parentURL.includes('/node_modules/');
|
|
7
|
+
|
|
8
|
+
if (specifier.endsWith('.js') && !specifier.startsWith('node:') && !isFromNodeModules) {
|
|
9
|
+
const tsSpecifier = specifier.replace(/\.js$/, '.ts');
|
|
10
|
+
try {
|
|
11
|
+
return nextResolve(tsSpecifier, context);
|
|
12
|
+
} catch {
|
|
13
|
+
// fall through to default resolution
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return nextResolve(specifier, context);
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Custom ESM resolver: rewrites .js imports to .ts for node --test with TypeScript sources.
|
|
2
|
+
// Usage: node --import ./agent/extensions/kata/tests/resolve-ts.mjs --test ...
|
|
3
|
+
//
|
|
4
|
+
// This is needed because pi extension source files use .js import specifiers
|
|
5
|
+
// (the pi runtime bundler convention), but only .ts files exist on disk.
|
|
6
|
+
// Node's built-in TypeScript support strips types but doesn't rewrite specifiers.
|
|
7
|
+
|
|
8
|
+
import { register } from 'node:module';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
|
|
11
|
+
register(new URL('./resolve-ts-hooks.mjs', import.meta.url), pathToFileURL('./'));
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// Tests for extractUatType — the core UAT classification primitive — plus
|
|
2
|
+
// prompt template loading and dispatch precondition assertions (via
|
|
3
|
+
// resolveSliceFile / extractUatType on real fixture files).
|
|
4
|
+
//
|
|
5
|
+
// Sections:
|
|
6
|
+
// (a)–(j) extractUatType classification (17 assertions from T01)
|
|
7
|
+
// (k) run-uat prompt template loading and content integrity (8 assertions)
|
|
8
|
+
// (l) dispatch precondition assertions via resolveSliceFile (4 assertions)
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { join, dirname } from "node:path";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
|
|
21
|
+
import { extractUatType } from "../files.ts";
|
|
22
|
+
import { resolveSliceFile } from "../paths.ts";
|
|
23
|
+
|
|
24
|
+
// ─── Worktree-aware prompt loader ──────────────────────────────────────────
|
|
25
|
+
// Resolves prompts relative to this test file so the worktree copy is used
|
|
26
|
+
// instead of the main checkout copy (matches complete-milestone.test.ts pattern).
|
|
27
|
+
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const worktreePromptsDir = join(__dirname, "..", "prompts");
|
|
30
|
+
|
|
31
|
+
function loadPromptFromWorktree(
|
|
32
|
+
name: string,
|
|
33
|
+
vars: Record<string, string> = {},
|
|
34
|
+
): string {
|
|
35
|
+
const path = join(worktreePromptsDir, `${name}.md`);
|
|
36
|
+
let content = readFileSync(path, "utf-8");
|
|
37
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
38
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
39
|
+
}
|
|
40
|
+
return content.trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Assertion helpers ─────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
let passed = 0;
|
|
46
|
+
let failed = 0;
|
|
47
|
+
|
|
48
|
+
function assert(condition: boolean, message: string): void {
|
|
49
|
+
if (condition) {
|
|
50
|
+
passed++;
|
|
51
|
+
} else {
|
|
52
|
+
failed++;
|
|
53
|
+
console.error(` FAIL: ${message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
58
|
+
if (JSON.stringify(actual) === JSON.stringify(expected)) {
|
|
59
|
+
passed++;
|
|
60
|
+
} else {
|
|
61
|
+
failed++;
|
|
62
|
+
console.error(
|
|
63
|
+
` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Fixture helpers ───────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
function createFixtureBase(): string {
|
|
71
|
+
const base = mkdtempSync(join(tmpdir(), "kata-run-uat-test-"));
|
|
72
|
+
mkdirSync(join(base, ".kata", "milestones"), { recursive: true });
|
|
73
|
+
return base;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeSliceFile(
|
|
77
|
+
base: string,
|
|
78
|
+
mid: string,
|
|
79
|
+
sid: string,
|
|
80
|
+
suffix: string,
|
|
81
|
+
content: string,
|
|
82
|
+
): void {
|
|
83
|
+
const dir = join(base, ".kata", "milestones", mid, "slices", sid);
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
writeFileSync(join(dir, `${sid}-${suffix}.md`), content);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function cleanup(base: string): void {
|
|
89
|
+
rmSync(base, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function makeUatContent(mode: string): string {
|
|
93
|
+
return `# UAT File\n\n## UAT Type\n\n- UAT mode: ${mode}\n- Some other bullet: value\n`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
97
|
+
// Tests
|
|
98
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
99
|
+
|
|
100
|
+
async function main(): Promise<void> {
|
|
101
|
+
// ─── (a) artifact-driven ──────────────────────────────────────────────────
|
|
102
|
+
console.log("\n── (a) artifact-driven");
|
|
103
|
+
|
|
104
|
+
assertEq(
|
|
105
|
+
extractUatType(makeUatContent("artifact-driven")),
|
|
106
|
+
"artifact-driven",
|
|
107
|
+
"plain artifact-driven → artifact-driven",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
assertEq(
|
|
111
|
+
extractUatType("## UAT Type\n\n- UAT mode: artifact-driven\n"),
|
|
112
|
+
"artifact-driven",
|
|
113
|
+
"minimal content, artifact-driven",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// ─── (b) live-runtime ─────────────────────────────────────────────────────
|
|
117
|
+
console.log("\n── (b) live-runtime");
|
|
118
|
+
|
|
119
|
+
assertEq(
|
|
120
|
+
extractUatType(makeUatContent("live-runtime")),
|
|
121
|
+
"live-runtime",
|
|
122
|
+
"plain live-runtime → live-runtime",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// ─── (c) human-experience ─────────────────────────────────────────────────
|
|
126
|
+
console.log("\n── (c) human-experience");
|
|
127
|
+
|
|
128
|
+
assertEq(
|
|
129
|
+
extractUatType(makeUatContent("human-experience")),
|
|
130
|
+
"human-experience",
|
|
131
|
+
"plain human-experience → human-experience",
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// ─── (d) mixed standalone ─────────────────────────────────────────────────
|
|
135
|
+
console.log("\n── (d) mixed standalone");
|
|
136
|
+
|
|
137
|
+
assertEq(
|
|
138
|
+
extractUatType(makeUatContent("mixed")),
|
|
139
|
+
"mixed",
|
|
140
|
+
"plain mixed → mixed",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// ─── (e) mixed with parenthetical ─────────────────────────────────────────
|
|
144
|
+
console.log("\n── (e) mixed parenthetical");
|
|
145
|
+
|
|
146
|
+
assertEq(
|
|
147
|
+
extractUatType(makeUatContent("mixed (artifact-driven + live-runtime)")),
|
|
148
|
+
"mixed",
|
|
149
|
+
"mixed (artifact-driven + live-runtime) → mixed (leading keyword only)",
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
assertEq(
|
|
153
|
+
extractUatType(makeUatContent("mixed (some other description)")),
|
|
154
|
+
"mixed",
|
|
155
|
+
"mixed with arbitrary parenthetical → mixed",
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// ─── (f) missing ## UAT Type section ──────────────────────────────────────
|
|
159
|
+
console.log("\n── (f) missing UAT Type section");
|
|
160
|
+
|
|
161
|
+
assertEq(
|
|
162
|
+
extractUatType("# UAT File\n\n## Overview\n\nSome content.\n"),
|
|
163
|
+
undefined,
|
|
164
|
+
"no ## UAT Type section → undefined",
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
assertEq(extractUatType(""), undefined, "empty content → undefined");
|
|
168
|
+
|
|
169
|
+
// ─── (g) ## UAT Type present but no UAT mode: bullet ─────────────────────
|
|
170
|
+
console.log("\n── (g) UAT Type section present, no UAT mode: bullet");
|
|
171
|
+
|
|
172
|
+
assertEq(
|
|
173
|
+
extractUatType(
|
|
174
|
+
"## UAT Type\n\n- Some other bullet: value\n- Another bullet\n",
|
|
175
|
+
),
|
|
176
|
+
undefined,
|
|
177
|
+
"section present but no UAT mode: bullet → undefined",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
assertEq(
|
|
181
|
+
extractUatType("## UAT Type\n\n"),
|
|
182
|
+
undefined,
|
|
183
|
+
"section present but empty → undefined",
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// ─── (h) unknown keyword ──────────────────────────────────────────────────
|
|
187
|
+
console.log("\n── (h) unknown keyword");
|
|
188
|
+
|
|
189
|
+
assertEq(
|
|
190
|
+
extractUatType(makeUatContent("automated")),
|
|
191
|
+
undefined,
|
|
192
|
+
"unknown keyword automated → undefined",
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
assertEq(
|
|
196
|
+
extractUatType(makeUatContent("fully-automated")),
|
|
197
|
+
undefined,
|
|
198
|
+
"unknown keyword fully-automated → undefined",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// ─── (i) extra whitespace around value ────────────────────────────────────
|
|
202
|
+
console.log("\n── (i) extra whitespace");
|
|
203
|
+
|
|
204
|
+
assertEq(
|
|
205
|
+
extractUatType("## UAT Type\n\n- UAT mode: artifact-driven \n"),
|
|
206
|
+
"artifact-driven",
|
|
207
|
+
"leading/trailing whitespace around value → still classified correctly",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
assertEq(
|
|
211
|
+
extractUatType(
|
|
212
|
+
"## UAT Type\n\n- UAT mode: mixed (artifact-driven + live-runtime) \n",
|
|
213
|
+
),
|
|
214
|
+
"mixed",
|
|
215
|
+
"whitespace around mixed parenthetical → mixed",
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// ─── (j) case sensitivity ─────────────────────────────────────────────────
|
|
219
|
+
console.log("\n── (j) case sensitivity");
|
|
220
|
+
|
|
221
|
+
assertEq(
|
|
222
|
+
extractUatType(makeUatContent("Artifact-Driven")),
|
|
223
|
+
"artifact-driven",
|
|
224
|
+
"Artifact-Driven (title case) → artifact-driven (function lowercases before matching)",
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
assertEq(
|
|
228
|
+
extractUatType(makeUatContent("MIXED")),
|
|
229
|
+
"mixed",
|
|
230
|
+
"MIXED (upper case) → mixed (function lowercases before matching)",
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// ─── (k) prompt template loading and content integrity ────────────────────
|
|
234
|
+
console.log("\n── (k) run-uat prompt template");
|
|
235
|
+
|
|
236
|
+
const milestoneId = "M001";
|
|
237
|
+
const sliceId = "S01";
|
|
238
|
+
const uatPath = ".kata/milestones/M001/slices/S01/S01-UAT.md";
|
|
239
|
+
const uatResultAbsPath = "/tmp/kata-test/S01-UAT-RESULT.md";
|
|
240
|
+
const uatResultPath = ".kata/milestones/M001/slices/S01/S01-UAT-RESULT.md";
|
|
241
|
+
const uatType = "artifact-driven";
|
|
242
|
+
const inlinedContext = "<!-- no context -->";
|
|
243
|
+
|
|
244
|
+
let promptResult: string | undefined;
|
|
245
|
+
let promptThrew = false;
|
|
246
|
+
try {
|
|
247
|
+
promptResult = loadPromptFromWorktree("run-uat", {
|
|
248
|
+
milestoneId,
|
|
249
|
+
sliceId,
|
|
250
|
+
uatPath,
|
|
251
|
+
uatResultAbsPath,
|
|
252
|
+
uatResultPath,
|
|
253
|
+
uatType,
|
|
254
|
+
inlinedContext,
|
|
255
|
+
});
|
|
256
|
+
} catch {
|
|
257
|
+
promptThrew = true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
assert(
|
|
261
|
+
!promptThrew,
|
|
262
|
+
'loadPromptFromWorktree("run-uat", vars) does not throw',
|
|
263
|
+
);
|
|
264
|
+
assert(
|
|
265
|
+
typeof promptResult === "string" && promptResult.length > 0,
|
|
266
|
+
"run-uat prompt result is a non-empty string",
|
|
267
|
+
);
|
|
268
|
+
assert(
|
|
269
|
+
promptResult?.includes(milestoneId) ?? false,
|
|
270
|
+
`prompt contains milestoneId value "${milestoneId}" after substitution`,
|
|
271
|
+
);
|
|
272
|
+
assert(
|
|
273
|
+
promptResult?.includes(sliceId) ?? false,
|
|
274
|
+
`prompt contains sliceId value "${sliceId}" after substitution`,
|
|
275
|
+
);
|
|
276
|
+
assert(
|
|
277
|
+
promptResult?.includes(uatResultAbsPath) ?? false,
|
|
278
|
+
`prompt contains uatResultAbsPath value after substitution`,
|
|
279
|
+
);
|
|
280
|
+
assert(
|
|
281
|
+
!/\{\{[^}]+\}\}/.test(promptResult ?? ""),
|
|
282
|
+
"no unreplaced {{...}} tokens remain after variable substitution",
|
|
283
|
+
);
|
|
284
|
+
assert(
|
|
285
|
+
/artifact|execute|run/i.test(promptResult ?? ""),
|
|
286
|
+
"prompt contains artifact-driven execution language (artifact/execute/run)",
|
|
287
|
+
);
|
|
288
|
+
assert(
|
|
289
|
+
/surfaced for human review/i.test(promptResult ?? ""),
|
|
290
|
+
'prompt contains "surfaced for human review" text for non-artifact-driven path',
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// ─── (l) dispatch precondition assertions via resolveSliceFile ────────────
|
|
294
|
+
console.log("\n── (l) dispatch preconditions via resolveSliceFile");
|
|
295
|
+
|
|
296
|
+
// State A: UAT file exists, UAT-RESULT file does NOT — triggers dispatch
|
|
297
|
+
{
|
|
298
|
+
const base = createFixtureBase();
|
|
299
|
+
const uatContent = makeUatContent("artifact-driven");
|
|
300
|
+
try {
|
|
301
|
+
writeSliceFile(base, "M001", "S01", "UAT", uatContent);
|
|
302
|
+
|
|
303
|
+
const uatFilePath = resolveSliceFile(base, "M001", "S01", "UAT");
|
|
304
|
+
assert(
|
|
305
|
+
uatFilePath !== null,
|
|
306
|
+
'resolveSliceFile(..., "UAT") returns non-null when UAT file exists (dispatch trigger state)',
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const uatResultFilePath = resolveSliceFile(
|
|
310
|
+
base,
|
|
311
|
+
"M001",
|
|
312
|
+
"S01",
|
|
313
|
+
"UAT-RESULT",
|
|
314
|
+
);
|
|
315
|
+
assertEq(
|
|
316
|
+
uatResultFilePath,
|
|
317
|
+
null,
|
|
318
|
+
'resolveSliceFile(..., "UAT-RESULT") returns null when result file missing (dispatch trigger state)',
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// End-to-end: file content → parse → classify
|
|
322
|
+
const rawContent = readFileSync(uatFilePath!, "utf-8");
|
|
323
|
+
assertEq(
|
|
324
|
+
extractUatType(rawContent),
|
|
325
|
+
"artifact-driven",
|
|
326
|
+
"extractUatType on fixture UAT file returns expected type (end-to-end data flow)",
|
|
327
|
+
);
|
|
328
|
+
} finally {
|
|
329
|
+
cleanup(base);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// State B: UAT-RESULT file exists — dispatch is skipped (idempotent)
|
|
334
|
+
{
|
|
335
|
+
const base = createFixtureBase();
|
|
336
|
+
try {
|
|
337
|
+
writeSliceFile(
|
|
338
|
+
base,
|
|
339
|
+
"M001",
|
|
340
|
+
"S01",
|
|
341
|
+
"UAT",
|
|
342
|
+
makeUatContent("artifact-driven"),
|
|
343
|
+
);
|
|
344
|
+
writeSliceFile(
|
|
345
|
+
base,
|
|
346
|
+
"M001",
|
|
347
|
+
"S01",
|
|
348
|
+
"UAT-RESULT",
|
|
349
|
+
"# UAT Result\n\nverdict: PASS\n",
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const uatResultFilePath = resolveSliceFile(
|
|
353
|
+
base,
|
|
354
|
+
"M001",
|
|
355
|
+
"S01",
|
|
356
|
+
"UAT-RESULT",
|
|
357
|
+
);
|
|
358
|
+
assert(
|
|
359
|
+
uatResultFilePath !== null,
|
|
360
|
+
'resolveSliceFile(..., "UAT-RESULT") returns non-null when result file exists (idempotent skip state)',
|
|
361
|
+
);
|
|
362
|
+
} finally {
|
|
363
|
+
cleanup(base);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
368
|
+
// Results
|
|
369
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
370
|
+
|
|
371
|
+
console.log(`\n${"=".repeat(40)}`);
|
|
372
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
373
|
+
if (failed > 0) {
|
|
374
|
+
process.exit(1);
|
|
375
|
+
} else {
|
|
376
|
+
console.log("All tests passed ✓");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
main().catch((error) => {
|
|
381
|
+
console.error(error);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
});
|