@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,57 @@
1
+ ---
2
+ id: {{taskId}}
3
+ parent: {{sliceId}}
4
+ milestone: {{milestoneId}}
5
+ provides:
6
+ - {{whatThisTaskProvides}}
7
+ key_files:
8
+ - {{filePath}}
9
+ key_decisions:
10
+ - {{decision}}
11
+ patterns_established:
12
+ - {{pattern}}
13
+ observability_surfaces:
14
+ - {{status endpoint, structured log, persisted failure state, diagnostic command, or none}}
15
+ duration: {{duration}}
16
+ verification_result: passed
17
+ completed_at: {{date}}
18
+ # Set blocker_discovered: true only if execution revealed the remaining slice plan
19
+ # is fundamentally invalid (wrong API, missing capability, architectural mismatch).
20
+ # Do NOT set true for ordinary bugs, minor deviations, or fixable issues.
21
+ blocker_discovered: false
22
+ ---
23
+
24
+ # {{taskId}}: {{taskTitle}}
25
+
26
+ <!-- One-liner must say what actually shipped, not just that work completed.
27
+ Good: "Added retry-aware worker status logging"
28
+ Bad: "Implemented logging improvements" -->
29
+
30
+ **{{oneLiner}}**
31
+
32
+ ## What Happened
33
+
34
+ {{narrative}}
35
+
36
+ ## Verification
37
+
38
+ {{whatWasVerifiedAndHow — commands run, tests passed, behavior confirmed}}
39
+
40
+ ## Diagnostics
41
+
42
+ {{howToInspectWhatThisTaskBuiltLater — status surfaces, logs, error shapes, failure artifacts, or none}}
43
+
44
+ ## Deviations
45
+
46
+ <!-- Deviations are unplanned changes to the written task plan, not ordinary debugging during implementation. -->
47
+
48
+ {{deviationsFromPlan_OR_none}}
49
+
50
+ ## Known Issues
51
+
52
+ {{issuesDiscoveredButNotFixed_OR_none}}
53
+
54
+ ## Files Created/Modified
55
+
56
+ - `{{filePath}}` — {{description}}
57
+ - `{{filePath}}` — {{description}}
@@ -0,0 +1,54 @@
1
+ # {{sliceId}}: {{sliceTitle}} — UAT
2
+
3
+ **Milestone:** {{milestoneId}}
4
+ **Written:** {{date}}
5
+
6
+ ## UAT Type
7
+
8
+ - UAT mode: {{artifact-driven | live-runtime | human-experience | mixed}}
9
+ - Why this mode is sufficient: {{reason}}
10
+
11
+ ## Preconditions
12
+
13
+ {{whatMustBeTrueBeforeTesting — server running, data seeded, etc.}}
14
+
15
+ ## Smoke Test
16
+
17
+ {{oneQuickCheckThatConfirmsTheSliceBasicallyWorks}}
18
+
19
+ ## Test Cases
20
+
21
+ ### 1. {{testName}}
22
+
23
+ 1. {{step}}
24
+ 2. {{step}}
25
+ 3. **Expected:** {{expected}}
26
+
27
+ ### 2. {{testName}}
28
+
29
+ 1. {{step}}
30
+ 2. **Expected:** {{expected}}
31
+
32
+ ## Edge Cases
33
+
34
+ ### {{edgeCaseName}}
35
+
36
+ 1. {{step}}
37
+ 2. **Expected:** {{expected}}
38
+
39
+ ## Failure Signals
40
+
41
+ - {{whatWouldIndicateSomethingIsBroken — errors, missing UI, wrong data}}
42
+
43
+ ## Requirements Proved By This UAT
44
+
45
+ - {{requirementIdOr_none}} — {{what this UAT proves}}
46
+
47
+ ## Not Proven By This UAT
48
+
49
+ - {{what this UAT intentionally does not prove}}
50
+ - {{remaining live/runtime/operational gaps, if any}}
51
+
52
+ ## Notes for Tester
53
+
54
+ {{anythingTheHumanShouldKnow — known rough edges, things to ignore, areas needing gut check}}
@@ -0,0 +1,327 @@
1
+ // Tests for pruneActivityLogs — age-based activity log pruning with
2
+ // highest-seq preservation invariant — plus step-11 prompt text assertion.
3
+ //
4
+ // Sections:
5
+ // (a) Basic pruning: one old file deleted, two recent survive
6
+ // (b) Highest-seq preserved even when all files are old
7
+ // (c) retentionDays=0 boundary: all non-highest-seq deleted
8
+ // (d) No-op when all files are recent
9
+ // (e) Empty directory: no crash
10
+ // (f) All old files: only highest-seq survives
11
+ // (g) Single file: always preserved (it IS highest-seq)
12
+ // (h) Seq number is tie-breaker (010 beats 001 lexicographically and numerically)
13
+ // (i) Non-matching filenames ignored: notes.txt survives, no crash
14
+ // (j) Step-11 prompt text: "refresh current state if needed"
15
+
16
+ import { mkdtempSync, mkdirSync, readdirSync, rmSync, utimesSync, writeFileSync } from 'node:fs';
17
+ import { join, dirname } from 'node:path';
18
+ import { tmpdir } from 'node:os';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ import { pruneActivityLogs } from '../activity-log.ts';
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+
25
+ // ─── Assertion helpers ─────────────────────────────────────────────────────
26
+
27
+ let passed = 0;
28
+ let failed = 0;
29
+
30
+ function assert(condition: boolean, message: string): void {
31
+ if (condition) {
32
+ passed++;
33
+ } else {
34
+ failed++;
35
+ console.error(` FAIL: ${message}`);
36
+ }
37
+ }
38
+
39
+ function assertEq<T>(actual: T, expected: T, message: string): void {
40
+ if (JSON.stringify(actual) === JSON.stringify(expected)) {
41
+ passed++;
42
+ } else {
43
+ failed++;
44
+ console.error(` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
45
+ }
46
+ }
47
+
48
+ // ─── Fixture helpers ───────────────────────────────────────────────────────
49
+
50
+ let tmpDirs: string[] = [];
51
+
52
+ function createTmpActivityDir(): string {
53
+ const dir = mkdtempSync(join(tmpdir(), 'kata-prune-test-'));
54
+ tmpDirs.push(dir);
55
+ return dir;
56
+ }
57
+
58
+ function writeActivityFile(activityDir: string, seq: string, name: string): string {
59
+ mkdirSync(activityDir, { recursive: true });
60
+ const filePath = join(activityDir, `${seq}-${name}.jsonl`);
61
+ writeFileSync(filePath, `{"seq":${parseInt(seq, 10)},"name":"${name}"}\n`, 'utf-8');
62
+ return filePath;
63
+ }
64
+
65
+ /** Set mtime to daysAgo days in the past. */
66
+ function backdateFile(filePath: string, daysAgo: number): void {
67
+ const pastMs = Date.now() - daysAgo * 24 * 60 * 60 * 1000;
68
+ const pastDate = new Date(pastMs);
69
+ utimesSync(filePath, pastDate, pastDate);
70
+ }
71
+
72
+ function cleanup(): void {
73
+ for (const dir of tmpDirs) {
74
+ rmSync(dir, { recursive: true, force: true });
75
+ }
76
+ tmpDirs = [];
77
+ }
78
+
79
+ process.on('exit', cleanup);
80
+
81
+ // ─── Helper: get sorted filenames (basenames only) in a directory ──────────
82
+
83
+ function listFiles(dir: string): string[] {
84
+ return readdirSync(dir).sort();
85
+ }
86
+
87
+ // ═══════════════════════════════════════════════════════════════════════════
88
+ // Tests
89
+ // ═══════════════════════════════════════════════════════════════════════════
90
+
91
+ async function main(): Promise<void> {
92
+
93
+ // ─── (a) Basic pruning ────────────────────────────────────────────────────
94
+ console.log('\n── (a) Basic pruning: one old file deleted, two recent survive');
95
+
96
+ {
97
+ const dir = createTmpActivityDir();
98
+ const f001 = writeActivityFile(dir, '001', 'execute-task-M001-S01-T01');
99
+ const _f002 = writeActivityFile(dir, '002', 'execute-task-M001-S01-T02');
100
+ const _f003 = writeActivityFile(dir, '003', 'execute-task-M001-S01-T03');
101
+
102
+ backdateFile(f001, 40); // older than 30-day retention
103
+
104
+ pruneActivityLogs(dir, 30);
105
+
106
+ const remaining = listFiles(dir);
107
+ assert(
108
+ !remaining.includes('001-execute-task-M001-S01-T01.jsonl'),
109
+ '(a) file 001 deleted (40 days old, past 30-day threshold)',
110
+ );
111
+ assert(
112
+ remaining.includes('002-execute-task-M001-S01-T02.jsonl'),
113
+ '(a) file 002 survives (recent)',
114
+ );
115
+ assert(
116
+ remaining.includes('003-execute-task-M001-S01-T03.jsonl'),
117
+ '(a) file 003 survives (recent, also highest-seq)',
118
+ );
119
+ }
120
+
121
+ // ─── (b) Highest-seq preserved even when all files are old ───────────────
122
+ console.log('\n── (b) Highest-seq preserved even when all files are old');
123
+
124
+ {
125
+ const dir = createTmpActivityDir();
126
+ const f001 = writeActivityFile(dir, '001', 'execute-task-M001-S01-T01');
127
+ const f002 = writeActivityFile(dir, '002', 'execute-task-M001-S01-T02');
128
+ const f003 = writeActivityFile(dir, '003', 'execute-task-M001-S01-T03');
129
+
130
+ backdateFile(f001, 40);
131
+ backdateFile(f002, 40);
132
+ backdateFile(f003, 40); // all old, but 003 is highest-seq
133
+
134
+ pruneActivityLogs(dir, 30);
135
+
136
+ const remaining = listFiles(dir);
137
+ assertEq(remaining.length, 1, '(b) exactly 1 file survives when all are old');
138
+ assert(
139
+ remaining.includes('003-execute-task-M001-S01-T03.jsonl'),
140
+ '(b) highest-seq file (003) is the survivor',
141
+ );
142
+ }
143
+
144
+ // ─── (c) retentionDays=0 boundary ────────────────────────────────────────
145
+ console.log('\n── (c) retentionDays=0: all non-highest-seq deleted even if brand-new');
146
+
147
+ {
148
+ const dir = createTmpActivityDir();
149
+ // All files have mtime=now (freshly written — no backdating)
150
+ writeActivityFile(dir, '001', 'execute-task-M002-S01-T01');
151
+ writeActivityFile(dir, '002', 'execute-task-M002-S01-T02');
152
+ writeActivityFile(dir, '003', 'execute-task-M002-S01-T03');
153
+
154
+ pruneActivityLogs(dir, 0); // cutoff = now → everything is "expired"
155
+
156
+ const remaining = listFiles(dir);
157
+ assertEq(remaining.length, 1, '(c) retentionDays=0: exactly 1 file survives');
158
+ assert(
159
+ remaining.includes('003-execute-task-M002-S01-T03.jsonl'),
160
+ '(c) retentionDays=0: only highest-seq (003) survives',
161
+ );
162
+ }
163
+
164
+ // ─── (d) No-op when all files are recent ─────────────────────────────────
165
+ console.log('\n── (d) No-op when all files are recent');
166
+
167
+ {
168
+ const dir = createTmpActivityDir();
169
+ writeActivityFile(dir, '001', 'execute-task-M003-S01-T01');
170
+ writeActivityFile(dir, '002', 'execute-task-M003-S01-T02');
171
+ writeActivityFile(dir, '003', 'execute-task-M003-S01-T03');
172
+ // No backdating — all files are fresh
173
+
174
+ pruneActivityLogs(dir, 30);
175
+
176
+ const remaining = listFiles(dir);
177
+ assertEq(remaining.length, 3, '(d) all 3 files survive when all are recent');
178
+ }
179
+
180
+ // ─── (e) Empty directory: no crash ────────────────────────────────────────
181
+ console.log('\n── (e) Empty directory: no crash');
182
+
183
+ {
184
+ const dir = createTmpActivityDir();
185
+ // dir exists but is empty
186
+
187
+ let threw = false;
188
+ try {
189
+ pruneActivityLogs(dir, 30);
190
+ } catch {
191
+ threw = true;
192
+ }
193
+
194
+ assert(!threw, '(e) pruneActivityLogs does not throw on empty directory');
195
+ assert(
196
+ readdirSync(dir).length === 0,
197
+ '(e) directory still exists and is still empty after no-op',
198
+ );
199
+ }
200
+
201
+ // ─── (f) All old files: only highest-seq survives ─────────────────────────
202
+ console.log('\n── (f) All old files: only highest-seq survives');
203
+
204
+ {
205
+ const dir = createTmpActivityDir();
206
+ const f004 = writeActivityFile(dir, '004', 'execute-task-M004-S01-T01');
207
+ const f005 = writeActivityFile(dir, '005', 'execute-task-M004-S01-T02');
208
+ const f006 = writeActivityFile(dir, '006', 'execute-task-M004-S01-T03');
209
+
210
+ backdateFile(f004, 60);
211
+ backdateFile(f005, 60);
212
+ backdateFile(f006, 60);
213
+
214
+ pruneActivityLogs(dir, 30);
215
+
216
+ const remaining = listFiles(dir);
217
+ assertEq(remaining.length, 1, '(f) exactly 1 file survives when all are old');
218
+ assert(
219
+ remaining[0].startsWith('006-'),
220
+ '(f) the surviving file starts with 006 (highest-seq)',
221
+ );
222
+ }
223
+
224
+ // ─── (g) Single file: always preserved ────────────────────────────────────
225
+ console.log('\n── (g) Single file: always preserved (it IS highest-seq)');
226
+
227
+ {
228
+ const dir = createTmpActivityDir();
229
+ const f001 = writeActivityFile(dir, '001', 'execute-task-M005-S01-T01');
230
+ backdateFile(f001, 100); // very old
231
+
232
+ pruneActivityLogs(dir, 30);
233
+
234
+ const remaining = listFiles(dir);
235
+ assertEq(remaining.length, 1, '(g) single file survives even when very old (it is the highest-seq)');
236
+ assert(
237
+ remaining.includes('001-execute-task-M005-S01-T01.jsonl'),
238
+ '(g) the single file (001) is preserved',
239
+ );
240
+ }
241
+
242
+ // ─── (h) Seq tie-breaker: 010 is higher than 001 ─────────────────────────
243
+ console.log('\n── (h) Seq number tie-breaker: 010 beats 001 numerically');
244
+
245
+ {
246
+ const dir = createTmpActivityDir();
247
+ const f001 = writeActivityFile(dir, '001', 'execute-task-M006-S01-T01');
248
+ const f010 = writeActivityFile(dir, '010', 'execute-task-M006-S01-T10');
249
+
250
+ backdateFile(f001, 40);
251
+ backdateFile(f010, 40); // both old; 010 is numerically highest
252
+
253
+ pruneActivityLogs(dir, 30);
254
+
255
+ const remaining = listFiles(dir);
256
+ assertEq(remaining.length, 1, '(h) exactly 1 file survives');
257
+ assert(
258
+ remaining.includes('010-execute-task-M006-S01-T10.jsonl'),
259
+ '(h) seq 010 (numeric 10) survives over seq 001 (numeric 1)',
260
+ );
261
+ }
262
+
263
+ // ─── (i) Non-matching filenames ignored ───────────────────────────────────
264
+ console.log('\n── (i) Non-matching filenames ignored: notes.txt survives, no crash');
265
+
266
+ {
267
+ const dir = createTmpActivityDir();
268
+ const f001 = writeActivityFile(dir, '001', 'execute-task-M007-S01-T01');
269
+ const notesPath = join(dir, 'notes.txt');
270
+ writeFileSync(notesPath, 'some notes\n', 'utf-8');
271
+
272
+ backdateFile(f001, 40); // eligible for pruning
273
+ // notes.txt never gets a seq prefix → should be ignored by pruner
274
+
275
+ let threw = false;
276
+ try {
277
+ pruneActivityLogs(dir, 30);
278
+ } catch {
279
+ threw = true;
280
+ }
281
+
282
+ assert(!threw, '(i) no crash when non-matching file is present');
283
+
284
+ const remaining = listFiles(dir);
285
+ assert(
286
+ remaining.includes('notes.txt'),
287
+ '(i) notes.txt (non-matching filename) survives pruning unchanged',
288
+ );
289
+ // 001 is deleted (old, and notes.txt is not counted as seq-bearing so 001 is not "highest")
290
+ // But wait — 001 IS the only seq file, making it highest-seq → it survives
291
+ assert(
292
+ remaining.includes('001-execute-task-M007-S01-T01.jsonl'),
293
+ '(i) seq 001 survives (it is the highest-seq among seq files)',
294
+ );
295
+ }
296
+
297
+ // ─── (j) Step-11 prompt text assertion ────────────────────────────────────
298
+ console.log('\n── (j) Step-11 prompt text: "refresh current state if needed"');
299
+
300
+ {
301
+ const { readFileSync } = await import('node:fs');
302
+ const promptPath = join(__dirname, '..', 'prompts', 'complete-slice.md');
303
+ const content = readFileSync(promptPath, 'utf-8');
304
+
305
+ assert(
306
+ content.includes('refresh current state if needed'),
307
+ '(j) complete-slice.md step 11 contains "refresh current state if needed"',
308
+ );
309
+ }
310
+
311
+ // ═══════════════════════════════════════════════════════════════════════════
312
+ // Results
313
+ // ═══════════════════════════════════════════════════════════════════════════
314
+
315
+ console.log(`\n${'='.repeat(40)}`);
316
+ console.log(`Results: ${passed} passed, ${failed} failed`);
317
+ if (failed > 0) {
318
+ process.exit(1);
319
+ } else {
320
+ console.log('All tests passed ✓');
321
+ }
322
+ }
323
+
324
+ main().catch((error) => {
325
+ console.error(error);
326
+ process.exit(1);
327
+ });
@@ -0,0 +1,97 @@
1
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+
5
+ import {
6
+ runKataDoctor,
7
+ selectDoctorScope,
8
+ filterDoctorIssues,
9
+ } from "../doctor.js";
10
+
11
+ let passed = 0;
12
+ let failed = 0;
13
+
14
+ function assert(condition: boolean, message: string): void {
15
+ if (condition) {
16
+ passed++;
17
+ } else {
18
+ failed++;
19
+ console.error(` FAIL: ${message}`);
20
+ }
21
+ }
22
+
23
+ const tmpBase = mkdtempSync(join(tmpdir(), "kata-auto-preflight-test-"));
24
+ const kata = join(tmpBase, ".kata");
25
+
26
+ mkdirSync(join(kata, "milestones", "M001", "slices", "S01", "tasks"), {
27
+ recursive: true,
28
+ });
29
+ mkdirSync(join(kata, "milestones", "M009", "slices", "S01", "tasks"), {
30
+ recursive: true,
31
+ });
32
+
33
+ writeFileSync(
34
+ join(kata, "milestones", "M001", "M001-ROADMAP.md"),
35
+ `# M001: Historical\n\n## Slices\n- [x] **S01: Old Slice** \`risk:low\` \`depends:[]\`\n > After this: old done\n`,
36
+ );
37
+ writeFileSync(
38
+ join(kata, "milestones", "M001", "slices", "S01", "S01-PLAN.md"),
39
+ `# S01: Old Slice\n\n**Goal:** Old\n**Demo:** Old\n\n## Must-Haves\n- done\n\n## Tasks\n- [x] **T01: Old Task** \`est:5m\`\n done\n`,
40
+ );
41
+ writeFileSync(
42
+ join(kata, "milestones", "M001", "slices", "S01", "tasks", "T01-SUMMARY.md"),
43
+ `---\nid: T01\nparent: S01\nmilestone: M001\nprovides: []\nrequires: []\naffects: []\nkey_files: []\nkey_decisions: []\npatterns_established: []\nobservability_surfaces: []\ndrill_down_paths: []\nduration: 5m\nverification_result: passed\ncompleted_at: 2026-03-09T00:00:00Z\n---\n\n# T01: Old Task\n\n**Done**\n\n## What Happened\nDone.\n\n## Diagnostics\n- log\n`,
44
+ );
45
+ writeFileSync(
46
+ join(kata, "milestones", "M001", "slices", "S01", "S01-SUMMARY.md"),
47
+ `---\nid: S01\nparent: M001\nmilestone: M001\nprovides: []\nrequires: []\naffects: []\nkey_files: []\nkey_decisions: []\npatterns_established: []\nobservability_surfaces: []\ndrill_down_paths: []\nduration: 5m\nverification_result: passed\ncompleted_at: 2026-03-09T00:00:00Z\n---\n\n# S01: Old Slice\n\n**Done**\n\n## What Happened\nDone.\n\n## Verification\nDone.\n\n## Deviations\nNone\n\n## Known Limitations\nNone\n\n## Follow-ups\nNone\n\n## Files Created/Modified\n- \`x\` — x\n\n## Forward Intelligence\n\n### What the next slice should know\n- x\n\n### What's fragile\n- x\n\n### Authoritative diagnostics\n- x\n\n### What assumptions changed\n- x\n`,
48
+ );
49
+
50
+ writeFileSync(
51
+ join(kata, "milestones", "M001", "M001-SUMMARY.md"),
52
+ `---\nid: M001\nstatus: complete\ncompleted_at: 2026-03-09T00:00:00Z\n---\n\n# M001: Historical\n\nComplete.\n`,
53
+ );
54
+
55
+ writeFileSync(
56
+ join(kata, "milestones", "M009", "M009-ROADMAP.md"),
57
+ `# M009: Active\n\n## Slices\n- [ ] **S01: Active Slice** \`risk:low\` \`depends:[]\`\n > After this: active works\n`,
58
+ );
59
+ writeFileSync(
60
+ join(kata, "milestones", "M009", "slices", "S01", "S01-PLAN.md"),
61
+ `# S01: Active Slice\n\n**Goal:** Active\n**Demo:** Active\n\n## Must-Haves\n- done\n\n## Tasks\n- [ ] **T01: Active Task** \`est:5m\`\n todo\n`,
62
+ );
63
+
64
+ async function main(): Promise<void> {
65
+ const scope = await selectDoctorScope(tmpBase);
66
+ assert(
67
+ scope === "M009/S01",
68
+ "active scope selected instead of historical milestone",
69
+ );
70
+
71
+ const scopedReport = await runKataDoctor(tmpBase, { fix: false, scope });
72
+ const scopedBlocking = filterDoctorIssues(scopedReport.issues, {
73
+ scope,
74
+ includeWarnings: false,
75
+ });
76
+ assert(scopedBlocking.length === 0, "no blocking issues in active scope");
77
+
78
+ const historicalReport = await runKataDoctor(tmpBase, { fix: false });
79
+ const historicalWarnings = historicalReport.issues.filter(
80
+ (issue) =>
81
+ issue.unitId.startsWith("M001/S01") && issue.severity === "warning",
82
+ );
83
+ assert(
84
+ historicalWarnings.length > 0,
85
+ "full repo still contains historical warning drift",
86
+ );
87
+
88
+ rmSync(tmpBase, { recursive: true, force: true });
89
+
90
+ console.log(`Results: ${passed} passed, ${failed} failed`);
91
+ if (failed > 0) process.exit(1);
92
+ }
93
+
94
+ main().catch((error) => {
95
+ console.error(error);
96
+ process.exit(1);
97
+ });
@@ -0,0 +1,53 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, readFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { writeUnitRuntimeRecord, readUnitRuntimeRecord } from '../unit-runtime.ts';
7
+ import { resolveAutoSupervisorConfig } from '../preferences.ts';
8
+
9
+ test('resolveAutoSupervisorConfig provides safe timeout defaults', () => {
10
+ const supervisor = resolveAutoSupervisorConfig();
11
+ assert.equal(supervisor.soft_timeout_minutes, 20);
12
+ assert.equal(supervisor.idle_timeout_minutes, 10);
13
+ assert.equal(supervisor.hard_timeout_minutes, 30);
14
+ });
15
+
16
+ test('writeUnitRuntimeRecord persists progress and recovery metadata defaults', () => {
17
+ const base = mkdtempSync(join(tmpdir(), 'kata-auto-supervisor-'));
18
+ const startedAt = 1234567890;
19
+
20
+ writeUnitRuntimeRecord(base, 'plan-milestone', 'M010', startedAt, {
21
+ phase: 'dispatched',
22
+ lastProgressAt: startedAt,
23
+ progressCount: 1,
24
+ lastProgressKind: 'dispatch',
25
+ });
26
+
27
+ const runtime = readUnitRuntimeRecord(base, 'plan-milestone', 'M010');
28
+ assert.ok(runtime);
29
+ assert.equal(runtime.phase, 'dispatched');
30
+ assert.equal(runtime.lastProgressAt, startedAt);
31
+ assert.equal(runtime.progressCount, 1);
32
+ assert.equal(runtime.lastProgressKind, 'dispatch');
33
+ assert.equal(runtime.recoveryAttempts, 0);
34
+ });
35
+
36
+ test('writeUnitRuntimeRecord keeps explicit recovery attempt fields', () => {
37
+ const base = mkdtempSync(join(tmpdir(), 'kata-auto-supervisor-'));
38
+ const startedAt = 2234567890;
39
+
40
+ writeUnitRuntimeRecord(base, 'research-milestone', 'M011', startedAt, {
41
+ phase: 'timeout',
42
+ recoveryAttempts: 2,
43
+ lastRecoveryReason: 'idle',
44
+ lastProgressAt: startedAt + 50,
45
+ progressCount: 3,
46
+ lastProgressKind: 'recovery-retry',
47
+ });
48
+
49
+ const runtime = JSON.parse(readFileSync(join(base, '.kata/runtime/units/research-milestone-M011.json'), 'utf8'));
50
+ assert.equal(runtime.recoveryAttempts, 2);
51
+ assert.equal(runtime.lastRecoveryReason, 'idle');
52
+ assert.equal(runtime.lastProgressKind, 'recovery-retry');
53
+ });