claude-kanban 0.2.1 → 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 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` : 'echo "No test command configured"';
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` : 'echo "No typecheck command configured"';
53
- const buildCommand = scripts.build ? `${run} build` : 'echo "No build command configured"';
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",
@@ -617,6 +699,7 @@ ${summary}
617
699
  cwd: this.projectPath,
618
700
  stdio: "pipe"
619
701
  });
702
+ this.symlinkDependencies(worktreePath);
620
703
  console.log(`[executor] Created worktree at ${worktreePath} on branch ${branchName}`);
621
704
  return { worktreePath, branchName };
622
705
  } catch (error) {
@@ -624,6 +707,38 @@ ${summary}
624
707
  return null;
625
708
  }
626
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
+ }
627
742
  /**
628
743
  * Remove a git worktree
629
744
  */
@@ -716,6 +831,17 @@ ${summary}
716
831
  const stepsText = task.steps.length > 0 ? `
717
832
  Verification steps:
718
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
+ ` : "";
719
845
  return `You are an AI coding agent. Complete the following task:
720
846
 
721
847
  ## TASK
@@ -729,23 +855,19 @@ ${stepsText}
729
855
  ## INSTRUCTIONS
730
856
  1. Implement this task as described above.
731
857
 
732
- 2. Verify your work:
733
- - Run typecheck: ${config.project.typecheckCommand}
734
- - Run tests: ${config.project.testCommand}
735
-
736
- 3. When complete, update the task in ${prdPath}:
858
+ ${verifySection}${verifySteps.length > 0 ? "3" : "2"}. When complete, update the task in ${prdPath}:
737
859
  - Find the task with id "${task.id}"
738
860
  - Set "passes": true
739
861
  - Set "status": "completed"
740
862
 
741
- 4. Document your work in ${progressPath}:
863
+ ${verifySteps.length > 0 ? "4" : "3"}. Document your work in ${progressPath}:
742
864
  - What you implemented and files changed
743
865
  - Key decisions made and why
744
866
  - Gotchas, edge cases, or tricky parts discovered
745
867
  - Useful patterns or approaches that worked well
746
868
  - Anything a future agent should know about this area of the codebase
747
869
 
748
- 5. Make a git commit with a descriptive message.
870
+ ${verifySteps.length > 0 ? "5" : "4"}. Make a git commit with a descriptive message.
749
871
 
750
872
  Focus only on this task. When successfully complete, output: <promise>COMPLETE</promise>`;
751
873
  }