claudeboard 2.1.0 → 2.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.
Files changed (2) hide show
  1. package/agents/developer.js +62 -69
  2. package/package.json +1 -1
@@ -1,10 +1,7 @@
1
1
  /**
2
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.
3
+ * Replaces the old API-based developer with Claude Code running directly
4
+ * in the project directory full codebase access, real shell commands.
8
5
  *
9
6
  * Requires: npm install -g @anthropic-ai/claude-code
10
7
  */
@@ -13,74 +10,83 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
13
10
  import { startTask, completeTask, failTask, addLog } from "./board-client.js";
14
11
  import { execSync } from "child_process";
15
12
 
16
- // Resolve the global `claude` binary path at startup
13
+ // ── Resolve the global `claude` binary path at startup ────────────────────────
17
14
  function resolveClaudePath() {
18
- // 1. Explicit override via env var
19
15
  if (process.env.CLAUDE_CODE_PATH) return process.env.CLAUDE_CODE_PATH;
20
-
21
- // 2. Ask the shell where `claude` lives (works after: npm install -g @anthropic-ai/claude-code)
22
- try {
23
- return execSync("which claude", { stdio: "pipe" }).toString().trim();
24
- } catch {}
25
-
26
- // 3. Common Homebrew / nvm / fnm paths
27
- const candidates = [
16
+ try { return execSync("which claude", { stdio: "pipe" }).toString().trim(); } catch {}
17
+ for (const p of [
28
18
  "/opt/homebrew/bin/claude",
29
19
  "/usr/local/bin/claude",
30
20
  `${process.env.HOME}/.nvm/versions/node/current/bin/claude`,
31
21
  `${process.env.HOME}/.npm-global/bin/claude`,
32
- ];
33
- for (const p of candidates) {
22
+ ]) {
34
23
  try { execSync(`test -f "${p}"`, { stdio: "pipe" }); return p; } catch {}
35
24
  }
25
+ return null;
26
+ }
36
27
 
37
- return null; // will surface as CLINotFoundError
28
+ // ── Build a full PATH so node/npm/npx are found inside the subprocess ─────────
29
+ // Exit code 127 = "command not found" — happens when PATH is stripped for global pkgs
30
+ function buildEnv() {
31
+ let nodeBinDir = "";
32
+ try { nodeBinDir = execSync("dirname $(which node)", { stdio: "pipe" }).toString().trim(); } catch {}
33
+
34
+ const pathParts = [
35
+ process.env.PATH || "",
36
+ nodeBinDir,
37
+ "/opt/homebrew/bin",
38
+ "/opt/homebrew/sbin",
39
+ "/usr/local/bin",
40
+ "/usr/bin",
41
+ "/bin",
42
+ `${process.env.HOME}/.npm-global/bin`,
43
+ `${process.env.HOME}/.nvm/versions/node/current/bin`,
44
+ ].filter(Boolean);
45
+
46
+ const fullPath = [...new Set(pathParts.join(":").split(":"))].join(":");
47
+ return { ...process.env, PATH: fullPath, HOME: process.env.HOME };
38
48
  }
39
49
 
40
50
  const CLAUDE_PATH = resolveClaudePath();
41
51
 
42
- // Tools Claude Code can use — full developer access
52
+ // ── Tools Claude Code can use ─────────────────────────────────────────────────
43
53
  const DEVELOPER_TOOLS = [
44
- "Read", // Read any file in the project
45
- "Write", // Create new files
46
- "Edit", // Edit existing files (surgical, line-level)
47
- "MultiEdit", // Edit multiple files in one shot
48
- "Bash", // Run npm install, npx expo install, tsc, etc.
49
- "Glob", // Find files by pattern (e.g. "**/*.tsx")
50
- "Grep", // Search file contents
51
- "TodoWrite", // Let Claude track its own subtasks
52
- "TodoRead", // Read its own task list
54
+ "Read", "Write", "Edit", "MultiEdit",
55
+ "Bash", "Glob", "Grep",
56
+ "TodoWrite", "TodoRead",
53
57
  ];
54
58
 
55
59
  const DEV_RULES = `
56
- You are a senior React Native / Expo developer working as part of an autonomous engineering team.
60
+ You are a senior React Native / Expo developer in an autonomous engineering team.
57
61
 
58
62
  RULES:
59
63
  - Write complete, production-ready code — no placeholders, no TODOs
60
64
  - Use TypeScript if the project uses it
61
- - Follow the existing code style and folder structure (read existing files first)
65
+ - Read existing files first to follow the project's patterns
62
66
  - Install packages with: npx expo install <package>
63
- - After writing files, run: npx tsc --noEmit — fix any TypeScript errors
64
- - If you hit an error, read it and fix it — iterate until it works
65
- - Do NOT ask questions. Do NOT ask for confirmation. Make your best judgment.
67
+ - After writing files run: npx tsc --noEmit — fix any errors you find
68
+ - If you hit an error, read it carefully and fix it — iterate until it works
69
+ - Do NOT ask questions or ask for confirmation. Make your best judgment.
66
70
  - When fully done, print EXACTLY this line: TASK_COMPLETE: <one sentence summary>
67
71
  `;
68
72
 
73
+ // ── Main export ───────────────────────────────────────────────────────────────
69
74
  export async function runDeveloperAgent(task, projectPath, techStack, retryContext = null) {
70
75
  console.log(` 🤖 Claude Code working on: ${task.title}`);
71
- if (CLAUDE_PATH) {
72
- console.log(` CLI: ${CLAUDE_PATH}`);
73
- } else {
76
+
77
+ if (!CLAUDE_PATH) {
74
78
  const hint = "Claude Code CLI not found. Run: npm install -g @anthropic-ai/claude-code";
75
79
  await startTask(task.id, hint);
76
80
  await failTask(task.id, hint);
77
81
  console.error(`\n ✗ ${hint}\n`);
78
82
  return { success: false, error: hint };
79
83
  }
84
+
85
+ console.log(` CLI: ${CLAUDE_PATH}`);
80
86
  await startTask(task.id, `Claude Code starting: ${task.title}`);
81
87
 
82
88
  const retryNote = retryContext
83
- ? `\n\n⚠️ RETRY — Previous attempt failed or QA flagged issues:\n${retryContext}\n\nFix these specific issues.`
89
+ ? `\n\n⚠️ RETRY — Previous attempt failed. Fix these issues:\n${retryContext}`
84
90
  : "";
85
91
 
86
92
  const techNote = techStack && Object.keys(techStack).length > 0
@@ -116,39 +122,31 @@ ${retryNote}
116
122
  permissionMode: "bypassPermissions",
117
123
  allowedTools: DEVELOPER_TOOLS,
118
124
  maxTurns: 80,
119
- ...(CLAUDE_PATH ? { pathToClaudeCodeExecutable: CLAUDE_PATH } : {}),
125
+ pathToClaudeCodeExecutable: CLAUDE_PATH,
120
126
  systemPrompt: {
121
127
  type: "preset",
122
128
  preset: "claude_code",
123
129
  append: DEV_RULES,
124
130
  },
125
- env: {
126
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
127
- },
131
+ env: buildEnv(),
128
132
  },
129
133
  })) {
130
134
 
131
- // ── Assistant messages ─────────────────────────────────────────────────
135
+ // Assistant messages — log tools and text
132
136
  if (message.type === "assistant") {
133
137
  for (const block of message.message.content) {
134
-
135
138
  if (block.type === "text" && block.text?.trim()) {
136
139
  const text = block.text.trim();
137
-
138
- // Detect completion signal
139
140
  if (text.includes("TASK_COMPLETE:")) {
140
141
  completed = true;
141
142
  summary = text.split("TASK_COMPLETE:")[1]?.trim().split("\n")[0] || task.title;
142
143
  }
143
-
144
- // Log meaningful progress
145
144
  if (text.length > 20) {
146
145
  const preview = text.split("\n")[0].slice(0, 120);
147
146
  await addLog(task.id, preview, "progress");
148
147
  console.log(` ${preview}`);
149
148
  }
150
149
  }
151
-
152
150
  if (block.type === "tool_use") {
153
151
  toolCallCount++;
154
152
  const log = formatToolLog(block);
@@ -160,14 +158,13 @@ ${retryNote}
160
158
  }
161
159
  }
162
160
 
163
- // ── Result ─────────────────────────────────────────────────────────────
161
+ // Result — task finished or errored
164
162
  if (message.type === "result") {
165
163
  if (message.subtype === "success") {
166
164
  if (!completed && message.result?.includes("TASK_COMPLETE:")) {
167
165
  completed = true;
168
166
  summary = message.result.split("TASK_COMPLETE:")[1]?.trim().split("\n")[0] || task.title;
169
167
  }
170
-
171
168
  const finalSummary = summary || `Implemented: ${task.title}`;
172
169
  await completeTask(task.id, `✓ ${finalSummary} (${toolCallCount} tool calls)`);
173
170
  console.log(` ✓ Done: ${task.title} — ${toolCallCount} tool calls`);
@@ -178,7 +175,7 @@ ${retryNote}
178
175
  await completeTask(task.id, `✓ ${summary} (hit turn limit)`);
179
176
  return { success: true, summary };
180
177
  }
181
- const err = "Reached max turns without completing increase maxTurns or break task into smaller pieces";
178
+ const err = "Reached max turns (80)consider breaking this task into smaller pieces";
182
179
  await failTask(task.id, err);
183
180
  return { success: false, error: err };
184
181
 
@@ -189,11 +186,11 @@ ${retryNote}
189
186
  }
190
187
  }
191
188
 
192
- // ── System init ────────────────────────────────────────────────────────
189
+ // System init — log session info
193
190
  if (message.type === "system" && message.subtype === "init") {
194
- const sessionShort = message.session_id?.slice(0, 8) || "?";
195
- console.log(` 📡 Session ${sessionShort} | ${message.tools?.length || 0} tools | ${message.model}`);
196
- await addLog(task.id, `Claude Code session ${sessionShort} in ${projectPath}`, "start");
191
+ const s = message.session_id?.slice(0, 8) || "?";
192
+ console.log(` 📡 Session ${s} | ${message.tools?.length || 0} tools | ${message.model}`);
193
+ await addLog(task.id, `Session ${s} started`, "start");
197
194
  }
198
195
  }
