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.
- package/agents/developer.js +182 -140
- package/bin/cli.js +15 -0
- package/package.json +2 -1
package/agents/developer.js
CHANGED
|
@@ -1,161 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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(`
|
|
23
|
-
await startTask(task.id, `
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
await
|
|
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
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
169
|
+
const err = "Session ended without result";
|
|
170
|
+
await failTask(task.id, err);
|
|
171
|
+
return { success: false, error: err };
|
|
128
172
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
} catch (err) {
|
|
174
|
+
const msg = err.message || String(err);
|
|
175
|
+
console.error(` ✗ Error:`, msg.slice(0, 200));
|
|
132
176
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
185
|
+
await failTask(task.id, msg.slice(0, 500));
|
|
186
|
+
return { success: false, error: msg };
|
|
187
|
+
}
|
|
141
188
|
}
|
|
142
189
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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": "
|
|
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",
|