claudeboard 1.5.0 → 2.0.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.
@@ -1,161 +1,203 @@
1
- import { callClaudeJSON, callClaude } from "./claude-api.js";
2
- import { startTask, completeTask, failTask, addLog } from "./board-client.js";
3
- import { readFile, writeFile, listFiles, projectTree, readFilesAsContext } from "../tools/filesystem.js";
4
- import { runCommand } from "../tools/terminal.js";
5
- import path from "path";
6
-
7
- const SYSTEM = `You are a senior React Native / Expo developer. You write clean, production-ready code.
1
+ /**
2
+ * Developer Agent powered by Claude Agent SDK
3
+ *
4
+ * Instead of calling the Claude API and manually writing files,
5
+ * this agent spawns Claude Code directly in the project directory.
6
+ * Claude Code can read the full codebase, run commands, install deps,
7
+ * fix errors, and iterate all autonomously.
8
+ *
9
+ * Requires: npm install -g @anthropic-ai/claude-code
10
+ */
8
11
 
9
- Rules:
10
- - Write complete, working code never use placeholders or TODOs
11
- - Always use TypeScript when the project uses it
12
- - Follow the existing code style and patterns in the project
13
- - Add console.log statements for key operations so QA can verify functionality
14
- - Add error handling with meaningful error messages
15
- - When creating screens, make them visually polished — proper spacing, colors, typography
16
- - Use the existing navigation structure — never change routing patterns mid-project
17
- - Import only packages that are already installed or that you explicitly install first
12
+ import { query } from "@anthropic-ai/claude-agent-sdk";
13
+ import { startTask, completeTask, failTask, addLog } from "./board-client.js";
18
14
 
19
- When you need to create or modify files, respond with a JSON array of file operations.`;
15
+ // Tools Claude Code can use full developer access
16
+ const DEVELOPER_TOOLS = [
17
+ "Read", // Read any file in the project
18
+ "Write", // Create new files
19
+ "Edit", // Edit existing files (surgical, line-level)
20
+ "MultiEdit", // Edit multiple files in one shot
21
+ "Bash", // Run npm install, npx expo install, tsc, etc.
22
+ "Glob", // Find files by pattern (e.g. "**/*.tsx")
23
+ "Grep", // Search file contents
24
+ "TodoWrite", // Let Claude track its own subtasks
25
+ "TodoRead", // Read its own task list
26
+ ];
27
+
28
+ const DEV_RULES = `
29
+ You are a senior React Native / Expo developer working as part of an autonomous engineering team.
30
+
31
+ RULES:
32
+ - Write complete, production-ready code — no placeholders, no TODOs
33
+ - Use TypeScript if the project uses it
34
+ - Follow the existing code style and folder structure (read existing files first)
35
+ - Install packages with: npx expo install <package>
36
+ - After writing files, run: npx tsc --noEmit — fix any TypeScript errors
37
+ - If you hit an error, read it and fix it — iterate until it works
38
+ - Do NOT ask questions. Do NOT ask for confirmation. Make your best judgment.
39
+ - When fully done, print EXACTLY this line: TASK_COMPLETE: <one sentence summary>
40
+ `;
20
41
 
