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.
- package/agents/developer.js +46 -84
- package/package.json +1 -1
package/agents/developer.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Developer Agent — powered by Claude Agent SDK
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
+
const CLAUDE_PATH = resolveClaudePath();
|
|
51
|
+
|
|
52
|
+
// ── Tools Claude Code can use ─────────────────────────────────────────────────
|
|
76
53
|
const DEVELOPER_TOOLS = [
|
|
77
|
-
"Read",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
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
|
|
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
|
-
-
|
|
65
|
+
- Read existing files first to follow the project's patterns
|
|
95
66
|
- Install packages with: npx expo install <package>
|
|
96
|
-
- After writing files
|
|
97
|
-
- If you hit an error, read it and fix it — iterate until it works
|
|
98
|
-
- Do NOT ask questions
|
|
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
|
-
|
|
105
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
189
|
+
// System init — log session info
|
|
224
190
|
if (message.type === "system" && message.subtype === "init") {
|
|
225
|
-
const
|
|
226
|
-
console.log(` 📡 Session ${
|
|
227
|
-
await addLog(task.id, `
|
|
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":
|
|
262
|
-
case "Write":
|
|
223
|
+
case "Read": return `Read: ${p}`;
|
|
224
|
+
case "Write": return `Write: ${p}`;
|
|
263
225
|
case "Edit":
|
|
264
|
-
case "MultiEdit":
|
|
265
|
-
case "Bash":
|
|
266
|
-
case "Glob":
|
|
267
|
-
case "Grep":
|
|
268
|
-
case "TodoWrite":
|
|
269
|
-
default:
|
|
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
|
}
|