claudeboard 2.2.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 +46 -84
  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,43 +10,27 @@ 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
  }
36
-
37
- return null; // will surface as CLINotFoundError
25
+ return null;
38
26
  }
39
27
 
40
- const CLAUDE_PATH = resolveClaudePath();
41
-
42
- // Build a full PATH for the Claude Code subprocess
43
- // Exit code 127 = "command not found" inside the subprocess — node/npm not in PATH
44
- function buildEnv() {// Exit code 127 = "command not found" inside the subprocess — node/npm not in PATH
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
45
30
  function buildEnv() {
46
- // Get node/npm directory to ensure they're in PATH
47
31
  let nodeBinDir = "";
48
- try {
49
- nodeBinDir = execSync("dirname $(which node)", { stdio: "pipe" }).toString().trim();
50
- } catch {}
32
+ try { nodeBinDir = execSync("dirname $(which node)", { stdio: "pipe" }).toString().trim(); } catch {}
51
33
 
52
- // Merge: existing PATH + node bin dir + common locations
53
34
  const pathParts = [
54
35
  process.env.PATH || "",
55
36
  nodeBinDir,
@@ -63,57 +44,49 @@ function buildEnv() {
63
44
  ].filter(Boolean);
64
45
 
65
46
  const fullPath = [...new Set(pathParts.join(":").split(":"))].join(":");
66
-
67
- return {
68
- ...process.env,
69
- PATH: fullPath,
70
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
71
- HOME: process.env.HOME,
72
- };
47
+ return { ...process.env, PATH: fullPath, HOME: process.env.HOME };
73
48
  }
74
49
 
75
- // Tools Claude Code can use — full developer access
50
+ const CLAUDE_PATH = resolveClaudePath();
51
+
52
+ // ── Tools Claude Code can use ─────────────────────────────────────────────────
76
53
  const DEVELOPER_TOOLS = [
77
- "Read", // Read any file in the project
78
- "Write", // Create new files
79
- "Edit", // Edit existing files (surgical, line-level)
80
- "MultiEdit", // Edit multiple files in one shot
81
- "Bash", // Run npm install, npx expo install, tsc, etc.
82
- "Glob", // Find files by pattern (e.g. "**/*.tsx")
83
- "Grep", // Search file contents
84
- "TodoWrite", // Let Claude track its own subtasks
85
- "TodoRead", // Read its own task list
54
+ "Read", "Write", "Edit", "MultiEdit",
55
+ "Bash", "Glob", "Grep",
56
+ "TodoWrite", "TodoRead",
86
57
  ];
87
58
 
88
59
  const DEV_RULES = `
89
- 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.
90
61
 
91
62
  RULES:
92
63
  - Write complete, production-ready code — no placeholders, no TODOs
93
64
  - Use TypeScript if the project uses it
94
- - Follow the existing code style and folder structure (read existing files first)
65
+ - Read existing files first to follow the project's patterns
95
66
  - Install packages with: npx expo install <package>
96
- - After writing files, run: npx tsc --noEmit — fix any TypeScript errors
97
- - If you hit an error, read it and fix it — iterate until it works
98
- - 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.
99
70
  - When fully done, print EXACTLY this line: TASK_COMPLETE: <one sentence summary>
100
71
  `;
101
72
 
73
+ // ── Main export ───────────────────────────────────────────────────────────────
102
74
  export async function runDeveloperAgent(task, projectPath, techStack, retryContext = null) {
103
75
  console.log(` 🤖 Claude Code working on: ${task.title}`);
104
- if (CLAUDE_PATH) {
105
- console.log(` CLI: ${CLAUDE_PATH}`);
106
- } else {
76
+
77
+ if (!CLAUDE_PATH) {
107
78
  const hint = "Claude Code CLI not found. Run: npm install -g @anthropic-ai/claude-code";
108
79
  await startTask(task.id, hint);
109
80
  await failTask(task.id, hint);
110
81
  console.error(`\n ✗ ${hint}\n`);
111
82
  return { success: false, error: hint };
112
83
  }
84
+
85
+ console.log(` CLI: ${CLAUDE_PATH}`);
113
86
  await startTask(task.id, `Claude Code starting: ${task.title}`);
114
87
 
115
88
  const retryNote = retryContext
116
- ? `\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}`
117
90
  : "";
118
91
 
119
92
  const techNote = techStack && Object.keys(techStack).length > 0
@@ -149,7 +122,7 @@ ${retryNote}
149
122
  permissionMode: "bypassPermissions",
150
123
  allowedTools: DEVELOPER_TOOLS,
151
124
  maxTurns: 80,
152
- ...(CLAUDE_PATH ? { pathToClaudeCodeExecutable: CLAUDE_PATH } : {}),
125
+ pathToClaudeCodeExecutable: CLAUDE_PATH,
153
126
  systemPrompt: {
154
127
  type: "preset",
155
128
  preset: "claude_code",
@@ -159,27 +132,21 @@ ${retryNote}
159
132
  },
160
133
  })) {
161
134
 
162
- // ── Assistant messages ─────────────────────────────────────────────────
135
+ // Assistant messages — log tools and text
163
136
  if (message.type === "assistant") {
164
137
  for (const block of message.message.content) {
165
-
166
138
  if (block.type === "text" && block.text?.trim()) {
167
139
  const text = block.text.trim();
168
-
169
- // Detect completion signal
170
140
  if (text.includes("TASK_COMPLETE:")) {
171
141
  completed = true;
172
142
  summary = text.split("TASK_COMPLETE:")[1]?.trim().split("\n")[0] || task.title;
173
143
  }
174
-
175
- // Log meaningful progress
176
144
  if (text.length > 20) {
177
145
  const preview = text.split("\n")[0].slice(0, 120);
178
146
  await addLog(task.id, preview, "progress");
179
147
  console.log(` ${preview}`);
180
148
  }
181
149
  }
182
-
183
150
  if (block.type === "tool_use") {
184
151
  toolCallCount++;
185
152
  const log = formatToolLog(block);
@@ -191,14 +158,13 @@ ${retryNote}
191
158
  }
192
159
  }
193
160
 
194
- // ── Result ─────────────────────────────────────────────────────────────
161
+ // Result — task finished or errored
195
162
  if (message.type === "result") {
196
163
  if (message.subtype === "success") {
197
164
  if (!completed && message.result?.includes("TASK_COMPLETE:")) {
198
165
  completed = true;
199
166
  summary = message.result.split("TASK_COMPLETE:")[1]?.trim().split("\n")[0] || task.title;
200
167
  }
201
-
202
168
  const finalSummary = summary || `Implemented: ${task.title}`;
203
169
  await completeTask(task.id, `✓ ${finalSummary} (${toolCallCount} tool calls)`);
204
170
  console.log(` ✓ Done: ${task.title} — ${toolCallCount} tool calls`);
@@ -209,7 +175,7 @@ ${retryNote}
209
175
  await completeTask(task.id, `✓ ${summary} (hit turn limit)`);
210
176
  return { success: true, summary };
211
177
  }
212
- 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";
213
179
  await failTask(task.id, err);
214
180
  return { success: false, error: err };
215
181
 
@@ -220,11 +186,11 @@ ${retryNote}
220
186
  }
221
187
  }
222
188
 
223
- // ── System init ────────────────────────────────────────────────────────
189
+ // System init — log session info
224
190
  if (message.type === "system" && message.subtype === "init") {
225
- const sessionShort = message.session_id?.slice(0, 8) || "?";
226
- console.log(` 📡 Session ${sessionShort} | ${message.tools?.length || 0} tools | ${message.model}`);
227
- 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");
228
194
  }
229
195
  }
230
196
 
@@ -233,7 +199,6 @@ ${retryNote}
233
199
  await completeTask(task.id, `✓ ${summary}`);
234
200
  return { success: true, summary };
235
201
  }
