claude-kanban 0.2.0 → 0.3.0
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/dist/bin/cli.js +192 -13
- package/dist/bin/cli.js.map +1 -1
- package/dist/server/index.js +107 -10
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
16
16
|
// src/server/services/executor.ts
|
|
17
17
|
import { spawn, execSync } from "child_process";
|
|
18
18
|
import { join as join4 } from "path";
|
|
19
|
-
import { writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync2, existsSync as existsSync2, appendFileSync as appendFileSync2, readFileSync as readFileSync4, rmSync } from "fs";
|
|
19
|
+
import { writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync2, existsSync as existsSync2, appendFileSync as appendFileSync2, readFileSync as readFileSync4, rmSync, symlinkSync } from "fs";
|
|
20
20
|
import { EventEmitter } from "events";
|
|
21
21
|
|
|
22
22
|
// src/server/services/project.ts
|
|
@@ -36,7 +36,39 @@ function detectPackageManager(projectPath) {
|
|
|
36
36
|
if (existsSync(join(projectPath, "bun.lockb"))) return "bun";
|
|
37
37
|
return "npm";
|
|
38
38
|
}
|
|
39
|
+
function detectProjectType(projectPath) {
|
|
40
|
+
if (existsSync(join(projectPath, "package.json"))) return "node";
|
|
41
|
+
if (existsSync(join(projectPath, "pyproject.toml")) || existsSync(join(projectPath, "requirements.txt")) || existsSync(join(projectPath, "setup.py"))) return "python";
|
|
42
|
+
if (existsSync(join(projectPath, "composer.json"))) return "php";
|
|
43
|
+
if (existsSync(join(projectPath, "Gemfile"))) return "ruby";
|
|
44
|
+
if (existsSync(join(projectPath, "go.mod"))) return "go";
|
|
45
|
+
if (existsSync(join(projectPath, "Cargo.toml"))) return "rust";
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|
|
39
48
|
function detectProjectCommands(projectPath) {
|
|
49
|
+
const projectType = detectProjectType(projectPath);
|
|
50
|
+
switch (projectType) {
|
|
51
|
+
case "node":
|
|
52
|
+
return detectNodeCommands(projectPath);
|
|
53
|
+
case "python":
|
|
54
|
+
return detectPythonCommands(projectPath);
|
|
55
|
+
case "php":
|
|
56
|
+
return detectPHPCommands(projectPath);
|
|
57
|
+
case "ruby":
|
|
58
|
+
return detectRubyCommands(projectPath);
|
|
59
|
+
case "go":
|
|
60
|
+
return detectGoCommands(projectPath);
|
|
61
|
+
case "rust":
|
|
62
|
+
return detectRustCommands(projectPath);
|
|
63
|
+
default:
|
|
64
|
+
return {
|
|
65
|
+
testCommand: "",
|
|
66
|
+
typecheckCommand: "",
|
|
67
|
+
buildCommand: ""
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function detectNodeCommands(projectPath) {
|
|
40
72
|
const pm = detectPackageManager(projectPath);
|
|
41
73
|
const run = pm === "npm" ? "npm run" : pm;
|
|
42
74
|
const packageJsonPath = join(projectPath, "package.json");
|
|
@@ -48,11 +80,61 @@ function detectProjectCommands(projectPath) {
|
|
|
48
80
|
} catch {
|
|
49
81
|
}
|
|
50
82
|
}
|
|
51
|
-
const testCommand = scripts.test ? `${run} test` :
|
|
52
|
-
const typecheckCommand = scripts.typecheck ? `${run} typecheck` : scripts["type-check"] ? `${run} type-check` : existsSync(join(projectPath, "tsconfig.json")) ? `${run === "npm run" ? "npx" : pm} tsc --noEmit` :
|
|
53
|
-
const buildCommand = scripts.build ? `${run} build` :
|
|
83
|
+
const testCommand = scripts.test ? `${run} test` : "";
|
|
84
|
+
const typecheckCommand = scripts.typecheck ? `${run} typecheck` : scripts["type-check"] ? `${run} type-check` : existsSync(join(projectPath, "tsconfig.json")) ? `${run === "npm run" ? "npx" : pm} tsc --noEmit` : "";
|
|
85
|
+
const buildCommand = scripts.build ? `${run} build` : "";
|
|
54
86
|
return { testCommand, typecheckCommand, buildCommand };
|
|
55
87
|
}
|
|
88
|
+
function detectPythonCommands(projectPath) {
|
|
89
|
+
const hasPytest = existsSync(join(projectPath, "pytest.ini")) || existsSync(join(projectPath, "pyproject.toml")) || existsSync(join(projectPath, "tests"));
|
|
90
|
+
const hasMypy = existsSync(join(projectPath, "mypy.ini")) || existsSync(join(projectPath, ".mypy.ini"));
|
|
91
|
+
return {
|
|
92
|
+
testCommand: hasPytest ? "pytest" : "",
|
|
93
|
+
typecheckCommand: hasMypy ? "mypy ." : "",
|
|
94
|
+
buildCommand: ""
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function detectPHPCommands(projectPath) {
|
|
98
|
+
const hasPhpunit = existsSync(join(projectPath, "phpunit.xml")) || existsSync(join(projectPath, "phpunit.xml.dist"));
|
|
99
|
+
const hasPest = existsSync(join(projectPath, "tests", "Pest.php"));
|
|
100
|
+
const hasPhpstan = existsSync(join(projectPath, "phpstan.neon")) || existsSync(join(projectPath, "phpstan.neon.dist"));
|
|
101
|
+
let testCommand = "";
|
|
102
|
+
if (hasPest) {
|
|
103
|
+
testCommand = "./vendor/bin/pest";
|
|
104
|
+
} else if (hasPhpunit) {
|
|
105
|
+
testCommand = "./vendor/bin/phpunit";
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
testCommand,
|
|
109
|
+
typecheckCommand: hasPhpstan ? "./vendor/bin/phpstan analyse" : "",
|
|
110
|
+
buildCommand: ""
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function detectRubyCommands(projectPath) {
|
|
114
|
+
const hasRspec = existsSync(join(projectPath, "spec")) || existsSync(join(projectPath, ".rspec"));
|
|
115
|
+
const hasMinitest = existsSync(join(projectPath, "test"));
|
|
116
|
+
return {
|
|
117
|
+
testCommand: hasRspec ? "bundle exec rspec" : hasMinitest ? "bundle exec rake test" : "",
|
|
118
|
+
typecheckCommand: "",
|
|
119
|
+
// Ruby doesn't have built-in typechecking (sorbet is optional)
|
|
120
|
+
buildCommand: ""
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function detectGoCommands(_projectPath) {
|
|
124
|
+
return {
|
|
125
|
+
testCommand: "go test ./...",
|
|
126
|
+
typecheckCommand: "go build ./...",
|
|
127
|
+
// Go's compiler is the typechecker
|
|
128
|
+
buildCommand: "go build"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function detectRustCommands(_projectPath) {
|
|
132
|
+
return {
|
|
133
|
+
testCommand: "cargo test",
|
|
134
|
+
typecheckCommand: "cargo check",
|
|
135
|
+
buildCommand: "cargo build"
|
|
136
|
+
};
|
|
137
|
+
}
|
|
56
138
|
function createInitialPRD(projectName) {
|
|
57
139
|
return {
|
|
58
140
|
version: "1.0",
|
|
@@ -494,6 +576,64 @@ var TaskExecutor = class extends EventEmitter {
|
|
|
494
576
|
if (!existsSync2(logPath)) return null;
|
|
495
577
|
return readFileSync4(logPath, "utf-8");
|
|
496
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* Format tool use for display in logs
|
|
581
|
+
*/
|
|
582
|
+
formatToolUse(toolName, input) {
|
|
583
|
+
const truncate = (str, maxLen = 80) => {
|
|
584
|
+
if (str.length <= maxLen) return str;
|
|
585
|
+
return str.slice(0, maxLen) + "...";
|
|
586
|
+
};
|
|
587
|
+
switch (toolName) {
|
|
588
|
+
case "Bash":
|
|
589
|
+
return `[Bash] $ ${truncate(String(input.command || ""))}
|
|
590
|
+
`;
|
|
591
|
+
case "Read":
|
|
592
|
+
return `[Read] ${input.file_path}
|
|
593
|
+
`;
|
|
594
|
+
case "Edit":
|
|
595
|
+
return `[Edit] ${input.file_path}
|
|
596
|
+
`;
|
|
597
|
+
case "Write":
|
|
598
|
+
return `[Write] ${input.file_path}
|
|
599
|
+
`;
|
|
600
|
+
case "Grep":
|
|
601
|
+
return `[Grep] "${truncate(String(input.pattern || ""))}" in ${input.path || "."}
|
|
602
|
+
`;
|
|
603
|
+
case "Glob":
|
|
604
|
+
return `[Glob] ${input.pattern} in ${input.path || "."}
|
|
605
|
+
`;
|
|
606
|
+
case "Task":
|
|
607
|
+
return `[Task] ${input.description || truncate(String(input.prompt || ""))}
|
|
608
|
+
`;
|
|
609
|
+
case "TodoWrite":
|
|
610
|
+
const todos = input.todos;
|
|
611
|
+
if (todos && Array.isArray(todos)) {
|
|
612
|
+
const summary = todos.map((t) => ` ${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u2192" : "\u25CB"} ${truncate(t.content, 60)}`).join("\n");
|
|
613
|
+
return `[TodoWrite]
|
|
614
|
+
${summary}
|
|
615
|
+
`;
|
|
616
|
+
}
|
|
617
|
+
return `[TodoWrite]
|
|
618
|
+
`;
|
|
619
|
+
case "WebFetch":
|
|
620
|
+
return `[WebFetch] ${input.url}
|
|
621
|
+
`;
|
|
622
|
+
case "WebSearch":
|
|
623
|
+
return `[WebSearch] "${truncate(String(input.query || ""))}"
|
|
624
|
+
`;
|
|
625
|
+
default:
|
|
626
|
+
const meaningfulParams = ["file_path", "path", "command", "query", "url", "pattern", "target"];
|
|
627
|
+
for (const param of meaningfulParams) {
|
|
628
|
+
if (input[param]) {
|
|
629
|
+
return `[${toolName}] ${truncate(String(input[param]))}
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return `[${toolName}]
|
|
634
|
+
`;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
497
637
|
/**
|
|
498
638
|
* Check if project is a git repository
|
|
499
639
|
*/
|
|
@@ -559,6 +699,7 @@ var TaskExecutor = class extends EventEmitter {
|
|
|
559
699
|
cwd: this.projectPath,
|
|
560
700
|
stdio: "pipe"
|
|
561
701
|
});
|
|
702
|
+
this.symlinkDependencies(worktreePath);
|
|
562
703
|
console.log(`[executor] Created worktree at ${worktreePath} on branch ${branchName}`);
|
|
563
704
|
return { worktreePath, branchName };
|
|
564
705
|
} catch (error) {
|
|
@@ -566,6 +707,38 @@ var TaskExecutor = class extends EventEmitter {
|
|
|
566
707
|
return null;
|
|
567
708
|
}
|
|
568
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* Symlink dependency directories from main project to worktree
|
|
712
|
+
*/
|
|
713
|
+
symlinkDependencies(worktreePath) {
|
|
714
|
+
const depDirs = [
|
|
715
|
+
"node_modules",
|
|
716
|
+
".pnpm",
|
|
717
|
+
// pnpm store
|
|
718
|
+
".yarn",
|
|
719
|
+
// yarn cache
|
|
720
|
+
"vendor",
|
|
721
|
+
// PHP/Ruby deps
|
|
722
|
+
"__pycache__",
|
|
723
|
+
// Python cache
|
|
724
|
+
".venv",
|
|
725
|
+
// Python virtual env
|
|
726
|
+
"venv"
|
|
727
|
+
// Python virtual env
|
|
728
|
+
];
|
|
729
|
+
for (const dir of depDirs) {
|
|
730
|
+
const sourcePath = join4(this.projectPath, dir);
|
|
731
|
+
const targetPath = join4(worktreePath, dir);
|
|
732
|
+
if (existsSync2(sourcePath) && !existsSync2(targetPath)) {
|
|
733
|
+
try {
|
|
734
|
+
symlinkSync(sourcePath, targetPath, "junction");
|
|
735
|
+
console.log(`[executor] Symlinked ${dir} to worktree`);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.log(`[executor] Could not symlink ${dir}:`, error);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
569
742
|
/**
|
|
570
743
|
* Remove a git worktree
|
|
571
744
|
*/
|
|
@@ -658,6 +831,17 @@ var TaskExecutor = class extends EventEmitter {
|
|
|
658
831
|
const stepsText = task.steps.length > 0 ? `
|
|
659
832
|
Verification steps:
|
|
660
833
|
${task.steps.map((s, i) => `${i + 1}. ${s}`).join("\n")}` : "";
|
|
834
|
+
const verifySteps = [];
|
|
835
|
+
if (config.project.typecheckCommand) {
|
|
836
|
+
verifySteps.push(`Run typecheck: ${config.project.typecheckCommand}`);
|
|
837
|
+
}
|
|
838
|
+
if (config.project.testCommand) {
|
|
839
|
+
verifySteps.push(`Run tests: ${config.project.testCommand}`);
|
|
840
|
+
}
|
|
841
|
+
const verifySection = verifySteps.length > 0 ? `2. Verify your work:
|
|
842
|
+
${verifySteps.map((s) => ` - ${s}`).join("\n")}
|
|
843
|
+
|
|
844
|
+
` : "";
|
|
661
845
|
return `You are an AI coding agent. Complete the following task:
|
|
662
846
|
|
|
663
847
|
## TASK
|
|
@@ -671,23 +855,19 @@ ${stepsText}
|
|
|
671
855
|
## INSTRUCTIONS
|
|
672
856
|
1. Implement this task as described above.
|
|
673
857
|
|
|
674
|
-
2.
|
|
675
|
-
- Run typecheck: ${config.project.typecheckCommand}
|
|
676
|
-
- Run tests: ${config.project.testCommand}
|
|
677
|
-
|
|
678
|
-
3. When complete, update the task in ${prdPath}:
|
|
858
|
+
${verifySection}${verifySteps.length > 0 ? "3" : "2"}. When complete, update the task in ${prdPath}:
|
|
679
859
|
- Find the task with id "${task.id}"
|
|
680
860
|
- Set "passes": true
|
|
681
861
|
- Set "status": "completed"
|
|
682
862
|
|
|
683
|
-
4. Document your work in ${progressPath}:
|
|
863
|
+
${verifySteps.length > 0 ? "4" : "3"}. Document your work in ${progressPath}:
|
|
684
864
|
- What you implemented and files changed
|
|
685
865
|
- Key decisions made and why
|
|
686
866
|
- Gotchas, edge cases, or tricky parts discovered
|
|
687
867
|
- Useful patterns or approaches that worked well
|
|
688
868
|
- Anything a future agent should know about this area of the codebase
|
|
689
869
|
|
|
690
|
-
5. Make a git commit with a descriptive message.
|
|
870
|
+
${verifySteps.length > 0 ? "5" : "4"}. Make a git commit with a descriptive message.
|
|
691
871
|
|
|
692
872
|
Focus only on this task. When successfully complete, output: <promise>COMPLETE</promise>`;
|
|
693
873
|
}
|
|
@@ -788,8 +968,7 @@ Focus only on this task. When successfully complete, output: <promise>COMPLETE</
|
|
|
788
968
|
if (block.type === "text") {
|
|
789
969
|
text += block.text;
|
|
790
970
|
} else if (block.type === "tool_use") {
|
|
791
|
-
text +=
|
|
792
|
-
`;
|
|
971
|
+
text += this.formatToolUse(block.name, block.input);
|
|
793
972
|
}
|
|
794
973
|
}
|
|
795
974
|
} else if (json.type === "content_block_delta" && json.delta?.text) {
|