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.
- package/agents/developer.js +62 -69
- 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,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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
+
// ── Tools Claude Code can use ─────────────────────────────────────────────────
|
|
43
53
|
const DEVELOPER_TOOLS = [
|
|
44
|
-
"Read",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
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
|
|
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
|
-
-
|
|
65
|
+
- Read existing files first to follow the project's patterns
|
|
62
66
|
- Install packages with: npx expo install <package>
|
|
63
|
-
- After writing files
|
|
64
|
-
- If you hit an error, read it and fix it — iterate until it works
|
|
65
|
-
- 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.
|
|
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
|
-
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
189
|
+
// System init — log session info
|
|
193
190
|
if (message.type === "system" && message.subtype === "init") {
|
|
194
|
-
const
|
|
195
|
-
console.log(` 📡 Session ${
|
|
196
|
-
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");
|
|
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":
|
|
231
|
-
case "Write":
|
|
223
|
+
case "Read": return `Read: ${p}`;
|
|
224
|
+
case "Write": return `Write: ${p}`;
|
|
232
225
|
case "Edit":
|
|
233
|
-
case "MultiEdit":
|
|
234
|
-
case "Bash":
|
|
235
|
-
case "Glob":
|
|
236
|
-
case "Grep":
|
|
237
|
-
case "TodoWrite":
|
|
238
|
-
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;
|
|
239
232
|
}
|
|
240
233
|
}
|