@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.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +56 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +95 -0
  9. package/dist/resource-loader.d.ts +18 -0
  10. package/dist/resource-loader.js +50 -0
  11. package/dist/wizard.d.ts +15 -0
  12. package/dist/wizard.js +159 -0
  13. package/package.json +50 -21
  14. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  15. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  16. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  17. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  18. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  19. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  20. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  21. package/pkg/package.json +8 -0
  22. package/scripts/postinstall.js +45 -0
  23. package/src/resources/AGENTS.md +108 -0
  24. package/src/resources/KATA-WORKFLOW.md +661 -0
  25. package/src/resources/agents/researcher.md +29 -0
  26. package/src/resources/agents/scout.md +56 -0
  27. package/src/resources/agents/worker.md +31 -0
  28. package/src/resources/extensions/ask-user-questions.ts +200 -0
  29. package/src/resources/extensions/bg-shell/index.ts +2758 -0
  30. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  31. package/src/resources/extensions/browser-tools/core.js +1057 -0
  32. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  33. package/src/resources/extensions/browser-tools/package.json +20 -0
  34. package/src/resources/extensions/context7/index.ts +428 -0
  35. package/src/resources/extensions/context7/package.json +11 -0
  36. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  37. package/src/resources/extensions/github/formatters.ts +207 -0
  38. package/src/resources/extensions/github/gh-api.ts +537 -0
  39. package/src/resources/extensions/github/index.ts +778 -0
  40. package/src/resources/extensions/kata/activity-log.ts +88 -0
  41. package/src/resources/extensions/kata/auto.ts +2786 -0
  42. package/src/resources/extensions/kata/commands.ts +355 -0
  43. package/src/resources/extensions/kata/crash-recovery.ts +85 -0
  44. package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
  45. package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
  46. package/src/resources/extensions/kata/doctor.ts +683 -0
  47. package/src/resources/extensions/kata/files.ts +730 -0
  48. package/src/resources/extensions/kata/gitignore.ts +165 -0
  49. package/src/resources/extensions/kata/guided-flow.ts +976 -0
  50. package/src/resources/extensions/kata/index.ts +556 -0
  51. package/src/resources/extensions/kata/metrics.ts +397 -0
  52. package/src/resources/extensions/kata/observability-validator.ts +408 -0
  53. package/src/resources/extensions/kata/package.json +11 -0
  54. package/src/resources/extensions/kata/paths.ts +346 -0
  55. package/src/resources/extensions/kata/preferences.ts +695 -0
  56. package/src/resources/extensions/kata/prompt-loader.ts +50 -0
  57. package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
  58. package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
  59. package/src/resources/extensions/kata/prompts/discuss.md +151 -0
  60. package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
  61. package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
  62. package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
  63. package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
  64. package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
  65. package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
  66. package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
  67. package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
  68. package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
  69. package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
  70. package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
  71. package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
  72. package/src/resources/extensions/kata/prompts/queue.md +85 -0
  73. package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
  74. package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
  75. package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
  76. package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
  77. package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
  78. package/src/resources/extensions/kata/prompts/system.md +341 -0
  79. package/src/resources/extensions/kata/session-forensics.ts +550 -0
  80. package/src/resources/extensions/kata/skill-discovery.ts +137 -0
  81. package/src/resources/extensions/kata/state.ts +509 -0
  82. package/src/resources/extensions/kata/templates/context.md +76 -0
  83. package/src/resources/extensions/kata/templates/decisions.md +8 -0
  84. package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
  85. package/src/resources/extensions/kata/templates/plan.md +133 -0
  86. package/src/resources/extensions/kata/templates/preferences.md +15 -0
  87. package/src/resources/extensions/kata/templates/project.md +31 -0
  88. package/src/resources/extensions/kata/templates/reassessment.md +28 -0
  89. package/src/resources/extensions/kata/templates/requirements.md +81 -0
  90. package/src/resources/extensions/kata/templates/research.md +46 -0
  91. package/src/resources/extensions/kata/templates/roadmap.md +118 -0
  92. package/src/resources/extensions/kata/templates/slice-context.md +58 -0
  93. package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
  94. package/src/resources/extensions/kata/templates/state.md +19 -0
  95. package/src/resources/extensions/kata/templates/task-plan.md +52 -0
  96. package/src/resources/extensions/kata/templates/task-summary.md +57 -0
  97. package/src/resources/extensions/kata/templates/uat.md +54 -0
  98. package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
  99. package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
  100. package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
  101. package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
  102. package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
  103. package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
  104. package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
  105. package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
  106. package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
  107. package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
  108. package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
  109. package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
  110. package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
  111. package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
  112. package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
  113. package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
  114. package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
  115. package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
  116. package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
  117. package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
  118. package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
  119. package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
  120. package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
  121. package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
  122. package/src/resources/extensions/kata/types.ts +159 -0
  123. package/src/resources/extensions/kata/unit-runtime.ts +163 -0
  124. package/src/resources/extensions/kata/workspace-index.ts +203 -0
  125. package/src/resources/extensions/kata/worktree.ts +182 -0
  126. package/src/resources/extensions/mac-tools/index.ts +852 -0
  127. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  128. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  129. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  130. package/src/resources/extensions/search-the-web/format.ts +258 -0
  131. package/src/resources/extensions/search-the-web/http.ts +238 -0
  132. package/src/resources/extensions/search-the-web/index.ts +68 -0
  133. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  134. package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
  135. package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
  136. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  137. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  138. package/src/resources/extensions/shared/interview-ui.ts +822 -0
  139. package/src/resources/extensions/shared/next-action-ui.ts +235 -0
  140. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  141. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  142. package/src/resources/extensions/shared/ui.ts +400 -0
  143. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  144. package/src/resources/extensions/slash-commands/audit.ts +92 -0
  145. package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
  146. package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
  147. package/src/resources/extensions/slash-commands/index.ts +12 -0
  148. package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
  149. package/src/resources/extensions/subagent/agents.ts +126 -0
  150. package/src/resources/extensions/subagent/index.ts +1293 -0
  151. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  152. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  153. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  154. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  155. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  156. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  157. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  158. package/src/resources/skills/swiftui/SKILL.md +208 -0
  159. package/src/resources/skills/swiftui/references/animations.md +921 -0
  160. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  161. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  162. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  163. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  164. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  165. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  166. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  167. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  168. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  169. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  170. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  171. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  172. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  173. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  174. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  175. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
  176. package/dist/commands/task.d.ts +0 -9
  177. package/dist/commands/task.d.ts.map +0 -1
  178. package/dist/commands/task.js +0 -129
  179. package/dist/commands/task.js.map +0 -1
  180. package/dist/commands/task.test.d.ts +0 -2
  181. package/dist/commands/task.test.d.ts.map +0 -1
  182. package/dist/commands/task.test.js +0 -169
  183. package/dist/commands/task.test.js.map +0 -1
  184. package/dist/e2e/task-e2e.test.d.ts +0 -2
  185. package/dist/e2e/task-e2e.test.d.ts.map +0 -1
  186. package/dist/e2e/task-e2e.test.js +0 -173
  187. package/dist/e2e/task-e2e.test.js.map +0 -1
  188. package/dist/index.d.ts +0 -3
  189. package/dist/index.d.ts.map +0 -1
  190. package/dist/index.js +0 -93
  191. package/dist/index.js.map +0 -1
  192. package/dist/slug.d.ts +0 -2
  193. package/dist/slug.d.ts.map +0 -1
  194. package/dist/slug.js +0 -12
  195. package/dist/slug.js.map +0 -1
  196. package/dist/slug.test.d.ts +0 -2
  197. package/dist/slug.test.d.ts.map +0 -1
  198. package/dist/slug.test.js +0 -32
  199. package/dist/slug.test.js.map +0 -1
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Kata Dashboard Overlay
3
+ *
4
+ * Full-screen overlay showing auto-mode progress: milestone/slice/task
5
+ * breakdown, current unit, completed units, timing, and activity log.
6
+ * Toggled with Ctrl+Alt+G or opened from /kata status.
7
+ */
8
+
9
+ import type { Theme } from "@mariozechner/pi-coding-agent";
10
+ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@mariozechner/pi-tui";
11
+ import { deriveState } from "./state.js";
12
+ import { loadFile, parseRoadmap, parsePlan } from "./files.js";
13
+ import { resolveMilestoneFile, resolveSliceFile } from "./paths.js";
14
+ import { getAutoDashboardData, type AutoDashboardData } from "./auto.js";
15
+ import {
16
+ getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice,
17
+ aggregateByModel, formatCost, formatTokenCount, formatCostProjection,
18
+ } from "./metrics.js";
19
+ import { loadEffectiveKataPreferences } from "./preferences.js";
20
+
21
+ function formatDuration(ms: number): string {
22
+ const s = Math.floor(ms / 1000);
23
+ if (s < 60) return `${s}s`;
24
+ const m = Math.floor(s / 60);
25
+ const rs = s % 60;
26
+ if (m < 60) return `${m}m ${rs}s`;
27
+ const h = Math.floor(m / 60);
28
+ const rm = m % 60;
29
+ return `${h}h ${rm}m`;
30
+ }
31
+
32
+ function unitLabel(type: string): string {
33
+ switch (type) {
34
+ case "research-milestone": return "Research";
35
+ case "plan-milestone": return "Plan";
36
+ case "research-slice": return "Research";
37
+ case "plan-slice": return "Plan";
38
+ case "execute-task": return "Execute";
39
+ case "complete-slice": return "Complete";
40
+ case "reassess-roadmap": return "Reassess";
41
+ default: return type;
42
+ }
43
+ }
44
+
45
+ function centerLine(content: string, width: number): string {
46
+ const vis = visibleWidth(content);
47
+ if (vis >= width) return truncateToWidth(content, width);
48
+ const leftPad = Math.floor((width - vis) / 2);
49
+ return " ".repeat(leftPad) + content;
50
+ }
51
+
52
+ function padRight(content: string, width: number): string {
53
+ const vis = visibleWidth(content);
54
+ return content + " ".repeat(Math.max(0, width - vis));
55
+ }
56
+
57
+ function joinColumns(left: string, right: string, width: number): string {
58
+ const leftW = visibleWidth(left);
59
+ const rightW = visibleWidth(right);
60
+ if (leftW + rightW + 2 > width) {
61
+ return truncateToWidth(`${left} ${right}`, width);
62
+ }
63
+ return left + " ".repeat(width - leftW - rightW) + right;
64
+ }
65
+
66
+ function fitColumns(parts: string[], width: number, separator = " "): string {
67
+ const filtered = parts.filter(Boolean);
68
+ if (filtered.length === 0) return "";
69
+ let result = filtered[0];
70
+ for (let i = 1; i < filtered.length; i++) {
71
+ const candidate = `${result}${separator}${filtered[i]}`;
72
+ if (visibleWidth(candidate) > width) break;
73
+ result = candidate;
74
+ }
75
+ return truncateToWidth(result, width);
76
+ }
77
+
78
+ export class KataDashboardOverlay {
79
+ private tui: { requestRender: () => void };
80
+ private theme: Theme;
81
+ private onClose: () => void;
82
+ private cachedWidth?: number;
83
+ private cachedLines?: string[];
84
+ private refreshTimer: ReturnType<typeof setInterval>;
85
+ private scrollOffset = 0;
86
+ private dashData: AutoDashboardData;
87
+ private milestoneData: MilestoneView | null = null;
88
+ private loading = true;
89
+
90
+ constructor(
91
+ tui: { requestRender: () => void },
92
+ theme: Theme,
93
+ onClose: () => void,
94
+ ) {
95
+ this.tui = tui;
96
+ this.theme = theme;
97
+ this.onClose = onClose;
98
+ this.dashData = getAutoDashboardData();
99
+
100
+ this.loadData().then(() => {
101
+ this.loading = false;
102
+ this.invalidate();
103
+ this.tui.requestRender();
104
+ });
105
+
106
+ this.refreshTimer = setInterval(() => {
107
+ this.dashData = getAutoDashboardData();
108
+ this.loadData().then(() => {
109
+ this.invalidate();
110
+ this.tui.requestRender();
111
+ });
112
+ }, 2000);
113
+ }
114
+
115
+ private async loadData(): Promise<void> {
116
+ const base = this.dashData.basePath || process.cwd();
117
+ try {
118
+ const state = await deriveState(base);
119
+ if (!state.activeMilestone) {
120
+ this.milestoneData = null;
121
+ return;
122
+ }
123
+
124
+ const mid = state.activeMilestone.id;
125
+ const view: MilestoneView = {
126
+ id: mid,
127
+ title: state.activeMilestone.title,
128
+ slices: [],
129
+ phase: state.phase,
130
+ progress: {
131
+ milestones: {
132
+ total: state.progress?.milestones.total ?? state.registry.length,
133
+ done: state.progress?.milestones.done ?? state.registry.filter(entry => entry.status === "complete").length,
134
+ },
135
+ },
136
+ };
137
+
138
+ const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
139
+ const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
140
+ if (roadmapContent) {
141
+ const roadmap = parseRoadmap(roadmapContent);
142
+ for (const s of roadmap.slices) {
143
+ const sliceView: SliceView = {
144
+ id: s.id,
145
+ title: s.title,
146
+ done: s.done,
147
+ risk: s.risk,
148
+ active: state.activeSlice?.id === s.id,
149
+ tasks: [],
150
+ };
151
+
152
+ if (sliceView.active) {
153
+ const planFile = resolveSliceFile(base, mid, s.id, "PLAN");
154
+ const planContent = planFile ? await loadFile(planFile) : null;
155
+ if (planContent) {
156
+ const plan = parsePlan(planContent);
157
+ sliceView.taskProgress = {
158
+ done: plan.tasks.filter(t => t.done).length,
159
+ total: plan.tasks.length,
160
+ };
161
+ for (const t of plan.tasks) {
162
+ sliceView.tasks.push({
163
+ id: t.id,
164
+ title: t.title,
165
+ done: t.done,
166
+ active: state.activeTask?.id === t.id,
167
+ });
168
+ }
169
+ }
170
+ }
171
+
172
+ view.slices.push(sliceView);
173
+ }
174
+ }
175
+
176
+ this.milestoneData = view;
177
+ } catch {
178
+ // Don't crash the overlay
179
+ }
180
+ }
181
+
182
+ handleInput(data: string): void {
183
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrlAlt("g"))) {
184
+ clearInterval(this.refreshTimer);
185
+ this.onClose();
186
+ return;
187
+ }
188
+
189
+ if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
190
+ this.scrollOffset++;
191
+ this.invalidate();
192
+ this.tui.requestRender();
193
+ return;
194
+ }
195
+
196
+ if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
197
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
198
+ this.invalidate();
199
+ this.tui.requestRender();
200
+ return;
201
+ }
202
+
203
+ if (data === "g") {
204
+ this.scrollOffset = 0;
205
+ this.invalidate();
206
+ this.tui.requestRender();
207
+ return;
208
+ }
209
+
210
+ if (data === "G") {
211
+ this.scrollOffset = 999;
212
+ this.invalidate();
213
+ this.tui.requestRender();
214
+ return;
215
+ }
216
+ }
217
+
218
+ render(width: number): string[] {
219
+ if (this.cachedLines && this.cachedWidth === width) {
220
+ return this.cachedLines;
221
+ }
222
+
223
+ const content = this.buildContentLines(width);
224
+ const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
225
+ const chromeHeight = 2;
226
+ const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
227
+ const maxScroll = Math.max(0, content.length - visibleContentRows);
228
+ this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
229
+ const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
230
+
231
+ const lines = this.wrapInBox(visibleContent, width);
232
+
233
+ this.cachedWidth = width;
234
+ this.cachedLines = lines;
235
+ return lines;
236
+ }
237
+
238
+ private wrapInBox(inner: string[], width: number): string[] {
239
+ const th = this.theme;
240
+ const border = (s: string) => th.fg("borderAccent", s);
241
+ const innerWidth = width - 4;
242
+ const lines: string[] = [];
243
+
244
+ lines.push(border("╭" + "─".repeat(width - 2) + "╮"));
245
+ for (const line of inner) {
246
+ const truncated = truncateToWidth(line, innerWidth);
247
+ const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
248
+ lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
249
+ }
250
+ lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
251
+ return lines;
252
+ }
253
+
254
+ private buildContentLines(width: number): string[] {
255
+ const th = this.theme;
256
+ const shellWidth = width - 4;
257
+ const contentWidth = Math.min(shellWidth, 128);
258
+ const sidePad = Math.max(0, Math.floor((shellWidth - contentWidth) / 2));
259
+ const leftMargin = " ".repeat(sidePad);
260
+ const lines: string[] = [];
261
+
262
+ const row = (content = ""): string => {
263
+ const truncated = truncateToWidth(content, contentWidth);
264
+ return leftMargin + padRight(truncated, contentWidth);
265
+ };
266
+ const blank = () => row("");
267
+ const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
268
+ const centered = (content: string) => row(centerLine(content, contentWidth));
269
+
270
+ const title = th.fg("accent", th.bold("Kata Dashboard"));
271
+ const status = this.dashData.active
272
+ ? `${Date.now() % 2000 < 1000 ? th.fg("success", "●") : th.fg("dim", "○")} ${th.fg("success", "AUTO")}`
273
+ : this.dashData.paused
274
+ ? th.fg("warning", "⏸ PAUSED")
275
+ : th.fg("dim", "idle");
276
+ const elapsed = th.fg("dim", formatDuration(this.dashData.elapsed));
277
+ lines.push(row(joinColumns(`${title} ${status}`, elapsed, contentWidth)));
278
+ lines.push(blank());
279
+
280
+ if (this.dashData.currentUnit) {
281
+ const cu = this.dashData.currentUnit;
282
+ const currentElapsed = th.fg("dim", formatDuration(Date.now() - cu.startedAt));
283
+ lines.push(row(joinColumns(
284
+ `${th.fg("text", "Now")}: ${th.fg("accent", unitLabel(cu.type))} ${th.fg("text", cu.id)}`,
285
+ currentElapsed,
286
+ contentWidth,
287
+ )));
288
+ lines.push(blank());
289
+ } else if (this.dashData.paused) {
290
+ lines.push(row(th.fg("dim", "/kata auto to resume")));
291
+ lines.push(blank());
292
+ } else {
293
+ lines.push(row(th.fg("dim", "No unit running · /kata auto to start")));
294
+ lines.push(blank());
295
+ }
296
+
297
+ if (this.loading) {
298
+ lines.push(centered(th.fg("dim", "Loading dashboard…")));
299
+ return lines;
300
+ }
301
+
302
+ if (this.milestoneData) {
303
+ const mv = this.milestoneData;
304
+ lines.push(row(th.fg("text", th.bold(`${mv.id}: ${mv.title}`))));
305
+ lines.push(blank());
306
+
307
+ const totalSlices = mv.slices.length;
308
+ const doneSlices = mv.slices.filter(s => s.done).length;
309
+ const totalMilestones = mv.progress.milestones.total;
310
+ const doneMilestones = mv.progress.milestones.done;
311
+ const activeSlice = mv.slices.find(s => s.active);
312
+
313
+ lines.push(blank());
314
+
315
+ if (activeSlice?.taskProgress) {
316
+ lines.push(row(this.renderProgressRow("Tasks", activeSlice.taskProgress.done, activeSlice.taskProgress.total, "accent", contentWidth)));
317
+ }
318
+ lines.push(row(this.renderProgressRow("Slices", doneSlices, totalSlices, "success", contentWidth)));
319
+ lines.push(row(this.renderProgressRow("Milestones", doneMilestones, totalMilestones, "warning", contentWidth)));
320
+
321
+ lines.push(blank());
322
+
323
+ for (const s of mv.slices) {
324
+ const icon = s.done ? th.fg("success", "✓")
325
+ : s.active ? th.fg("accent", "▸")
326
+ : th.fg("dim", "○");
327
+ const titleText = s.active ? th.fg("accent", `${s.id}: ${s.title}`)
328
+ : s.done ? th.fg("muted", `${s.id}: ${s.title}`)
329
+ : th.fg("dim", `${s.id}: ${s.title}`);
330
+ const risk = th.fg("dim", s.risk);
331
+ lines.push(row(joinColumns(` ${icon} ${titleText}`, risk, contentWidth)));
332
+
333
+ if (s.active && s.tasks.length > 0) {
334
+ for (const t of s.tasks) {
335
+ const tIcon = t.done ? th.fg("success", "✓")
336
+ : t.active ? th.fg("warning", "▸")
337
+ : th.fg("dim", "·");
338
+ const tTitle = t.active ? th.fg("warning", `${t.id}: ${t.title}`)
339
+ : t.done ? th.fg("muted", `${t.id}: ${t.title}`)
340
+ : th.fg("dim", `${t.id}: ${t.title}`);
341
+ lines.push(row(` ${tIcon} ${truncateToWidth(tTitle, contentWidth - 6)}`));
342
+ }
343
+ }
344
+ }
345
+ } else {
346
+ lines.push(centered(th.fg("dim", "No active milestone.")));
347
+ }
348
+
349
+ if (this.dashData.completedUnits.length > 0) {
350
+ lines.push(blank());
351
+ lines.push(hr());
352
+ lines.push(row(th.fg("text", th.bold("Completed"))));
353
+ lines.push(blank());
354
+
355
+ const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
356
+ for (const u of recent) {
357
+ const left = ` ${th.fg("success", "✓")} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
358
+ const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
359
+ lines.push(row(joinColumns(left, right, contentWidth)));
360
+ }
361
+
362
+ if (this.dashData.completedUnits.length > 10) {
363
+ lines.push(row(th.fg("dim", ` ...and ${this.dashData.completedUnits.length - 10} more`)));
364
+ }
365
+ }
366
+
367
+ const ledger = getLedger();
368
+ if (ledger && ledger.units.length > 0) {
369
+ const totals = getProjectTotals(ledger.units);
370
+
371
+ lines.push(blank());
372
+ lines.push(hr());
373
+ lines.push(row(th.fg("text", th.bold("Cost & Usage"))));
374
+ lines.push(blank());
375
+
376
+ lines.push(row(fitColumns([
377
+ `${th.fg("warning", formatCost(totals.cost))} total`,
378
+ `${th.fg("text", formatTokenCount(totals.tokens.total))} tokens`,
379
+ `${th.fg("text", String(totals.toolCalls))} tools`,
380
+ `${th.fg("text", String(totals.units))} units`,
381
+ ], contentWidth, ` ${th.fg("dim", "·")} `)));
382
+
383
+ lines.push(row(fitColumns([
384
+ `${th.fg("dim", "in:")} ${th.fg("text", formatTokenCount(totals.tokens.input))}`,
385
+ `${th.fg("dim", "out:")} ${th.fg("text", formatTokenCount(totals.tokens.output))}`,
386
+ `${th.fg("dim", "cache-r:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheRead))}`,
387
+ `${th.fg("dim", "cache-w:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheWrite))}`,
388
+ ], contentWidth, " ")));
389
+
390
+ const phases = aggregateByPhase(ledger.units);
391
+ if (phases.length > 0) {
392
+ lines.push(blank());
393
+ lines.push(row(th.fg("dim", "By Phase")));
394
+ for (const p of phases) {
395
+ const pct = totals.cost > 0 ? Math.round((p.cost / totals.cost) * 100) : 0;
396
+ const left = ` ${th.fg("text", p.phase.padEnd(14))}${th.fg("warning", formatCost(p.cost).padStart(8))}`;
397
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${formatTokenCount(p.tokens.total)} tok ${p.units} units`);
398
+ lines.push(row(joinColumns(left, right, contentWidth)));
399
+ }
400
+ }
401
+
402
+ const slices = aggregateBySlice(ledger.units);
403
+ if (slices.length > 0) {
404
+ lines.push(blank());
405
+ lines.push(row(th.fg("dim", "By Slice")));
406
+ for (const s of slices) {
407
+ const pct = totals.cost > 0 ? Math.round((s.cost / totals.cost) * 100) : 0;
408
+ const left = ` ${th.fg("text", s.sliceId.padEnd(14))}${th.fg("warning", formatCost(s.cost).padStart(8))}`;
409
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${formatTokenCount(s.tokens.total)} tok ${formatDuration(s.duration)}`);
410
+ lines.push(row(joinColumns(left, right, contentWidth)));
411
+ }
412
+ }
413
+
414
+ // Cost projection — only when active milestone data is available
415
+ if (this.milestoneData) {
416
+ const mv = this.milestoneData;
417
+ const msTotalSlices = mv.slices.length;
418
+ const msDoneSlices = mv.slices.filter(s => s.done).length;
419
+ const remainingCount = msTotalSlices - msDoneSlices;
420
+ const overlayPrefs = loadEffectiveKataPreferences()?.preferences;
421
+ const projLines = formatCostProjection(slices, remainingCount, overlayPrefs?.budget_ceiling);
422
+ if (projLines.length > 0) {
423
+ lines.push(blank());
424
+ for (const line of projLines) {
425
+ const colored = line.toLowerCase().includes('ceiling')
426
+ ? th.fg("warning", line)
427
+ : th.fg("dim", line);
428
+ lines.push(row(colored));
429
+ }
430
+ }
431
+ }
432
+
433
+ const models = aggregateByModel(ledger.units);
434
+ if (models.length > 1) {
435
+ lines.push(blank());
436
+ lines.push(row(th.fg("dim", "By Model")));
437
+ for (const m of models) {
438
+ const pct = totals.cost > 0 ? Math.round((m.cost / totals.cost) * 100) : 0;
439
+ const modelName = truncateToWidth(m.model, 38);
440
+ const left = ` ${th.fg("text", modelName.padEnd(38))}${th.fg("warning", formatCost(m.cost).padStart(8))}`;
441
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`);
442
+ lines.push(row(joinColumns(left, right, contentWidth)));
443
+ }
444
+ }
445
+
446
+ lines.push(blank());
447
+ lines.push(row(`${th.fg("dim", "avg/unit:")} ${th.fg("text", formatCost(totals.cost / totals.units))} ${th.fg("dim", "·")} ${th.fg("text", formatTokenCount(Math.round(totals.tokens.total / totals.units)))} tokens`));
448
+ }
449
+
450
+ lines.push(blank());
451
+ lines.push(hr());
452
+ lines.push(centered(th.fg("dim", "↑↓ scroll · g/G top/end · esc close")));
453
+
454
+ return lines;
455
+ }
456
+
457
+ private renderProgressRow(
458
+ label: string,
459
+ done: number,
460
+ total: number,
461
+ color: "success" | "accent" | "warning",
462
+ width: number,
463
+ ): string {
464
+ const th = this.theme;
465
+ const pct = total > 0 ? Math.round((done / total) * 100) : 0;
466
+ const labelWidth = 12;
467
+ const rightWidth = 14;
468
+ const gap = 2;
469
+ const labelText = truncateToWidth(label, labelWidth, "").padEnd(labelWidth);
470
+ const ratioText = `${done}/${total}`;
471
+ const rightText = `${String(pct).padStart(3)}% ${ratioText.padStart(rightWidth - 5)}`;
472
+ const barWidth = Math.max(12, width - labelWidth - rightWidth - gap * 2);
473
+ const filled = total > 0 ? Math.round((done / total) * barWidth) : 0;
474
+ const bar = th.fg(color, "█".repeat(filled)) + th.fg("dim", "░".repeat(Math.max(0, barWidth - filled)));
475
+ return `${th.fg("dim", labelText)}${" ".repeat(gap)}${bar}${" ".repeat(gap)}${th.fg("dim", rightText)}`;
476
+ }
477
+
478
+ invalidate(): void {
479
+ this.cachedWidth = undefined;
480
+ this.cachedLines = undefined;
481
+ }
482
+
483
+ dispose(): void {
484
+ clearInterval(this.refreshTimer);
485
+ }
486
+ }
487
+
488
+ interface MilestoneView {
489
+ id: string;
490
+ title: string;
491
+ slices: SliceView[];
492
+ phase: string;
493
+ progress: {
494
+ milestones: {
495
+ total: number;
496
+ done: number;
497
+ };
498
+ };
499
+ }
500
+
501
+ interface SliceView {
502
+ id: string;
503
+ title: string;
504
+ done: boolean;
505
+ risk: string;
506
+ active: boolean;
507
+ tasks: TaskView[];
508
+ taskProgress?: { done: number; total: number };
509
+ }
510
+
511
+ interface TaskView {
512
+ id: string;
513
+ title: string;
514
+ done: boolean;
515
+ active: boolean;
516
+ }
@@ -0,0 +1,103 @@
1
+ # Kata Preferences Reference
2
+
3
+ Full documentation for `~/.kata-cli/preferences.md` (global) and `.kata/preferences.md` (project).
4
+
5
+ ---
6
+
7
+ ## Notes
8
+
9
+ - Keep this skill-first.
10
+ - Prefer explicit skill names or absolute paths.
11
+ - Use absolute paths for personal/local skills when you want zero ambiguity.
12
+ - These preferences guide which skills Kata should load and follow; they do not override higher-priority instructions in the current conversation.
13
+
14
+ ---
15
+
16
+ ## Field Guide
17
+
18
+ - `version`: schema version. Start at `1`.
19
+
20
+ - `always_use_skills`: skills Kata should use whenever they are relevant.
21
+
22
+ - `prefer_skills`: soft defaults Kata should prefer when relevant.
23
+
24
+ - `avoid_skills`: skills Kata should avoid unless clearly needed.
25
+
26
+ - `skill_rules`: situational rules with a human-readable `when` trigger and one or more of `use`, `prefer`, or `avoid`.
27
+
28
+ - `custom_instructions`: extra durable instructions related to skill use.
29
+
30
+ - `models`: per-stage model selection for auto-mode. Keys: `research`, `planning`, `execution`, `completion`. Values: model IDs (e.g. `claude-sonnet-4-6`, `claude-opus-4-6`). Omit a key to use whatever model is currently active.
31
+
32
+ - `skill_discovery`: controls how Kata discovers and applies skills during auto-mode. Valid values:
33
+ - `auto` — skills are found and applied automatically without prompting.
34
+ - `suggest` — (default) skills are identified during research but not installed automatically.
35
+ - `off` — skill discovery is disabled entirely.
36
+
37
+ - `auto_supervisor`: configures the auto-mode supervisor that monitors agent progress and enforces timeouts. Keys:
38
+ - `model`: model ID to use for the supervisor process (defaults to the currently active model).
39
+ - `soft_timeout_minutes`: minutes before the supervisor issues a soft warning (default: 20).
40
+ - `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10).
41
+ - `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30).
42
+
43
+ ---
44
+
45
+ ## Best Practices
46
+
47
+ - Keep `always_use_skills` short.
48
+ - Use `skill_rules` for situational routing, not broad personality preferences.
49
+ - Prefer skill names for stable built-in skills.
50
+ - Prefer absolute paths for local personal skills.
51
+
52
+ ---
53
+
54
+ ## Models Example
55
+
56
+ ```yaml
57
+ ---
58
+ version: 1
59
+ models:
60
+ research: claude-sonnet-4-6
61
+ planning: claude-opus-4-6
62
+ execution: claude-sonnet-4-6
63
+ completion: claude-sonnet-4-6
64
+ ---
65
+ ```
66
+
67
+ Opus for planning (where architectural decisions matter most), Sonnet for everything else (faster, cheaper). Omit any key to use the currently selected model.
68
+
69
+ ---
70
+
71
+ ## Example Variations
72
+
73
+ **Minimal — always load a UAT skill and route Clerk tasks:**
74
+
75
+ ```yaml
76
+ ---
77
+ version: 1
78
+ always_use_skills:
79
+ - /Users/you/.claude/skills/verify-uat
80
+ skill_rules:
81
+ - when: finishing implementation and human judgment matters
82
+ use:
83
+ - /Users/you/.claude/skills/verify-uat
84
+ ---
85
+ ```
86
+
87
+ **Richer routing — prefer cleanup and authentication skills:**
88
+
89
+ ```yaml
90
+ ---
91
+ version: 1
92
+ prefer_skills:
93
+ - commit-ignore
94
+ skill_rules:
95
+ - when: task involves Clerk authentication
96
+ use:
97
+ - clerk
98
+ - clerk-setup
99
+ - when: the user is looking for installable capability rather than implementation
100
+ prefer:
101
+ - find-skills
102
+ ---
103
+ ```