@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,976 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kata Guided Flow — Smart Entry Wizard
|
|
3
|
+
*
|
|
4
|
+
* One function: showSmartEntry(). Reads state from disk, shows a contextual
|
|
5
|
+
* wizard via showNextAction(), and dispatches through KATA-WORKFLOW.md.
|
|
6
|
+
* No execution state, no hooks, no tools — the LLM does the rest.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ExtensionAPI,
|
|
11
|
+
ExtensionContext,
|
|
12
|
+
ExtensionCommandContext,
|
|
13
|
+
} from "@mariozechner/pi-coding-agent";
|
|
14
|
+
import { showNextAction } from "../shared/next-action-ui.js";
|
|
15
|
+
import { loadFile, parseRoadmap } from "./files.js";
|
|
16
|
+
import { loadPrompt } from "./prompt-loader.js";
|
|
17
|
+
import { deriveState } from "./state.js";
|
|
18
|
+
import { startAuto } from "./auto.js";
|
|
19
|
+
import { readCrashLock, clearLock, formatCrashInfo } from "./crash-recovery.js";
|
|
20
|
+
import {
|
|
21
|
+
kataRoot,
|
|
22
|
+
milestonesDir,
|
|
23
|
+
resolveMilestoneFile,
|
|
24
|
+
resolveSliceFile,
|
|
25
|
+
resolveSlicePath,
|
|
26
|
+
resolveKataRootFile,
|
|
27
|
+
relKataRootFile,
|
|
28
|
+
relMilestoneFile,
|
|
29
|
+
relSliceFile,
|
|
30
|
+
relSlicePath,
|
|
31
|
+
} from "./paths.js";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
34
|
+
import { execSync } from "node:child_process";
|
|
35
|
+
import { ensureGitignore, ensurePreferences } from "./gitignore.js";
|
|
36
|
+
|
|
37
|
+
// ─── Auto-start after discuss ─────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Stashed context + flag for auto-starting after discuss phase completes */
|
|
40
|
+
let pendingAutoStart: {
|
|
41
|
+
ctx: ExtensionCommandContext;
|
|
42
|
+
pi: ExtensionAPI;
|
|
43
|
+
basePath: string;
|
|
44
|
+
milestoneId: string; // the milestone being discussed
|
|
45
|
+
} | null = null;
|
|
46
|
+
|
|
47
|
+
/** Called from agent_end to check if auto-mode should start after discuss */
|
|
48
|
+
export function checkAutoStartAfterDiscuss(): boolean {
|
|
49
|
+
if (!pendingAutoStart) return false;
|
|
50
|
+
|
|
51
|
+
const { ctx, pi, basePath, milestoneId } = pendingAutoStart;
|
|
52
|
+
|
|
53
|
+
// Don't fire until the discuss phase has actually produced a context file
|
|
54
|
+
// for the milestone being discussed. agent_end fires after every LLM turn,
|
|
55
|
+
// including the initial "What do you want to build?" response — we need to
|
|
56
|
+
// wait for the full conversation to complete and the LLM to write CONTEXT.md.
|
|
57
|
+
const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
|
|
58
|
+
if (!contextFile) return false; // no context yet — keep waiting
|
|
59
|
+
|
|
60
|
+
pendingAutoStart = null;
|
|
61
|
+
startAuto(ctx, pi, basePath, false).catch(() => {});
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
type UIContext = ExtensionContext;
|
|
68
|
+
|
|
69
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read KATA-WORKFLOW.md and dispatch it to the LLM with a contextual note.
|
|
73
|
+
* This is the only way the wizard triggers work — everything else is the LLM's job.
|
|
74
|
+
*/
|
|
75
|
+
function dispatchWorkflow(
|
|
76
|
+
pi: ExtensionAPI,
|
|
77
|
+
note: string,
|
|
78
|
+
customType = "kata-run",
|
|
79
|
+
): void {
|
|
80
|
+
const workflowPath =
|
|
81
|
+
process.env.KATA_WORKFLOW_PATH ??
|
|
82
|
+
join(process.env.HOME ?? "~", ".kata-cli", "KATA-WORKFLOW.md");
|
|
83
|
+
const workflow = readFileSync(workflowPath, "utf-8");
|
|
84
|
+
|
|
85
|
+
pi.sendMessage(
|
|
86
|
+
{
|
|
87
|
+
customType,
|
|
88
|
+
content: `Read the following Kata workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${note}`,
|
|
89
|
+
display: false,
|
|
90
|
+
},
|
|
91
|
+
{ triggerTurn: true },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build the discuss-and-plan prompt for a new milestone.
|
|
97
|
+
* Used by all three "new milestone" paths (first ever, no active, all complete).
|
|
98
|
+
*/
|
|
99
|
+
function buildDiscussPrompt(
|
|
100
|
+
nextId: string,
|
|
101
|
+
preamble: string,
|
|
102
|
+
basePath: string,
|
|
103
|
+
): string {
|
|
104
|
+
const milestoneDirAbs = join(basePath, ".kata", "milestones", nextId);
|
|
105
|
+
return loadPrompt("discuss", {
|
|
106
|
+
milestoneId: nextId,
|
|
107
|
+
preamble,
|
|
108
|
+
contextAbsPath: join(milestoneDirAbs, `${nextId}-CONTEXT.md`),
|
|
109
|
+
roadmapAbsPath: join(milestoneDirAbs, `${nextId}-ROADMAP.md`),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function findMilestoneIds(basePath: string): string[] {
|
|
114
|
+
const dir = milestonesDir(basePath);
|
|
115
|
+
try {
|
|
116
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
117
|
+
.filter((d) => d.isDirectory())
|
|
118
|
+
.map((d) => {
|
|
119
|
+
const match = d.name.match(/^(M\d+)/);
|
|
120
|
+
return match ? match[1] : d.name;
|
|
121
|
+
})
|
|
122
|
+
.sort();
|
|
123
|
+
} catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Queue ─────────────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Queue future milestones via conversational intake.
|
|
132
|
+
*
|
|
133
|
+
* Safe to run while auto-mode is executing — only writes to future milestone
|
|
134
|
+
* directories (which auto-mode won't touch until it reaches them) and appends
|
|
135
|
+
* to project.md / queue.md.
|
|
136
|
+
*
|
|
137
|
+
* The flow:
|
|
138
|
+
* 1. Build context about all existing milestones (complete, active, pending)
|
|
139
|
+
* 2. Dispatch the queue prompt — LLM discusses with the user, assesses scope
|
|
140
|
+
* 3. LLM writes CONTEXT.md files for new milestones (no roadmaps — JIT)
|
|
141
|
+
* 4. Auto-mode picks them up naturally when it advances past current work
|
|
142
|
+
*
|
|
143
|
+
* Root durable artifacts use uppercase names like PROJECT.md and QUEUE.md.
|
|
144
|
+
*/
|
|
145
|
+
export async function showQueue(
|
|
146
|
+
ctx: ExtensionCommandContext,
|
|
147
|
+
pi: ExtensionAPI,
|
|
148
|
+
basePath: string,
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
// ── Ensure .kata/ exists ─────────────────────────────────────────────
|
|
151
|
+
const kataDir = kataRoot(basePath);
|
|
152
|
+
if (!existsSync(kataDir)) {
|
|
153
|
+
ctx.ui.notify(
|
|
154
|
+
"No Kata project found. Run /kata to start one first.",
|
|
155
|
+
"warning",
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const state = await deriveState(basePath);
|
|
161
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
162
|
+
|
|
163
|
+
if (milestoneIds.length === 0) {
|
|
164
|
+
ctx.ui.notify(
|
|
165
|
+
"No milestones exist yet. Run /kata to create the first one.",
|
|
166
|
+
"warning",
|
|
167
|
+
);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Build existing milestones context for the prompt ────────────────
|
|
172
|
+
const existingContext = await buildExistingMilestonesContext(
|
|
173
|
+
basePath,
|
|
174
|
+
milestoneIds,
|
|
175
|
+
state,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// ── Determine next milestone ID ─────────────────────────────────────
|
|
179
|
+
const maxNum = milestoneIds.reduce((max, id) => {
|
|
180
|
+
const num = parseInt(id.replace(/^M/, ""), 10);
|
|
181
|
+
return num > max ? num : max;
|
|
182
|
+
}, 0);
|
|
183
|
+
const nextId = `M${String(maxNum + 1).padStart(3, "0")}`;
|
|
184
|
+
const nextIdPlus1 = `M${String(maxNum + 2).padStart(3, "0")}`;
|
|
185
|
+
|
|
186
|
+
// ── Build preamble ──────────────────────────────────────────────────
|
|
187
|
+
const activePart = state.activeMilestone
|
|
188
|
+
? `Currently executing: ${state.activeMilestone.id} — ${state.activeMilestone.title} (phase: ${state.phase}).`
|
|
189
|
+
: "No milestone currently active.";
|
|
190
|
+
|
|
191
|
+
const pendingCount = state.registry.filter(
|
|
192
|
+
(m) => m.status === "pending",
|
|
193
|
+
).length;
|
|
194
|
+
const completeCount = state.registry.filter(
|
|
195
|
+
(m) => m.status === "complete",
|
|
196
|
+
).length;
|
|
197
|
+
|
|
198
|
+
const preamble = [
|
|
199
|
+
`Queuing new work onto an existing Kata project.`,
|
|
200
|
+
activePart,
|
|
201
|
+
`${completeCount} milestone(s) complete, ${pendingCount} pending.`,
|
|
202
|
+
`Next available milestone ID: ${nextId}.`,
|
|
203
|
+
].join(" ");
|
|
204
|
+
|
|
205
|
+
// ── Dispatch the queue prompt ───────────────────────────────────────
|
|
206
|
+
const prompt = loadPrompt("queue", {
|
|
207
|
+
preamble,
|
|
208
|
+
nextId,
|
|
209
|
+
nextIdPlus1,
|
|
210
|
+
existingMilestonesContext: existingContext,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
pi.sendMessage(
|
|
214
|
+
{
|
|
215
|
+
customType: "kata-queue",
|
|
216
|
+
content: prompt,
|
|
217
|
+
display: false,
|
|
218
|
+
},
|
|
219
|
+
{ triggerTurn: true },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Build a context block describing all existing milestones for the queue prompt.
|
|
225
|
+
* Gives the LLM enough information to dedup, sequence, and dependency-check.
|
|
226
|
+
*/
|
|
227
|
+
async function buildExistingMilestonesContext(
|
|
228
|
+
basePath: string,
|
|
229
|
+
milestoneIds: string[],
|
|
230
|
+
state: import("./types.js").kataState,
|
|
231
|
+
): Promise<string> {
|
|
232
|
+
const sections: string[] = [];
|
|
233
|
+
|
|
234
|
+
// Include PROJECT.md if it exists — it has the milestone sequence and project description
|
|
235
|
+
const projectPath = resolveKataRootFile(basePath, "PROJECT");
|
|
236
|
+
if (existsSync(projectPath)) {
|
|
237
|
+
const projectContent = await loadFile(projectPath);
|
|
238
|
+
if (projectContent) {
|
|
239
|
+
sections.push(
|
|
240
|
+
`### Project Overview\nSource: \`${relKataRootFile("PROJECT")}\`\n\n${projectContent.trim()}`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Include DECISIONS.md if it exists — architectural decisions inform new milestone scoping
|
|
246
|
+
const decisionsPath = resolveKataRootFile(basePath, "DECISIONS");
|
|
247
|
+
if (existsSync(decisionsPath)) {
|
|
248
|
+
const decisionsContent = await loadFile(decisionsPath);
|
|
249
|
+
if (decisionsContent) {
|
|
250
|
+
sections.push(
|
|
251
|
+
`### Decisions Register\nSource: \`${relKataRootFile("DECISIONS")}\`\n\n${decisionsContent.trim()}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// For each milestone, include context and status
|
|
257
|
+
for (const mid of milestoneIds) {
|
|
258
|
+
const registryEntry = state.registry.find((m) => m.id === mid);
|
|
259
|
+
const status = registryEntry?.status ?? "unknown";
|
|
260
|
+
const title = registryEntry?.title ?? mid;
|
|
261
|
+
|
|
262
|
+
const parts: string[] = [];
|
|
263
|
+
parts.push(`### ${mid}: ${title}\n**Status:** ${status}`);
|
|
264
|
+
|
|
265
|
+
// Include context file — this is the primary content for understanding scope
|
|
266
|
+
const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
|
|
267
|
+
if (contextFile) {
|
|
268
|
+
const content = await loadFile(contextFile);
|
|
269
|
+
if (content) {
|
|
270
|
+
parts.push(`\n**Context:**\n${content.trim()}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For completed milestones, include the summary if it exists
|
|
275
|
+
if (status === "complete") {
|
|
276
|
+
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
|
277
|
+
if (summaryFile) {
|
|
278
|
+
const content = await loadFile(summaryFile);
|
|
279
|
+
if (content) {
|
|
280
|
+
parts.push(`\n**Summary:**\n${content.trim()}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// For active/pending milestones, include the roadmap if it exists
|
|
286
|
+
// (shows what's planned but not yet built)
|
|
287
|
+
if (status === "active" || status === "pending") {
|
|
288
|
+
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
289
|
+
if (roadmapFile) {
|
|
290
|
+
const content = await loadFile(roadmapFile);
|
|
291
|
+
if (content) {
|
|
292
|
+
parts.push(`\n**Roadmap:**\n${content.trim()}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
sections.push(parts.join("\n"));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Include queue log if it exists — shows what's been queued before
|
|
301
|
+
const queuePath = resolveKataRootFile(basePath, "QUEUE");
|
|
302
|
+
if (existsSync(queuePath)) {
|
|
303
|
+
const queueContent = await loadFile(queuePath);
|
|
304
|
+
if (queueContent) {
|
|
305
|
+
sections.push(
|
|
306
|
+
`### Previous Queue Entries\nSource: \`${relKataRootFile("QUEUE")}\`\n\n${queueContent.trim()}`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return sections.join("\n\n---\n\n");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ─── Discuss Flow ─────────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Build a rich inlined-context prompt for discussing a specific slice.
|
|
318
|
+
* Preloads roadmap, milestone context, research, decisions, and completed
|
|
319
|
+
* slice summaries so the agent can ask grounded UX/behaviour questions
|
|
320
|
+
* without wasting a turn reading files.
|
|
321
|
+
*/
|
|
322
|
+
async function buildDiscussSlicePrompt(
|
|
323
|
+
mid: string,
|
|
324
|
+
sid: string,
|
|
325
|
+
sTitle: string,
|
|
326
|
+
base: string,
|
|
327
|
+
): Promise<string> {
|
|
328
|
+
const inlined: string[] = [];
|
|
329
|
+
|
|
330
|
+
// Roadmap — always included so the agent sees surrounding slices
|
|
331
|
+
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
332
|
+
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
333
|
+
const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
|
|
334
|
+
if (roadmapContent) {
|
|
335
|
+
inlined.push(
|
|
336
|
+
`### Milestone Roadmap\nSource: \`${roadmapRel}\`\n\n${roadmapContent.trim()}`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Milestone context — understanding the full milestone intent
|
|
341
|
+
const contextPath = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
342
|
+
const contextRel = relMilestoneFile(base, mid, "CONTEXT");
|
|
343
|
+
const contextContent = contextPath ? await loadFile(contextPath) : null;
|
|
344
|
+
if (contextContent) {
|
|
345
|
+
inlined.push(
|
|
346
|
+
`### Milestone Context\nSource: \`${contextRel}\`\n\n${contextContent.trim()}`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Milestone research — technical grounding
|
|
351
|
+
const researchPath = resolveMilestoneFile(base, mid, "RESEARCH");
|
|
352
|
+
const researchRel = relMilestoneFile(base, mid, "RESEARCH");
|
|
353
|
+
const researchContent = researchPath ? await loadFile(researchPath) : null;
|
|
354
|
+
if (researchContent) {
|
|
355
|
+
inlined.push(
|
|
356
|
+
`### Milestone Research\nSource: \`${researchRel}\`\n\n${researchContent.trim()}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Decisions — architectural context that constrains this slice
|
|
361
|
+
const decisionsPath = resolveKataRootFile(base, "DECISIONS");
|
|
362
|
+
if (existsSync(decisionsPath)) {
|
|
363
|
+
const decisionsContent = await loadFile(decisionsPath);
|
|
364
|
+
if (decisionsContent) {
|
|
365
|
+
inlined.push(
|
|
366
|
+
`### Decisions Register\nSource: \`${relKataRootFile("DECISIONS")}\`\n\n${decisionsContent.trim()}`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Completed slice summaries — what was already built that this slice builds on
|
|
372
|
+
if (roadmapContent) {
|
|
373
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
374
|
+
for (const s of roadmap.slices) {
|
|
375
|
+
if (!s.done || s.id === sid) continue;
|
|
376
|
+
const summaryPath = resolveSliceFile(base, mid, s.id, "SUMMARY");
|
|
377
|
+
const summaryRel = relSliceFile(base, mid, s.id, "SUMMARY");
|
|
378
|
+
const summaryContent = summaryPath ? await loadFile(summaryPath) : null;
|
|
379
|
+
if (summaryContent) {
|
|
380
|
+
inlined.push(
|
|
381
|
+
`### ${s.id} Summary (completed)\nSource: \`${summaryRel}\`\n\n${summaryContent.trim()}`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const inlinedContext =
|
|
388
|
+
inlined.length > 0
|
|
389
|
+
? `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`
|
|
390
|
+
: `## Inlined Context\n\n_(no context files found yet — go in blind and ask broad questions)_`;
|
|
391
|
+
|
|
392
|
+
const sliceDirAbsPath = join(base, ".kata", "milestones", mid, "slices", sid);
|
|
393
|
+
const contextAbsPath = join(sliceDirAbsPath, `${sid}-CONTEXT.md`);
|
|
394
|
+
|
|
395
|
+
return loadPrompt("guided-discuss-slice", {
|
|
396
|
+
milestoneId: mid,
|
|
397
|
+
sliceId: sid,
|
|
398
|
+
sliceTitle: sTitle,
|
|
399
|
+
inlinedContext,
|
|
400
|
+
sliceDirAbsPath,
|
|
401
|
+
contextAbsPath,
|
|
402
|
+
projectRoot: base,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* /kata discuss — show a picker of non-done slices and run a slice interview.
|
|
408
|
+
* Loops back to the picker after each discussion so the user can chain
|
|
409
|
+
* multiple slice interviews in one session.
|
|
410
|
+
*/
|
|
411
|
+
export async function showDiscuss(
|
|
412
|
+
ctx: ExtensionCommandContext,
|
|
413
|
+
pi: ExtensionAPI,
|
|
414
|
+
basePath: string,
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
// Guard: no .kata/ project
|
|
417
|
+
if (!existsSync(join(basePath, ".kata"))) {
|
|
418
|
+
ctx.ui.notify(
|
|
419
|
+
"No Kata project found. Run /kata to start one first.",
|
|
420
|
+
"warning",
|
|
421
|
+
);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const state = await deriveState(basePath);
|
|
426
|
+
|
|
427
|
+
// Guard: no active milestone
|
|
428
|
+
if (!state.activeMilestone) {
|
|
429
|
+
ctx.ui.notify(
|
|
430
|
+
"No active milestone. Run /kata to create one first.",
|
|
431
|
+
"warning",
|
|
432
|
+
);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const mid = state.activeMilestone.id;
|
|
437
|
+
const milestoneTitle = state.activeMilestone.title;
|
|
438
|
+
|
|
439
|
+
// Guard: no roadmap yet
|
|
440
|
+
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
441
|
+
const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
|
|
442
|
+
if (!roadmapContent) {
|
|
443
|
+
ctx.ui.notify(
|
|
444
|
+
"No roadmap yet for this milestone. Run /kata to plan first.",
|
|
445
|
+
"warning",
|
|
446
|
+
);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
451
|
+
const pendingSlices = roadmap.slices.filter((s) => !s.done);
|
|
452
|
+
|
|
453
|
+
if (pendingSlices.length === 0) {
|
|
454
|
+
ctx.ui.notify("All slices are complete — nothing to discuss.", "info");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Loop: show picker, dispatch discuss, repeat until "not_yet"
|
|
459
|
+
while (true) {
|
|
460
|
+
const actions = pendingSlices.map((s, i) => ({
|
|
461
|
+
id: s.id,
|
|
462
|
+
label: `${s.id}: ${s.title}`,
|
|
463
|
+
description: state.activeSlice?.id === s.id ? "active slice" : "upcoming",
|
|
464
|
+
recommended: i === 0,
|
|
465
|
+
}));
|
|
466
|
+
|
|
467
|
+
const choice = await showNextAction(ctx as any, {
|
|
468
|
+
title: "Kata — Discuss a slice",
|
|
469
|
+
summary: [
|
|
470
|
+
`${mid}: ${milestoneTitle}`,
|
|
471
|
+
"Pick a slice to interview. Context file will be written when done.",
|
|
472
|
+
],
|
|
473
|
+
actions,
|
|
474
|
+
notYetMessage: "Run /kata discuss when ready.",
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
if (choice === "not_yet") return;
|
|
478
|
+
|
|
479
|
+
const chosen = pendingSlices.find((s) => s.id === choice);
|
|
480
|
+
if (!chosen) return;
|
|
481
|
+
|
|
482
|
+
const prompt = await buildDiscussSlicePrompt(
|
|
483
|
+
mid,
|
|
484
|
+
chosen.id,
|
|
485
|
+
chosen.title,
|
|
486
|
+
basePath,
|
|
487
|
+
);
|
|
488
|
+
dispatchWorkflow(pi, prompt, "kata-discuss");
|
|
489
|
+
|
|
490
|
+
// Wait for the discuss session to finish, then loop back to the picker
|
|
491
|
+
await ctx.waitForIdle();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ─── Smart Entry Point ────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* The one wizard. Reads state, shows contextual options, dispatches into the workflow doc.
|
|
499
|
+
*/
|
|
500
|
+
export async function showSmartEntry(
|
|
501
|
+
ctx: ExtensionCommandContext,
|
|
502
|
+
pi: ExtensionAPI,
|
|
503
|
+
basePath: string,
|
|
504
|
+
): Promise<void> {
|
|
505
|
+
// ── Ensure git repo exists — Kata needs it for branch-per-slice ──────
|
|
506
|
+
try {
|
|
507
|
+
execSync("git rev-parse --git-dir", { cwd: basePath, stdio: "pipe" });
|
|
508
|
+
} catch {
|
|
509
|
+
execSync("git init", { cwd: basePath, stdio: "pipe" });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── Ensure .gitignore has baseline patterns ──────────────────────────
|
|
513
|
+
ensureGitignore(basePath);
|
|
514
|
+
|
|
515
|
+
// ── No Kata project OR no milestone → Create first/next milestone ────
|
|
516
|
+
if (!existsSync(join(basePath, ".kata"))) {
|
|
517
|
+
// Bootstrap .kata/ silently — the user wants a milestone, not to "init"
|
|
518
|
+
const kataDir = kataRoot(basePath);
|
|
519
|
+
mkdirSync(join(kataDir, "milestones"), { recursive: true });
|
|
520
|
+
|
|
521
|
+
// ── Create PREFERENCES.md template ────────────────────────────────
|
|
522
|
+
ensurePreferences(basePath);
|
|
523
|
+
try {
|
|
524
|
+
execSync(
|
|
525
|
+
"git add -A .kata .gitignore && git commit -m 'chore: init kata'",
|
|
526
|
+
{
|
|
527
|
+
cwd: basePath,
|
|
528
|
+
stdio: "pipe",
|
|
529
|
+
},
|
|
530
|
+
);
|
|
531
|
+
} catch {
|
|
532
|
+
// nothing to commit — that's fine
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Check for crash from previous auto-mode session
|
|
537
|
+
const crashLock = readCrashLock(basePath);
|
|
538
|
+
if (crashLock) {
|
|
539
|
+
clearLock(basePath);
|
|
540
|
+
const resume = await showNextAction(ctx as any, {
|
|
541
|
+
title: "Kata — Interrupted Session Detected",
|
|
542
|
+
summary: [formatCrashInfo(crashLock)],
|
|
543
|
+
actions: [
|
|
544
|
+
{
|
|
545
|
+
id: "resume",
|
|
546
|
+
label: "Resume with /kata auto",
|
|
547
|
+
description: "Pick up where it left off",
|
|
548
|
+
recommended: true,
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
id: "continue",
|
|
552
|
+
label: "Continue manually",
|
|
553
|
+
description: "Open the wizard as normal",
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
});
|
|
557
|
+
if (resume === "resume") {
|
|
558
|
+
await startAuto(ctx, pi, basePath, false);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const state = await deriveState(basePath);
|
|
564
|
+
|
|
565
|
+
if (!state.activeMilestone) {
|
|
566
|
+
// Guard: if a discuss session is already in flight, don't re-inject the prompt.
|
|
567
|
+
// Both /kata and /kata auto reach this branch when no milestone exists yet.
|
|
568
|
+
// Without this guard, every subsequent /kata call overwrites pendingAutoStart
|
|
569
|
+
// and fires another dispatchWorkflow, resetting the conversation mid-interview.
|
|
570
|
+
if (pendingAutoStart) {
|
|
571
|
+
ctx.ui.notify(
|
|
572
|
+
"Discussion already in progress — answer the question above to continue.",
|
|
573
|
+
"info",
|
|
574
|
+
);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
579
|
+
const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
|
|
580
|
+
const isFirst = milestoneIds.length === 0;
|
|
581
|
+
|
|
582
|
+
if (isFirst) {
|
|
583
|
+
// First ever — skip wizard, just ask directly
|
|
584
|
+
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId };
|
|
585
|
+
dispatchWorkflow(
|
|
586
|
+
pi,
|
|
587
|
+
buildDiscussPrompt(
|
|
588
|
+
nextId,
|
|
589
|
+
`New project, milestone ${nextId}. Do NOT read or explore .kata/ — it's empty scaffolding.`,
|
|
590
|
+
basePath,
|
|
591
|
+
),
|
|
592
|
+
);
|
|
593
|
+
} else {
|
|
594
|
+
const choice = await showNextAction(ctx as any, {
|
|
595
|
+
title: "Kata — Kata Workflow",
|
|
596
|
+
summary: ["No active milestone."],
|
|
597
|
+
actions: [
|
|
598
|
+
{
|
|
599
|
+
id: "new_milestone",
|
|
600
|
+
label: "Create next milestone",
|
|
601
|
+
description: "Define what to build next.",
|
|
602
|
+
recommended: true,
|
|
603
|
+
},
|
|
604
|
+
],
|
|
605
|
+
notYetMessage: "Run /kata when ready.",
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
if (choice === "new_milestone") {
|
|
609
|
+
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId };
|
|
610
|
+
dispatchWorkflow(
|
|
611
|
+
pi,
|
|
612
|
+
buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath),
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const milestoneId = state.activeMilestone.id;
|
|
620
|
+
const milestoneTitle = state.activeMilestone.title;
|
|
621
|
+
|
|
622
|
+
// ── All milestones complete → New milestone ──────────────────────────
|
|
623
|
+
if (state.phase === "complete") {
|
|
624
|
+
const choice = await showNextAction(ctx as any, {
|
|
625
|
+
title: `Kata — ${milestoneId}: ${milestoneTitle}`,
|
|
626
|
+
summary: ["All milestones complete."],
|
|
627
|
+
actions: [
|
|
628
|
+
{
|
|
629
|
+
id: "new_milestone",
|
|
630
|
+
label: "Start new milestone",
|
|
631
|
+
description: "Define and plan the next milestone.",
|
|
632
|
+
recommended: true,
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: "status",
|
|
636
|
+
label: "View status",
|
|
637
|
+
description: "Review what was built.",
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
notYetMessage: "Run /kata when ready.",
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
if (choice === "new_milestone") {
|
|
644
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
645
|
+
const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
|
|
646
|
+
|
|
647
|
+
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId };
|
|
648
|
+
dispatchWorkflow(
|
|
649
|
+
pi,
|
|
650
|
+
buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath),
|
|
651
|
+
);
|
|
652
|
+
} else if (choice === "status") {
|
|
653
|
+
const { fireStatusViaCommand } = await import("./commands.js");
|
|
654
|
+
await fireStatusViaCommand(ctx);
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ── No active slice ──────────────────────────────────────────────────
|
|
660
|
+
if (!state.activeSlice) {
|
|
661
|
+
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
662
|
+
const hasRoadmap = !!(roadmapFile && (await loadFile(roadmapFile)));
|
|
663
|
+
|
|
664
|
+
if (!hasRoadmap) {
|
|
665
|
+
// No roadmap → discuss or plan
|
|
666
|
+
const contextFile = resolveMilestoneFile(
|
|
667
|
+
basePath,
|
|
668
|
+
milestoneId,
|
|
669
|
+
"CONTEXT",
|
|
670
|
+
);
|
|
671
|
+
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
672
|
+
|
|
673
|
+
const actions = [
|
|
674
|
+
{
|
|
675
|
+
id: "plan",
|
|
676
|
+
label: "Create roadmap",
|
|
677
|
+
description: hasContext
|
|
678
|
+
? "Context captured. Decompose into slices with a boundary map."
|
|
679
|
+
: "Decompose the milestone into slices with a boundary map.",
|
|
680
|
+
recommended: true,
|
|
681
|
+
},
|
|
682
|
+
...(!hasContext
|
|
683
|
+
? [
|
|
684
|
+
{
|
|
685
|
+
id: "discuss",
|
|
686
|
+
label: "Discuss first",
|
|
687
|
+
description: "Capture decisions on gray areas before planning.",
|
|
688
|
+
},
|
|
689
|
+
]
|
|
690
|
+
: []),
|
|
691
|
+
];
|
|
692
|
+
|
|
693
|
+
const choice = await showNextAction(ctx as any, {
|
|
694
|
+
title: `Kata — ${milestoneId}: ${milestoneTitle}`,
|
|
695
|
+
summary: [
|
|
696
|
+
hasContext
|
|
697
|
+
? "Context captured. Ready to create roadmap."
|
|
698
|
+
: "New milestone — no roadmap yet.",
|
|
699
|
+
],
|
|
700
|
+
actions,
|
|
701
|
+
notYetMessage: "Run /kata when ready.",
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
if (choice === "plan") {
|
|
705
|
+
dispatchWorkflow(
|
|
706
|
+
pi,
|
|
707
|
+
loadPrompt("guided-plan-milestone", {
|
|
708
|
+
milestoneId,
|
|
709
|
+
milestoneTitle,
|
|
710
|
+
}),
|
|
711
|
+
);
|
|
712
|
+
} else if (choice === "discuss") {
|
|
713
|
+
dispatchWorkflow(
|
|
714
|
+
pi,
|
|
715
|
+
loadPrompt("guided-discuss-milestone", {
|
|
716
|
+
milestoneId,
|
|
717
|
+
milestoneTitle,
|
|
718
|
+
}),
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
} else {
|
|
722
|
+
// Roadmap exists — either blocked or ready for auto
|
|
723
|
+
const actions = [
|
|
724
|
+
{
|
|
725
|
+
id: "auto",
|
|
726
|
+
label: "Go auto",
|
|
727
|
+
description:
|
|
728
|
+
"Execute everything automatically until milestone complete.",
|
|
729
|
+
recommended: true,
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
id: "status",
|
|
733
|
+
label: "View status",
|
|
734
|
+
description: "See milestone progress and blockers.",
|
|
735
|
+
},
|
|
736
|
+
];
|
|
737
|
+
|
|
738
|
+
const choice = await showNextAction(ctx as any, {
|
|
739
|
+
title: `Kata — ${milestoneId}: ${milestoneTitle}`,
|
|
740
|
+
summary: ["Roadmap exists. Ready to execute."],
|
|
741
|
+
actions,
|
|
742
|
+
notYetMessage: "Run /kata status for details.",
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
if (choice === "auto") {
|
|
746
|
+
await startAuto(ctx, pi, basePath, false);
|
|
747
|
+
} else if (choice === "status") {
|
|
748
|
+
const { fireStatusViaCommand } = await import("./commands.js");
|
|
749
|
+
await fireStatusViaCommand(ctx);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const sliceId = state.activeSlice.id;
|
|
756
|
+
const sliceTitle = state.activeSlice.title;
|
|
757
|
+
|
|
758
|
+
// ── Slice needs planning ─────────────────────────────────────────────
|
|
759
|
+
if (state.phase === "planning") {
|
|
760
|
+
const contextFile = resolveSliceFile(
|
|
761
|
+
basePath,
|
|
762
|
+
milestoneId,
|
|
763
|
+
sliceId,
|
|
764
|
+
"CONTEXT",
|
|
765
|
+
);
|
|
766
|
+
const researchFile = resolveSliceFile(
|
|
767
|
+
basePath,
|
|
768
|
+
milestoneId,
|
|
769
|
+
sliceId,
|
|
770
|
+
"RESEARCH",
|
|
771
|
+
);
|
|
772
|
+
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
773
|
+
const hasResearch = !!(researchFile && (await loadFile(researchFile)));
|
|
774
|
+
|
|
775
|
+
const actions = [
|
|
776
|
+
{
|
|
777
|
+
id: "plan",
|
|
778
|
+
label: `Plan ${sliceId}`,
|
|
779
|
+
description: `Decompose "${sliceTitle}" into tasks with must-haves.`,
|
|
780
|
+
recommended: true,
|
|
781
|
+
},
|
|
782
|
+
...(!hasContext
|
|
783
|
+
? [
|
|
784
|
+
{
|
|
785
|
+
id: "discuss",
|
|
786
|
+
label: `Discuss ${sliceId} first`,
|
|
787
|
+
description: "Capture context and decisions for this slice.",
|
|
788
|
+
},
|
|
789
|
+
]
|
|
790
|
+
: []),
|
|
791
|
+
...(!hasResearch
|
|
792
|
+
? [
|
|
793
|
+
{
|
|
794
|
+
id: "research",
|
|
795
|
+
label: `Research ${sliceId} first`,
|
|
796
|
+
description: "Scout codebase and relevant docs.",
|
|
797
|
+
},
|
|
798
|
+
]
|
|
799
|
+
: []),
|
|
800
|
+
{
|
|
801
|
+
id: "status",
|
|
802
|
+
label: "View status",
|
|
803
|
+
description: "See milestone progress.",
|
|
804
|
+
},
|
|
805
|
+
];
|
|
806
|
+
|
|
807
|
+
const summaryParts = [];
|
|
808
|
+
if (hasContext) summaryParts.push("context ✓");
|
|
809
|
+
if (hasResearch) summaryParts.push("research ✓");
|
|
810
|
+
const summaryLine =
|
|
811
|
+
summaryParts.length > 0
|
|
812
|
+
? `${sliceId}: ${sliceTitle} (${summaryParts.join(", ")})`
|
|
813
|
+
: `${sliceId}: ${sliceTitle} — ready for planning.`;
|
|
814
|
+
|
|
815
|
+
const choice = await showNextAction(ctx as any, {
|
|
816
|
+
title: `Kata — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
|
|
817
|
+
summary: [summaryLine],
|
|
818
|
+
actions,
|
|
819
|
+
notYetMessage: "Run /kata when ready.",
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
if (choice === "plan") {
|
|
823
|
+
dispatchWorkflow(
|
|
824
|
+
pi,
|
|
825
|
+
loadPrompt("guided-plan-slice", {
|
|
826
|
+
milestoneId,
|
|
827
|
+
sliceId,
|
|
828
|
+
sliceTitle,
|
|
829
|
+
}),
|
|
830
|
+
);
|
|
831
|
+
} else if (choice === "discuss") {
|
|
832
|
+
dispatchWorkflow(
|
|
833
|
+
pi,
|
|
834
|
+
await buildDiscussSlicePrompt(
|
|
835
|
+
milestoneId,
|
|
836
|
+
sliceId,
|
|
837
|
+
sliceTitle,
|
|
838
|
+
basePath,
|
|
839
|
+
),
|
|
840
|
+
);
|
|
841
|
+
} else if (choice === "research") {
|
|
842
|
+
dispatchWorkflow(
|
|
843
|
+
pi,
|
|
844
|
+
loadPrompt("guided-research-slice", {
|
|
845
|
+
milestoneId,
|
|
846
|
+
sliceId,
|
|
847
|
+
sliceTitle,
|
|
848
|
+
}),
|
|
849
|
+
);
|
|
850
|
+
} else if (choice === "status") {
|
|
851
|
+
const { fireStatusViaCommand } = await import("./commands.js");
|
|
852
|
+
await fireStatusViaCommand(ctx);
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// ── All tasks done → Complete slice ──────────────────────────────────
|
|
858
|
+
if (state.phase === "summarizing") {
|
|
859
|
+
const choice = await showNextAction(ctx as any, {
|
|
860
|
+
title: `Kata — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
|
|
861
|
+
summary: ["All tasks complete. Ready for slice summary."],
|
|
862
|
+
actions: [
|
|
863
|
+
{
|
|
864
|
+
id: "complete",
|
|
865
|
+
label: `Complete ${sliceId}`,
|
|
866
|
+
description:
|
|
867
|
+
"Write slice summary, UAT, mark done, and squash-merge to main.",
|
|
868
|
+
recommended: true,
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
id: "status",
|
|
872
|
+
label: "View status",
|
|
873
|
+
description: "Review tasks before completing.",
|
|
874
|
+
},
|
|
875
|
+
],
|
|
876
|
+
notYetMessage: "Run /kata when ready.",
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
if (choice === "complete") {
|
|
880
|
+
dispatchWorkflow(
|
|
881
|
+
pi,
|
|
882
|
+
loadPrompt("guided-complete-slice", {
|
|
883
|
+
milestoneId,
|
|
884
|
+
sliceId,
|
|
885
|
+
sliceTitle,
|
|
886
|
+
}),
|
|
887
|
+
);
|
|
888
|
+
} else if (choice === "status") {
|
|
889
|
+
const { fireStatusViaCommand } = await import("./commands.js");
|
|
890
|
+
await fireStatusViaCommand(ctx);
|
|
891
|
+
}
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// ── Active task → Execute ────────────────────────────────────────────
|
|
896
|
+
if (state.activeTask) {
|
|
897
|
+
const taskId = state.activeTask.id;
|
|
898
|
+
const taskTitle = state.activeTask.title;
|
|
899
|
+
|
|
900
|
+
const continueFile = resolveSliceFile(
|
|
901
|
+
basePath,
|
|
902
|
+
milestoneId,
|
|
903
|
+
sliceId,
|
|
904
|
+
"CONTINUE",
|
|
905
|
+
);
|
|
906
|
+
const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
907
|
+
const hasInterrupted =
|
|
908
|
+
!!(continueFile && (await loadFile(continueFile))) ||
|
|
909
|
+
!!(sDir && (await loadFile(join(sDir, "continue.md"))));
|
|
910
|
+
|
|
911
|
+
const choice = await showNextAction(ctx as any, {
|
|
912
|
+
title: `Kata — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
|
|
913
|
+
summary: [
|
|
914
|
+
hasInterrupted
|
|
915
|
+
? `Resuming: ${taskId} — ${taskTitle}`
|
|
916
|
+
: `Next: ${taskId} — ${taskTitle}`,
|
|
917
|
+
],
|
|
918
|
+
actions: [
|
|
919
|
+
{
|
|
920
|
+
id: "execute",
|
|
921
|
+
label: hasInterrupted ? `Resume ${taskId}` : `Execute ${taskId}`,
|
|
922
|
+
description: hasInterrupted
|
|
923
|
+
? "Continue from where you left off."
|
|
924
|
+
: `Start working on "${taskTitle}".`,
|
|
925
|
+
recommended: true,
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
id: "auto",
|
|
929
|
+
label: "Go auto",
|
|
930
|
+
description: "Execute this and all remaining tasks automatically.",
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
id: "status",
|
|
934
|
+
label: "View status",
|
|
935
|
+
description: "See slice progress before starting.",
|
|
936
|
+
},
|
|
937
|
+
],
|
|
938
|
+
notYetMessage: "Run /kata when ready.",
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
if (choice === "auto") {
|
|
942
|
+
await startAuto(ctx, pi, basePath, false);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (choice === "execute") {
|
|
947
|
+
if (hasInterrupted) {
|
|
948
|
+
dispatchWorkflow(
|
|
949
|
+
pi,
|
|
950
|
+
loadPrompt("guided-resume-task", {
|
|
951
|
+
milestoneId,
|
|
952
|
+
sliceId,
|
|
953
|
+
}),
|
|
954
|
+
);
|
|
955
|
+
} else {
|
|
956
|
+
dispatchWorkflow(
|
|
957
|
+
pi,
|
|
958
|
+
loadPrompt("guided-execute-task", {
|
|
959
|
+
milestoneId,
|
|
960
|
+
sliceId,
|
|
961
|
+
taskId,
|
|
962
|
+
taskTitle,
|
|
963
|
+
}),
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
} else if (choice === "status") {
|
|
967
|
+
const { fireStatusViaCommand } = await import("./commands.js");
|
|
968
|
+
await fireStatusViaCommand(ctx);
|
|
969
|
+
}
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// ── Fallback: show status ────────────────────────────────────────────
|
|
974
|
+
const { fireStatusViaCommand } = await import("./commands.js");
|
|
975
|
+
await fireStatusViaCommand(ctx);
|
|
976
|
+
}
|