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 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",
@@ -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. Verify your work:
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 += `[Tool: ${block.name}]
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) {