21
42
  export async function runDeveloperAgent(task, projectPath, techStack, retryContext = null) {
22
- console.log(` 💻 Developer working on: ${task.title}`);
23
- await startTask(task.id, `Starting implementation: ${task.title}`);
24
-
25
- const MAX_RETRIES = 3;
26
- let attempt = 0;
27
- let lastError = null;
28
-
29
- while (attempt < MAX_RETRIES) {
30
- attempt++;
31
- if (attempt > 1) {
32
- console.log(` 🔄 Retry ${attempt}/${MAX_RETRIES} for: ${task.title}`);
33
- await addLog(task.id, `Retry ${attempt}: fixing ${lastError}`, "progress");
34
- }
35
-
36
- try {
37
- // Gather context
38
- const tree = projectTree(projectPath);
39
- const relevantFiles = getRelevantFiles(task, projectPath);
40
- const fileContext = readFilesAsContext(relevantFiles, projectPath);
41
-
42
- const retryNote = retryContext
43
- ? `\n\nPREVIOUS ATTEMPT FAILED:\n${retryContext}\n\nFix these issues.`
44
- : "";
45
-
46
- const lastErrorNote = lastError
47
- ? `\n\nLAST ERROR: ${lastError}\nFix this specifically.`
48
- : "";
49
-
50
- const prompt = `
51
- Task: ${task.title}
52
- Description: ${task.description}
53
-
54
- Project structure:
55
- ${tree}
56
-
57
- Relevant existing files:
58
- ${fileContext || "No existing files yet — this may be a fresh setup task."}
59
-
60
- Tech stack: ${JSON.stringify(techStack || {}, null, 2)}
61
- ${retryNote}${lastErrorNote}
62
-
63
- Respond with JSON:
64
- {
65
- "plan": "Brief explanation of your approach",
66
- "installPackages": ["pkg1", "pkg2"],
67
- "files": [
68
- {
69
- "path": "relative/path/from/project/root.tsx",
70
- "action": "create|modify|delete",
71
- "content": "complete file content here"
72
- }
73
- ],
74
- "commands": ["any post-file commands to run, e.g. npx expo install package"],
75
- "verificationSteps": "What to check to verify this works"
76
- }`;
77
-
78
- const result = await callClaudeJSON(SYSTEM, prompt);
79
-
80
- // Install packages first
81
- if (result.installPackages?.length > 0) {
82
- const pkgs = result.installPackages.join(" ");
83
- await addLog(task.id, `Installing: ${pkgs}`, "progress");
84
- const install = await runCommand(`npx expo install ${pkgs}`, projectPath, 120000);
85
- if (install.exitCode !== 0) {
86
- // Try npm install as fallback
87
- await runCommand(`npm install ${pkgs}`, projectPath, 120000);
43
+ console.log(` 🤖 Claude Code working on: ${task.title}`);
44
+ await startTask(task.id, `Claude Code starting: ${task.title}`);
45
+
46
+ const retryNote = retryContext
47
+ ? `\n\n⚠️ RETRY — Previous attempt failed or QA flagged issues:\n${retryContext}\n\nFix these specific issues.`
48
+ : "";
49
+
50
+ const techNote = techStack && Object.keys(techStack).length > 0
51
+ ? `\nKnown tech stack: ${JSON.stringify(techStack)}`
52
+ : "";
53
+
54
+ const prompt = `## Task to implement
55
+
56
+ **Title:** ${task.title}
57
+
58
+ **Description:**
59
+ ${task.description || "No additional description provided."}
60
+ ${techNote}
61
+ ${retryNote}
62
+
63
+ ## Steps
64
+ 1. Explore the project structure to understand existing patterns
65
+ 2. Implement the task completely
66
+ 3. Install any missing dependencies with: npx expo install <pkg>
67
+ 4. Run: npx tsc --noEmit and fix any errors
68
+ 5. When done, print: TASK_COMPLETE: <summary>`;
69
+
70
+ try {
71
+ let toolCallCount = 0;
72
+ let completed = false;
73
+ let summary = "";
74
+
75
+ for await (const message of query({
76
+ prompt,
77
+ options: {
78
+ cwd: projectPath,
79
+ model: "claude-sonnet-4-20250514",
80
+ permissionMode: "bypassPermissions",
81
+ allowedTools: DEVELOPER_TOOLS,
82
+ maxTurns: 80,
83
+ systemPrompt: {
84
+ type: "preset",
85
+ preset: "claude_code",
86
+ append: DEV_RULES,
87
+ },
88
+ env: {
89
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
90
+ },
91
+ },
92
+ })) {
93
+
94
+ // ── Assistant messages ─────────────────────────────────────────────────
95
+ if (message.type === "assistant") {
96
+ for (const block of message.message.content) {
97
+
98
+ if (block.type === "text" && block.text?.trim()) {
99
+ const text = block.text.trim();
100
+
101
+ // Detect completion signal
102
+ if (text.includes("TASK_COMPLETE:")) {
103
+ completed = true;
104
+ summary = text.split("TASK_COMPLETE:")[1]?.trim().split("\n")[0] || task.title;
105
+ }
106
+
107
+ // Log meaningful progress
108
+ if (text.length > 20) {
109
+ const preview = text.split("\n")[0].slice(0, 120);
110
+ await addLog(task.id, preview, "progress");
111
+ console.log(` ${preview}`);
112
+ }
113
+ }
114
+
115
+ if (block.type === "tool_use") {
116
+ toolCallCount++;
117
+ const log = formatToolLog(block);
118
+ if (log) {
119
+ await addLog(task.id, log, "progress");
120
+ console.log(` 🔧 ${log}`);
121
+ }
122
+ }
88
123
  }
89
124
  }
90
125
 
91
- // Write files
92
- for (const file of result.files || []) {
93
- const fullPath = path.join(projectPath, file.path);
94
- if (file.action === "delete") {
95
- try { fs.unlinkSync(fullPath); } catch {}
126
+ // ── Result ─────────────────────────────────────────────────────────────
127
+ if (message.type === "result") {
128
+ if (message.subtype === "success") {
129
+ if (!completed && message.result?.includes("TASK_COMPLETE:")) {
130
+ completed = true;
131
+ summary = message.result.split("TASK_COMPLETE:")[1]?.trim().split("\n")[0] || task.title;
132
+ }
133
+
134
+ const finalSummary = summary || `Implemented: ${task.title}`;
135
+ await completeTask(task.id, `✓ ${finalSummary} (${toolCallCount} tool calls)`);
136
+ console.log(` ✓ Done: ${task.title} — ${toolCallCount} tool calls`);
137
+ return { success: true, summary: finalSummary };
138
+
139
+ } else if (message.subtype === "error_max_turns") {
140
+ if (completed) {
141
+ await completeTask(task.id, `✓ ${summary} (hit turn limit)`);
142
+ return { success: true, summary };
143
+ }
144
+ const err = "Reached max turns without completing — increase maxTurns or break task into smaller pieces";
145
+ await failTask(task.id, err);
146
+ return { success: false, error: err };
147
+
96
148
  } else {
97
- writeFile(fullPath, file.content);
98
- await addLog(task.id, `${file.action}: ${file.path}`, "progress");
149
+ const err = message.result || "Claude Code returned an error";
150
+ await failTask(task.id, err.slice(0, 500));
151
+ return { success: false, error: err };
99
152
  }
100
153
  }
101
154
 
102
- // Run any additional commands
103
- for (const cmd of result.commands || []) {
104
- await addLog(task.id, `Running: ${cmd}`, "progress");
105
- await runCommand(cmd, projectPath, 60000);
106
- }
107
-
108
- // Verify it compiles
109
- const typecheck = await runCommand("npx tsc --noEmit 2>&1 | head -20", projectPath, 30000);
110
- const hasTSErrors = typecheck.stdout.includes("error TS");
111
-
112
- if (hasTSErrors) {
113
- lastError = `TypeScript errors:\n${typecheck.stdout}`;
114
- continue; // retry
155
+ // ── System init ────────────────────────────────────────────────────────
156
+ if (message.type === "system" && message.subtype === "init") {
157
+ const sessionShort = message.session_id?.slice(0, 8) || "?";
158
+ console.log(` 📡 Session ${sessionShort} | ${message.tools?.length || 0} tools | ${message.model}`);
159
+ await addLog(task.id, `Claude Code session ${sessionShort} in ${projectPath}`, "start");
115
160
  }
161
+ }
116
162
 
117
- // Check for obvious JS syntax errors
118
- const buildCheck = await runCommand(
119
- "npx expo export --platform web --output-dir /tmp/cb-check 2>&1 | tail -10",
120
- projectPath,
121
- 90000
122
- );
163
+ // Generator ended without a result message
164
+ if (completed) {
165
+ await completeTask(task.id, `✓ ${summary}`);
166
+ return { success: true, summary };
167
+ }
123
168
 
124
- if (buildCheck.stdout.includes("ERROR") || buildCheck.stdout.includes("Cannot find")) {
125
- lastError = `Build error:\n${buildCheck.stdout}`;
126
- continue; // retry
127
- }
169
+ const err = "Session ended without result";
170
+ await failTask(task.id, err);
171
+ return { success: false, error: err };
128
172
 
129
- await completeTask(task.id, `✓ Implemented: ${result.plan}`);
130
- console.log(` ✓ Done: ${task.title}`);
131
- return { success: true, files: result.files, verificationSteps: result.verificationSteps };
173
+ } catch (err) {
174
+ const msg = err.message || String(err);
175
+ console.error(` ✗ Error:`, msg.slice(0, 200));
132
176
 
133
- } catch (err) {
134
- lastError = err.message;
135
- console.error(` ✗ Error on attempt ${attempt}:`, err.message);
177
+ // Missing CLI — helpful message
178
+ if (msg.includes("CLINotFoundError") || msg.includes("ENOENT") || msg.includes("claude-code")) {
179
+ const hint = "Claude Code CLI not installed. Run: npm install -g @anthropic-ai/claude-code";
180
+ await failTask(task.id, hint);
181
+ console.error(`\n ⚠️ ${hint}\n`);
182
+ return { success: false, error: hint };
136
183
  }
137
- }
138
184
 
139
- await failTask(task.id, `Failed after ${MAX_RETRIES} attempts. Last error: ${lastError}`);
140
- return { success: false, error: lastError };
185
+ await failTask(task.id, msg.slice(0, 500));
186
+ return { success: false, error: msg };
187
+ }
141
188
  }
142
189
 
143
- /**
144
- * Get files relevant to this task based on keywords
145
- */
146
- function getRelevantFiles(task, projectPath) {
147
- const keywords = task.title.toLowerCase().split(" ");
148
- const allFiles = listFiles(projectPath, [".ts", ".tsx", ".js", ".jsx", ".json"]);
149
-
150
- // Always include key config files
151
- const alwaysInclude = ["package.json", "app.json", "tsconfig.json", "babel.config.js"];
152
- const result = allFiles.filter((f) => {
153
- const rel = path.relative(projectPath, f);
154
- if (alwaysInclude.some((name) => rel.endsWith(name))) return true;
155
- if (rel.includes("app/") && rel.endsWith("_layout")) return true;
156
- // Include files that match task keywords
157
- return keywords.some((kw) => kw.length > 4 && rel.toLowerCase().includes(kw));
158
- });
159
-
160
- return result.slice(0, 15); // Max 15 files for context
190
+ function formatToolLog(block) {
191
+ const { name, input = {} } = block;
192
+ switch (name) {
193
+ case "Read": return `Read: ${input.file_path || input.path || ""}`;
194
+ case "Write": return `Write: ${input.file_path || input.path || ""}`;
195
+ case "Edit":
196
+ case "MultiEdit": return `Edit: ${input.file_path || input.path || ""}`;
197
+ case "Bash": return `Run: ${(input.command || "").slice(0, 80)}`;
198
+ case "Glob": return `Glob: ${input.pattern || ""}`;
199
+ case "Grep": return `Grep: "${(input.pattern || "").slice(0, 40)}"`;
200
+ case "TodoWrite": return `Planning subtasks...`;
201
+ default: return null;
202
+ }
161
203
  }
package/bin/cli.js CHANGED
@@ -151,6 +151,21 @@ program
151
151
  if (opts.restart) console.log(chalk.yellow(` Mode → FORCE RESTART\n`));
152
152
  else console.log(chalk.dim(` Mode → auto-resume if tasks exist\n`));
153
153
 
154
+ // ── Verify Claude Code CLI is installed ───────────────────────────────────
155
+ const { execSync } = await import("child_process");
156
+ try {
157
+ execSync("claude --version", { stdio: "pipe" });
158
+ const version = execSync("claude --version", { stdio: "pipe" }).toString().trim();
159
+ console.log(chalk.dim(` Claude Code → ${version}\n`));
160
+ } catch {
161
+ console.log(chalk.red("\n ✗ Claude Code CLI not found!\n"));
162
+ console.log(chalk.yellow(" The developer agent requires Claude Code to be installed:"));
163
+ console.log(chalk.bold(" npm install -g @anthropic-ai/claude-code\n"));
164
+ console.log(chalk.dim(" Then authenticate:"));
165
+ console.log(chalk.bold(" claude\n"));
166
+ process.exit(1);
167
+ }
168
+
154
169
  const { runOrchestrator } = await import("../agents/orchestrator.js");
155
170
 
156
171
  await runOrchestrator({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeboard",
3
- "version": "1.5.0",
3
+ "version": "2.0.0",
4
4
  "description": "AI engineering team — from PRD to working mobile app, autonomously",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "README.md"
16
16
  ],
17
17
  "dependencies": {
18
+ "@anthropic-ai/claude-agent-sdk": "latest",
18
19
  "@supabase/supabase-js": "^2.43.1",
19
20
  "chalk": "^5.3.0",
20
21
  "commander": "^12.0.0",