@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,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
|
+
});
|