236
-
237
202
  const err = "Session ended without result";
238
203
  await failTask(task.id, err);
239
204
  return { success: false, error: err };
@@ -241,15 +206,11 @@ ${retryNote}
241
206
  } catch (err) {
242
207
  const msg = err.message || String(err);
243
208
  console.error(` ✗ Error:`, msg.slice(0, 200));
244
-
245
- // Missing CLI — helpful message
246
- if (msg.includes("CLINotFoundError") || msg.includes("ENOENT") || msg.includes("claude-code")) {
209
+ if (msg.includes("CLINotFoundError") || msg.includes("ENOENT")) {
247
210
  const hint = "Claude Code CLI not installed. Run: npm install -g @anthropic-ai/claude-code";
248
211
  await failTask(task.id, hint);
249
- console.error(`\n ⚠️ ${hint}\n`);
250
212
  return { success: false, error: hint };
251
213
  }
252
-
253
214
  await failTask(task.id, msg.slice(0, 500));
254
215
  return { success: false, error: msg };
255
216
  }
@@ -257,15 +218,16 @@ ${retryNote}
257
218
 
258
219
  function formatToolLog(block) {
259
220
  const { name, input = {} } = block;
221
+ const p = input.file_path || input.path || "";
260
222
  switch (name) {
261
- case "Read": return `Read: ${input.file_path || input.path || ""}`;
262
- case "Write": return `Write: ${input.file_path || input.path || ""}`;
223
+ case "Read": return `Read: ${p}`;
224
+ case "Write": return `Write: ${p}`;
263
225
  case "Edit":
264
- case "MultiEdit": return `Edit: ${input.file_path || input.path || ""}`;
265
- case "Bash": return `Run: ${(input.command || "").slice(0, 80)}`;
266
- case "Glob": return `Glob: ${input.pattern || ""}`;
267
- case "Grep": return `Grep: "${(input.pattern || "").slice(0, 40)}"`;
268
- case "TodoWrite": return `Planning subtasks...`;
269
- 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;
270
232
  }
271
233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeboard",
3
- "version": "2.2.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": {