claudeboard 1.1.0 → 1.5.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/board-client.js +27 -0
- package/agents/claude-api.js +24 -7
- package/agents/orchestrator.js +22 -4
- package/bin/cli.js +7 -3
- package/dashboard/index.html +1189 -572
- package/dashboard/server.js +234 -116
- package/package.json +2 -1
package/agents/board-client.js
CHANGED
|
@@ -77,6 +77,33 @@ export async function createTask({ epicId, title, description, priority = "mediu
|
|
|
77
77
|
return data;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Check if this project already has tasks in the board
|
|
82
|
+
* Used to detect resume vs fresh start
|
|
83
|
+
*/
|
|
84
|
+
export async function hasTasks() {
|
|
85
|
+
const { data, error } = await supabase
|
|
86
|
+
.from("cb_tasks")
|
|
87
|
+
.select("id")
|
|
88
|
+
.eq("project", PROJECT)
|
|
89
|
+
.limit(1);
|
|
90
|
+
return Array.isArray(data) && data.length > 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reset any stuck "in_progress" tasks back to "todo"
|
|
95
|
+
* Called on resume so the agent re-picks them up
|
|
96
|
+
*/
|
|
97
|
+
export async function resetStuckTasks() {
|
|
98
|
+
const { data } = await supabase
|
|
99
|
+
.from("cb_tasks")
|
|
100
|
+
.update({ status: "todo", started_at: null })
|
|
101
|
+
.eq("project", PROJECT)
|
|
102
|
+
.eq("status", "in_progress")
|
|
103
|
+
.select();
|
|
104
|
+
return data?.length || 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
export async function getStats() {
|
|
81
108
|
const tasks = await getAllTasks();
|
|
82
109
|
return {
|
package/agents/claude-api.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const MODEL = "claude-sonnet-4-20250514";
|
|
7
|
-
const MAX_TOKENS =
|
|
7
|
+
const MAX_TOKENS = 16000; // Increased — large codegen responses need more tokens
|
|
8
8
|
|
|
9
9
|
function getHeaders() {
|
|
10
10
|
const key = process.env.ANTHROPIC_API_KEY;
|
|
@@ -59,18 +59,35 @@ export async function callClaude(systemPrompt, userMessage, options = {}) {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Call Claude API expecting JSON response
|
|
62
|
-
*
|
|
62
|
+
* Robust: tries 3 extraction strategies + auto-repair for truncated responses
|
|
63
63
|
*/
|
|
64
64
|
export async function callClaudeJSON(systemPrompt, userMessage, options = {}) {
|
|
65
65
|
const sys = systemPrompt + "\n\nYou MUST respond with valid JSON only. No markdown, no explanation, no backticks. Pure JSON.";
|
|
66
|
-
const { text } = await callClaude(sys, userMessage, options);
|
|
66
|
+
const { text } = await callClaude(sys, userMessage, { ...options, maxTokens: options.maxTokens || MAX_TOKENS });
|
|
67
67
|
|
|
68
|
+
// Try 1: direct parse after stripping backticks
|
|
68
69
|
try {
|
|
69
|
-
const clean = text.replace(/```json
|
|
70
|
+
const clean = text.replace(/```json\n?|```/g, "").trim();
|
|
70
71
|
return JSON.parse(clean);
|
|
71
|
-
} catch
|
|
72
|
-
|
|
73
|
-
}
|
|
72
|
+
} catch {}
|
|
73
|
+
|
|
74
|
+
// Try 2: extract first { } or [ ] block
|
|
75
|
+
try {
|
|
76
|
+
const match = text.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
|
|
77
|
+
if (match) return JSON.parse(match[1]);
|
|
78
|
+
} catch {}
|
|
79
|
+
|
|
80
|
+
// Try 3: ask Claude to repair truncated/broken JSON
|
|
81
|
+
try {
|
|
82
|
+
const repair = await callClaude(
|
|
83
|
+
"You are a JSON repair tool. Fix this broken or truncated JSON. Return ONLY valid JSON, nothing else, no backticks.",
|
|
84
|
+
`Broken JSON:\n${text.slice(0, 8000)}`
|
|
85
|
+
);
|
|
86
|
+
const clean = repair.text.replace(/```json\n?|```/g, "").trim();
|
|
87
|
+
return JSON.parse(clean);
|
|
88
|
+
} catch {}
|
|
89
|
+
|
|
90
|
+
throw new Error(`Failed to parse JSON after 3 attempts. Preview: ${text.slice(0, 300)}`);
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
/**
|
package/agents/orchestrator.js
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
addLog,
|
|
14
14
|
createTask,
|
|
15
15
|
createEpic,
|
|
16
|
+
hasTasks,
|
|
17
|
+
resetStuckTasks,
|
|
16
18
|
} from "./board-client.js";
|
|
17
19
|
import { initSupabaseReader } from "../tools/supabase-reader.js";
|
|
18
20
|
import { runCommand, startProcess, waitForPort } from "../tools/terminal.js";
|
|
@@ -49,15 +51,31 @@ export async function runOrchestrator(config) {
|
|
|
49
51
|
console.log(chalk.dim(` Project: ${projectPath}\n`));
|
|
50
52
|
|
|
51
53
|
let techStack = {};
|
|
54
|
+
let isResume = false;
|
|
52
55
|
|
|
53
|
-
// ──
|
|
54
|
-
|
|
56
|
+
// ── DETECT: RESUME or FRESH START ─────────────────────────────────────────
|
|
57
|
+
const existingTasks = await hasTasks();
|
|
58
|
+
|
|
59
|
+
if (existingTasks && !config.forceRestart) {
|
|
60
|
+
isResume = true;
|
|
61
|
+
const stuck = await resetStuckTasks();
|
|
62
|
+
const stats = await getStats();
|
|
63
|
+
|
|
64
|
+
console.log(chalk.yellow(" ↩️ Resuming existing session\n"));
|
|
65
|
+
console.log(chalk.dim(` Found ${stats.total} tasks — ${stats.done} done, ${stats.todo} todo, ${stats.error} failed`));
|
|
66
|
+
if (stuck > 0) {
|
|
67
|
+
console.log(chalk.dim(` Reset ${stuck} stuck task(s) back to todo`));
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
} else {
|
|
71
|
+
// ── PHASE 1: ARCHITECTURE ────────────────────────────────────────────────
|
|
72
|
+
if (config.forceRestart) {
|
|
73
|
+
console.log(chalk.dim(" Force restart — skipping existing tasks (they remain in board)\n"));
|
|
74
|
+
}
|
|
55
75
|
console.log(chalk.bold.cyan("[ PHASE 1: ARCHITECTURE ]\n"));
|
|
56
76
|
const archResult = await runArchitectAgent(prdContent, projectName);
|
|
57
77
|
techStack = archResult.techStack;
|
|
58
78
|
console.log(chalk.green(`\n ✓ ${archResult.totalTasks} tasks created across ${archResult.epics.length} epics\n`));
|
|
59
|
-
} else {
|
|
60
|
-
console.log(chalk.dim(" Skipping architect (tasks already exist)\n"));
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
// ── START EXPO ─────────────────────────────────────────────────────────────
|
package/bin/cli.js
CHANGED
|
@@ -62,6 +62,7 @@ program
|
|
|
62
62
|
supabaseUrl: answers.supabaseUrl,
|
|
63
63
|
supabaseKey: answers.supabaseKey,
|
|
64
64
|
anthropicKey: answers.anthropicKey,
|
|
65
|
+
projectDir: process.cwd(),
|
|
65
66
|
createdAt: new Date().toISOString(),
|
|
66
67
|
};
|
|
67
68
|
|
|
@@ -106,6 +107,7 @@ program
|
|
|
106
107
|
ANTHROPIC_API_KEY: config.anthropicKey || process.env.ANTHROPIC_API_KEY || "",
|
|
107
108
|
PORT: String(port),
|
|
108
109
|
PROJECT_NAME: config.projectName,
|
|
110
|
+
PROJECT_DIR: config.projectDir || process.cwd(),
|
|
109
111
|
},
|
|
110
112
|
stdio: "pipe",
|
|
111
113
|
});
|
|
@@ -129,7 +131,7 @@ program
|
|
|
129
131
|
.description("Run the AI engineering team on your project")
|
|
130
132
|
.requiredOption("--prd <path>", "Path to PRD markdown file")
|
|
131
133
|
.requiredOption("--project <path>", "Path to your app project directory")
|
|
132
|
-
.option("--
|
|
134
|
+
.option("--restart", "Force restart from scratch (ignore existing tasks in board)")
|
|
133
135
|
.option("--expo-port <port>", "Expo Web port for QA screenshots", "8081")
|
|
134
136
|
.action(async (opts) => {
|
|
135
137
|
console.log(LOGO);
|
|
@@ -145,7 +147,9 @@ program
|
|
|
145
147
|
console.log(chalk.bold(` AI Team starting for: ${chalk.cyan(config.projectName)}\n`));
|
|
146
148
|
console.log(chalk.dim(` Dashboard → http://localhost:${config.port}`));
|
|
147
149
|
console.log(chalk.dim(` PRD → ${opts.prd}`));
|
|
148
|
-
console.log(chalk.dim(` Project → ${opts.project}
|
|
150
|
+
console.log(chalk.dim(` Project → ${opts.project}`));
|
|
151
|
+
if (opts.restart) console.log(chalk.yellow(` Mode → FORCE RESTART\n`));
|
|
152
|
+
else console.log(chalk.dim(` Mode → auto-resume if tasks exist\n`));
|
|
149
153
|
|
|
150
154
|
const { runOrchestrator } = await import("../agents/orchestrator.js");
|
|
151
155
|
|
|
@@ -156,7 +160,7 @@ program
|
|
|
156
160
|
supabaseKey: config.supabaseKey,
|
|
157
161
|
projectName: config.projectName,
|
|
158
162
|
expoPort: parseInt(opts.expoPort),
|
|
159
|
-
|
|
163
|
+
forceRestart: !!opts.restart,
|
|
160
164
|
});
|
|
161
165
|
});
|
|
162
166
|
|