199
196
 
@@ -202,7 +199,6 @@ ${retryNote}
202
199
  await completeTask(task.id, `✓ ${summary}`);
203
200
  return { success: true, summary };
204
201
  }
205
-
206
202
  const err = "Session ended without result";
207
203
  await failTask(task.id, err);
208
204
  return { success: false, error: err };
@@ -210,15 +206,11 @@ ${retryNote}
210
206
  } catch (err) {
211
207
  const msg = err.message || String(err);
212
208
  console.error(` ✗ Error:`, msg.slice(0, 200));
213
-
214
- // Missing CLI — helpful message
215
- if (msg.includes("CLINotFoundError") || msg.includes("ENOENT") || msg.includes("claude-code")) {
209
+ if (msg.includes("CLINotFoundError") || msg.includes("ENOENT")) {
216
210
  const hint = "Claude Code CLI not installed. Run: npm install -g @anthropic-ai/claude-code";
217
211
  await failTask(task.id, hint);
218
- console.error(`\n ⚠️ ${hint}\n`);
219
212
  return { success: false, error: hint };
220
213
  }
221
-
222
214
  await failTask(task.id, msg.slice(0, 500));
223
215
  return { success: false, error: msg };
224
216
  }
@@ -226,15 +218,16 @@ ${retryNote}
226
218
 
227
219
  function formatToolLog(block) {
228
220
  const { name, input = {} } = block;
221
+ const p = input.file_path || input.path || "";
229
222
  switch (name) {
230
- case "Read": return `Read: ${input.file_path || input.path || ""}`;
231
- case "Write": return `Write: ${input.file_path || input.path || ""}`;
223
+ case "Read": return `Read: ${p}`;
224
+ case "Write": return `Write: ${p}`;
232
225
  case "Edit":
233
- case "MultiEdit": return `Edit: ${input.file_path || input.path || ""}`;
234
- case "Bash": return `Run: ${(input.command || "").slice(0, 80)}`;
235
- case "Glob": return `Glob: ${input.pattern || ""}`;
236
- case "Grep": return `Grep: "${(input.pattern || "").slice(0, 40)}"`;
237
- case "TodoWrite": return `Planning subtasks...`;
238
- default: return null;
226
+ case "MultiEdit": return `Edit: ${p}`;
227
+ case "Bash": return `Run: ${(input.command || "").slice(0, 80)}`;
228
+ case "Glob": return `Glob: ${input.pattern || ""}`;
229
+ case "Grep": return `Grep: "${(input.pattern || "").slice(0, 40)}"`;
230
+ case "TodoWrite": return `Planning...`;
231
+ default: return null;
239
232
  }
240
233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeboard",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "AI engineering team — from PRD to working mobile app, autonomously",
5
5
  "type": "module",
6
6
  "bin": {