@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,203 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { loadFile, parsePlan, parseRoadmap } from "./files.ts";
|
|
5
|
+
import {
|
|
6
|
+
milestonesDir,
|
|
7
|
+
resolveMilestoneFile,
|
|
8
|
+
resolveSliceFile,
|
|
9
|
+
resolveSlicePath,
|
|
10
|
+
resolveTaskFile,
|
|
11
|
+
resolveTasksDir,
|
|
12
|
+
} from "./paths.ts";
|
|
13
|
+
import { deriveState } from "./state.ts";
|
|
14
|
+
import { type ValidationIssue, validateCompleteBoundary, validatePlanBoundary } from "./observability-validator.ts";
|
|
15
|
+
import { getSliceBranchName } from "./worktree.ts";
|
|
16
|
+
|
|
17
|
+
export interface WorkspaceTaskTarget {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
done: boolean;
|
|
21
|
+
planPath?: string;
|
|
22
|
+
summaryPath?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WorkspaceSliceTarget {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
done: boolean;
|
|
29
|
+
planPath?: string;
|
|
30
|
+
summaryPath?: string;
|
|
31
|
+
uatPath?: string;
|
|
32
|
+
tasksDir?: string;
|
|
33
|
+
branch?: string;
|
|
34
|
+
tasks: WorkspaceTaskTarget[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WorkspaceMilestoneTarget {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
roadmapPath?: string;
|
|
41
|
+
slices: WorkspaceSliceTarget[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface WorkspaceScopeTarget {
|
|
45
|
+
scope: string;
|
|
46
|
+
label: string;
|
|
47
|
+
kind: "project" | "milestone" | "slice" | "task";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface KataWorkspaceIndex {
|
|
51
|
+
milestones: WorkspaceMilestoneTarget[];
|
|
52
|
+
active: {
|
|
53
|
+
milestoneId?: string;
|
|
54
|
+
sliceId?: string;
|
|
55
|
+
taskId?: string;
|
|
56
|
+
phase: string;
|
|
57
|
+
};
|
|
58
|
+
scopes: WorkspaceScopeTarget[];
|
|
59
|
+
validationIssues: ValidationIssue[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findMilestoneIds(basePath: string): string[] {
|
|
63
|
+
try {
|
|
64
|
+
return readdirSync(milestonesDir(basePath), { withFileTypes: true })
|
|
65
|
+
.filter(entry => entry.isDirectory())
|
|
66
|
+
.map(entry => {
|
|
67
|
+
const match = entry.name.match(/^(M\d+)/);
|
|
68
|
+
return match ? match[1] : entry.name;
|
|
69
|
+
})
|
|
70
|
+
.sort();
|
|
71
|
+
} catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function titleFromRoadmapHeader(content: string, fallbackId: string): string {
|
|
77
|
+
const roadmap = parseRoadmap(content);
|
|
78
|
+
return roadmap.title.replace(/^M\d+[^:]*:\s*/, "") || fallbackId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function indexSlice(basePath: string, milestoneId: string, sliceId: string, fallbackTitle: string, done: boolean): Promise<WorkspaceSliceTarget> {
|
|
82
|
+
const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN") ?? undefined;
|
|
83
|
+
const summaryPath = resolveSliceFile(basePath, milestoneId, sliceId, "SUMMARY") ?? undefined;
|
|
84
|
+
const uatPath = resolveSliceFile(basePath, milestoneId, sliceId, "UAT") ?? undefined;
|
|
85
|
+
const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId) ?? undefined;
|
|
86
|
+
|
|
87
|
+
const tasks: WorkspaceTaskTarget[] = [];
|
|
88
|
+
let title = fallbackTitle;
|
|
89
|
+
|
|
90
|
+
if (planPath) {
|
|
91
|
+
const content = await loadFile(planPath);
|
|
92
|
+
if (content) {
|
|
93
|
+
const plan = parsePlan(content);
|
|
94
|
+
title = plan.title || fallbackTitle;
|
|
95
|
+
for (const task of plan.tasks) {
|
|
96
|
+
tasks.push({
|
|
97
|
+
id: task.id,
|
|
98
|
+
title: task.title,
|
|
99
|
+
done: task.done,
|
|
100
|
+
planPath: resolveTaskFile(basePath, milestoneId, sliceId, task.id, "PLAN") ?? undefined,
|
|
101
|
+
summaryPath: resolveTaskFile(basePath, milestoneId, sliceId, task.id, "SUMMARY") ?? undefined,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
id: sliceId,
|
|
109
|
+
title,
|
|
110
|
+
done,
|
|
111
|
+
planPath,
|
|
112
|
+
summaryPath,
|
|
113
|
+
uatPath,
|
|
114
|
+
tasksDir,
|
|
115
|
+
branch: getSliceBranchName(milestoneId, sliceId),
|
|
116
|
+
tasks,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function indexWorkspace(basePath: string): Promise<KataWorkspaceIndex> {
|
|
121
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
122
|
+
const milestones: WorkspaceMilestoneTarget[] = [];
|
|
123
|
+
const validationIssues: ValidationIssue[] = [];
|
|
124
|
+
|
|
125
|
+
for (const milestoneId of milestoneIds) {
|
|
126
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP") ?? undefined;
|
|
127
|
+
let title = milestoneId;
|
|
128
|
+
const slices: WorkspaceSliceTarget[] = [];
|
|
129
|
+
|
|
130
|
+
if (roadmapPath) {
|
|
131
|
+
const roadmapContent = await loadFile(roadmapPath);
|
|
132
|
+
if (roadmapContent) {
|
|
133
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
134
|
+
title = titleFromRoadmapHeader(roadmapContent, milestoneId);
|
|
135
|
+
for (const slice of roadmap.slices) {
|
|
136
|
+
const indexedSlice = await indexSlice(basePath, milestoneId, slice.id, slice.title, slice.done);
|
|
137
|
+
slices.push(indexedSlice);
|
|
138
|
+
validationIssues.push(...await validatePlanBoundary(basePath, milestoneId, slice.id));
|
|
139
|
+
validationIssues.push(...await validateCompleteBoundary(basePath, milestoneId, slice.id));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
milestones.push({ id: milestoneId, title, roadmapPath, slices });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const state = await deriveState(basePath);
|
|
148
|
+
const active = {
|
|
149
|
+
milestoneId: state.activeMilestone?.id,
|
|
150
|
+
sliceId: state.activeSlice?.id,
|
|
151
|
+
taskId: state.activeTask?.id,
|
|
152
|
+
phase: state.phase,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const scopes: WorkspaceScopeTarget[] = [{ scope: "project", label: "project", kind: "project" }];
|
|
156
|
+
for (const milestone of milestones) {
|
|
157
|
+
scopes.push({ scope: milestone.id, label: `${milestone.id}: ${milestone.title}`, kind: "milestone" });
|
|
158
|
+
for (const slice of milestone.slices) {
|
|
159
|
+
scopes.push({ scope: `${milestone.id}/${slice.id}`, label: `${milestone.id}/${slice.id}: ${slice.title}`, kind: "slice" });
|
|
160
|
+
for (const task of slice.tasks) {
|
|
161
|
+
scopes.push({
|
|
162
|
+
scope: `${milestone.id}/${slice.id}/${task.id}`,
|
|
163
|
+
label: `${milestone.id}/${slice.id}/${task.id}: ${task.title}`,
|
|
164
|
+
kind: "task",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { milestones, active, scopes, validationIssues };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function listDoctorScopeSuggestions(basePath: string): Promise<Array<{ value: string; label: string }>> {
|
|
174
|
+
const index = await indexWorkspace(basePath);
|
|
175
|
+
const activeSliceScope = index.active.milestoneId && index.active.sliceId
|
|
176
|
+
? `${index.active.milestoneId}/${index.active.sliceId}`
|
|
177
|
+
: null;
|
|
178
|
+
|
|
179
|
+
const ordered = [...index.scopes].filter(scope => scope.kind !== "project");
|
|
180
|
+
ordered.sort((a, b) => {
|
|
181
|
+
if (activeSliceScope && a.scope === activeSliceScope) return -1;
|
|
182
|
+
if (activeSliceScope && b.scope === activeSliceScope) return 1;
|
|
183
|
+
return a.scope.localeCompare(b.scope);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return ordered.map(scope => ({ value: scope.scope, label: scope.label }));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function getSuggestedNextCommands(basePath: string): Promise<string[]> {
|
|
190
|
+
const index = await indexWorkspace(basePath);
|
|
191
|
+
const scope = index.active.milestoneId && index.active.sliceId
|
|
192
|
+
? `${index.active.milestoneId}/${index.active.sliceId}`
|
|
193
|
+
: index.active.milestoneId;
|
|
194
|
+
|
|
195
|
+
const commands = new Set<string>();
|
|
196
|
+
if (index.active.phase === "planning") commands.add("/kata");
|
|
197
|
+
if (index.active.phase === "executing" || index.active.phase === "summarizing") commands.add("/kata auto");
|
|
198
|
+
if (scope) commands.add(`/kata doctor ${scope}`);
|
|
199
|
+
if (scope) commands.add(`/kata doctor fix ${scope}`);
|
|
200
|
+
if (index.validationIssues.length > 0 && scope) commands.add(`/kata doctor audit ${scope}`);
|
|
201
|
+
commands.add("/kata status");
|
|
202
|
+
return [...commands];
|
|
203
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kata Slice Branch Management
|
|
3
|
+
*
|
|
4
|
+
* Simple branch-per-slice workflow. No worktrees, no registry.
|
|
5
|
+
* Runtime state (metrics, activity, lock, STATE.md) is gitignored
|
|
6
|
+
* so branch switches are clean.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. ensureSliceBranch() — create + checkout slice branch
|
|
10
|
+
* 2. agent does work, commits
|
|
11
|
+
* 3. mergeSliceToMain() — checkout main, squash-merge, delete branch
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
export interface MergeSliceResult {
|
|
18
|
+
branch: string;
|
|
19
|
+
mergedCommitMessage: string;
|
|
20
|
+
deletedBranch: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runGit(basePath: string, args: string[], options: { allowFailure?: boolean } = {}): string {
|
|
24
|
+
try {
|
|
25
|
+
return execSync(`git ${args.join(" ")}`, {
|
|
26
|
+
cwd: basePath,
|
|
27
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
}).trim();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (options.allowFailure) return "";
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
throw new Error(`git ${args.join(" ")} failed in ${basePath}: ${message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getSliceBranchName(milestoneId: string, sliceId: string): string {
|
|
38
|
+
return `kata/${milestoneId}/${sliceId}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getMainBranch(basePath: string): string {
|
|
42
|
+
const symbolic = runGit(basePath, ["symbolic-ref", "refs/remotes/origin/HEAD"], { allowFailure: true });
|
|
43
|
+
if (symbolic) {
|
|
44
|
+
const match = symbolic.match(/refs\/remotes\/origin\/(.+)$/);
|
|
45
|
+
if (match) return match[1]!;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mainExists = runGit(basePath, ["show-ref", "--verify", "refs/heads/main"], { allowFailure: true });
|
|
49
|
+
if (mainExists) return "main";
|
|
50
|
+
|
|
51
|
+
const masterExists = runGit(basePath, ["show-ref", "--verify", "refs/heads/master"], { allowFailure: true });
|
|
52
|
+
if (masterExists) return "master";
|
|
53
|
+
|
|
54
|
+
return runGit(basePath, ["branch", "--show-current"]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getCurrentBranch(basePath: string): string {
|
|
58
|
+
return runGit(basePath, ["branch", "--show-current"]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function branchExists(basePath: string, branch: string): boolean {
|
|
62
|
+
try {
|
|
63
|
+
runGit(basePath, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Ensure the slice branch exists and is checked out.
|
|
72
|
+
* Creates the branch from main if it doesn't exist.
|
|
73
|
+
* Returns true if the branch was newly created.
|
|
74
|
+
*/
|
|
75
|
+
export function ensureSliceBranch(basePath: string, milestoneId: string, sliceId: string): boolean {
|
|
76
|
+
const branch = getSliceBranchName(milestoneId, sliceId);
|
|
77
|
+
const current = getCurrentBranch(basePath);
|
|
78
|
+
|
|
79
|
+
if (current === branch) return false;
|
|
80
|
+
|
|
81
|
+
const mainBranch = getMainBranch(basePath);
|
|
82
|
+
let created = false;
|
|
83
|
+
|
|
84
|
+
if (!branchExists(basePath, branch)) {
|
|
85
|
+
runGit(basePath, ["branch", branch, mainBranch]);
|
|
86
|
+
created = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
runGit(basePath, ["checkout", branch]);
|
|
90
|
+
return created;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Auto-commit any dirty files in the current working tree.
|
|
95
|
+
* Returns the commit message used, or null if already clean.
|
|
96
|
+
*/
|
|
97
|
+
export function autoCommitCurrentBranch(
|
|
98
|
+
basePath: string, unitType: string, unitId: string,
|
|
99
|
+
): string | null {
|
|
100
|
+
const status = runGit(basePath, ["status", "--short"]);
|
|
101
|
+
if (!status.trim()) return null;
|
|
102
|
+
|
|
103
|
+
runGit(basePath, ["add", "-A"]);
|
|
104
|
+
|
|
105
|
+
const staged = runGit(basePath, ["diff", "--cached", "--stat"]);
|
|
106
|
+
if (!staged.trim()) return null;
|
|
107
|
+
|
|
108
|
+
const message = `chore(${unitId}): auto-commit after ${unitType}`;
|
|
109
|
+
runGit(basePath, ["commit", "-m", JSON.stringify(message)]);
|
|
110
|
+
return message;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Switch to main, auto-committing any dirty files on the current branch first.
|
|
115
|
+
*/
|
|
116
|
+
export function switchToMain(basePath: string): void {
|
|
117
|
+
const mainBranch = getMainBranch(basePath);
|
|
118
|
+
const current = getCurrentBranch(basePath);
|
|
119
|
+
if (current === mainBranch) return;
|
|
120
|
+
|
|
121
|
+
// Auto-commit if dirty
|
|
122
|
+
autoCommitCurrentBranch(basePath, "pre-switch", current);
|
|
123
|
+
|
|
124
|
+
runGit(basePath, ["checkout", mainBranch]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Squash-merge a completed slice branch to main.
|
|
129
|
+
* Expects to already be on main (call switchToMain first).
|
|
130
|
+
* Deletes the branch after merge.
|
|
131
|
+
*/
|
|
132
|
+
export function mergeSliceToMain(
|
|
133
|
+
basePath: string, milestoneId: string, sliceId: string, sliceTitle: string,
|
|
134
|
+
): MergeSliceResult {
|
|
135
|
+
const branch = getSliceBranchName(milestoneId, sliceId);
|
|
136
|
+
const mainBranch = getMainBranch(basePath);
|
|
137
|
+
|
|
138
|
+
const current = getCurrentBranch(basePath);
|
|
139
|
+
if (current !== mainBranch) {
|
|
140
|
+
throw new Error(`Expected to be on ${mainBranch}, found ${current}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!branchExists(basePath, branch)) {
|
|
144
|
+
throw new Error(`Slice branch ${branch} does not exist`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ahead = runGit(basePath, ["rev-list", "--count", `${mainBranch}..${branch}`]);
|
|
148
|
+
if (Number(ahead) <= 0) {
|
|
149
|
+
throw new Error(`Slice branch ${branch} has no commits ahead of ${mainBranch}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
runGit(basePath, ["merge", "--squash", branch]);
|
|
153
|
+
const mergedCommitMessage = `feat(${milestoneId}/${sliceId}): ${sliceTitle}`;
|
|
154
|
+
runGit(basePath, ["commit", "-m", JSON.stringify(mergedCommitMessage)]);
|
|
155
|
+
runGit(basePath, ["branch", "-D", branch]);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
branch,
|
|
159
|
+
mergedCommitMessage,
|
|
160
|
+
deletedBranch: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if we're currently on a slice branch (not main).
|
|
166
|
+
*/
|
|
167
|
+
export function isOnSliceBranch(basePath: string): boolean {
|
|
168
|
+
const current = getCurrentBranch(basePath);
|
|
169
|
+
return current.startsWith("kata/");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get the active slice branch name, or null if on main.
|
|
174
|
+
*/
|
|
175
|
+
export function getActiveSliceBranch(basePath: string): string | null {
|
|
176
|
+
try {
|
|
177
|
+
const current = getCurrentBranch(basePath);
|
|
178
|
+
return current.startsWith("kata/") ? current : null;
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|