claudeboard 2.3.0 → 2.8.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.
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  const MODEL = "claude-sonnet-4-20250514";
7
- const MAX_TOKENS = 16000; // Increasedlarge codegen responses need more tokens
7
+ const MAX_TOKENS = 8096; // Max output tokens input context window is 200k, no limits there
8
8
 
9
9
  function getHeaders() {
10
10
  const key = process.env.ANTHROPIC_API_KEY;
@@ -0,0 +1,240 @@
1
+ import { callClaude } from "./claude-api.js";
2
+ import { runCommand } from "../tools/terminal.js";
3
+ import { startProcess, waitForPort } from "../tools/terminal.js";
4
+ import chalk from "chalk";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { createRequire } from "module";
8
+
9
+ const require = createRequire(import.meta.url);
10
+
11
+ const MAX_FIX_ATTEMPTS = 3;
12
+
13
+ /**
14
+ * Expo Health Check Agent
15
+ * Runs before development loop starts.
16
+ * 1. Install deps
17
+ * 2. Try to start Expo
18
+ * 3. If it crashes, read the error and fix it (up to 3 attempts)
19
+ * 4. Return { ready: true/false, process, port }
20
+ */
21
+ export async function runExpoHealthCheck(projectPath, port = 8081) {
22
+ console.log(chalk.bold.cyan("\n[ EXPO HEALTH CHECK ]\n"));
23
+
24
+ if (!fs.existsSync(path.join(projectPath, "package.json"))) {
25
+ console.log(chalk.dim(" No package.json found — skipping Expo health check"));
26
+ return { ready: false, process: null };
27
+ }
28
+
29
+ // ── Step 1: Install dependencies ──────────────────────────────────────────
30
+ console.log(chalk.dim(" Installing dependencies (--legacy-peer-deps)..."));
31
+ const installResult = await runCommand(
32
+ "npm install --legacy-peer-deps 2>&1",
33
+ projectPath,
34
+ 120000
35
+ );
36
+ const installErrors = extractNpmErrors(installResult.stdout);
37
+ if (installErrors) {
38
+ console.log(chalk.yellow(` ⚠ npm install issues detected:\n${installErrors}`));
39
+ // Try to fix install errors before continuing
40
+ const fixed = await fixInstallErrors(projectPath, installErrors);
41
+ if (!fixed) {
42
+ console.log(chalk.red(" ✗ Could not fix install errors — continuing without Expo"));
43
+ return { ready: false, process: null };
44
+ }
45
+ }
46
+ console.log(chalk.dim(" ✓ Dependencies installed"));
47
+
48
+ // ── Step 2: Attempt Expo start with error detection ───────────────────────
49
+ for (let attempt = 1; attempt <= MAX_FIX_ATTEMPTS; attempt++) {
50
+ console.log(chalk.dim(` Starting Expo (attempt ${attempt}/${MAX_FIX_ATTEMPTS})...`));
51
+
52
+ const result = await tryStartExpo(projectPath, port);
53
+
54
+ if (result.ready) {
55
+ console.log(chalk.green(` ✓ Expo running on port ${port}\n`));
56
+ return { ready: true, process: result.process };
57
+ }
58
+
59
+ const error = result.error;
60
+ console.log(chalk.yellow(` ✗ Expo failed to start:\n ${error?.slice(0, 200)}`));
61
+
62
+ if (attempt === MAX_FIX_ATTEMPTS) break;
63
+
64
+ // ── Step 3: Ask Claude to fix the error ─────────────────────────────────
65
+ console.log(chalk.dim(` Asking Claude to fix the error...`));
66
+ const fixed = await fixExpoError(projectPath, error);
67
+
68
+ if (!fixed) {
69
+ console.log(chalk.red(" Could not determine fix — skipping Expo"));
70
+ break;
71
+ }
72
+ console.log(chalk.dim(` Applied fix — retrying...`));
73
+ }
74
+
75
+ console.log(chalk.yellow(" ⚠ Expo not available — continuing without visual QA\n"));
76
+ return { ready: false, process: null };
77
+ }
78
+
79
+ // ── Try to start Expo and detect if it crashes or succeeds ─────────────────
80
+ async function tryStartExpo(projectPath, port) {
81
+ return new Promise((resolve) => {
82
+ let output = "";
83
+ let resolved = false;
84
+ let proc = null;
85
+
86
+ const done = (ready, error) => {
87
+ if (resolved) return;
88
+ resolved = true;
89
+ if (!ready && proc) {
90
+ try { proc.kill(); } catch {}
91
+ proc = null;
92
+ }
93
+ resolve({ ready, process: proc, error });
94
+ };
95
+
96
+ // Collect logs for 20 seconds, then give up if no success
97
+ const timeout = setTimeout(() => done(false, `Expo did not start within 20s. Last output:\n${output.slice(-500)}`), 20000);
98
+
99
+ try {
100
+ const { spawn } = require("child_process");
101
+ proc = spawn("npx", ["expo", "start", "--web", "--port", String(port)], {
102
+ cwd: projectPath,
103
+ env: {
104
+ ...process.env,
105
+ CI: "1",
106
+ EXPO_NO_INTERACTIVE: "1",
107
+ EXPO_NO_DOTENV: "0",
108
+ },
109
+ stdio: "pipe",
110
+ });
111
+
112
+ proc.stdout.on("data", (d) => {
113
+ const text = d.toString();
114
+ output += text;
115
+ // Success signals
116
+ if (
117
+ text.includes("Metro waiting on") ||
118
+ text.includes(`localhost:${port}`) ||
119
+ text.includes("Web is waiting") ||
120
+ text.includes("Bundling complete")
121
+ ) {
122
+ clearTimeout(timeout);
123
+ done(true, null);
124
+ }
125
+ });
126
+
127
+ proc.stderr.on("data", (d) => {
128
+ const text = d.toString();
129
+ output += text;
130
+ // Fatal crash signals
131
+ if (
132
+ text.includes("Unable to resolve module") ||
133
+ text.includes("Cannot find module") ||
134
+ text.includes("Error: Cannot") ||
135
+ text.includes("ENOENT") ||
136
+ text.includes("SyntaxError")
137
+ ) {
138
+ clearTimeout(timeout);
139
+ // Small delay to collect full error
140
+ setTimeout(() => done(false, output.slice(-1500)), 1000);
141
+ }
142
+ });
143
+
144
+ proc.on("close", (code) => {
145
+ clearTimeout(timeout);
146
+ if (code !== 0) done(false, `Process exited with code ${code}.\n${output.slice(-1000)}`);
147
+ });
148
+
149
+ } catch (e) {
150
+ clearTimeout(timeout);
151
+ done(false, e.message);
152
+ }
153
+ });
154
+ }
155
+
156
+ // ── Ask Claude to diagnose and apply the fix ──────────────────────────────
157
+ async function fixExpoError(projectPath, errorText) {
158
+ const pkgRaw = fs.readFileSync(path.join(projectPath, "package.json"), "utf8");
159
+ const pkg = JSON.parse(pkgRaw);
160
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
161
+
162
+ const prompt = `You are a React Native / Expo dependency expert.
163
+
164
+ The app failed to start with this error:
165
+ \`\`\`
166
+ ${errorText}
167
+ \`\`\`
168
+
169
+ Current package.json dependencies:
170
+ \`\`\`json
171
+ ${JSON.stringify(deps, null, 2)}
172
+ \`\`\`
173
+
174
+ Project path: ${projectPath}
175
+
176
+ Diagnose the root cause and provide the EXACT shell commands to fix it.
177
+ Common fixes:
178
+ - Version conflicts: upgrade/downgrade specific packages
179
+ - Missing modules: npm install <package>
180
+ - Peer dep issues: npm install <pkg>@<compatible-version> --legacy-peer-deps
181
+ - Wrong import paths: patch the source file
182
+
183
+ Respond with JSON:
184
+ {
185
+ "diagnosis": "One sentence explanation of the root cause",
186
+ "commands": ["npm install ...", "..."],
187
+ "confidence": 0-100
188
+ }`;
189
+
190
+ let verdict;
191
+ try {
192
+ const { callClaudeJSON } = await import("./claude-api.js");
193
+ verdict = await callClaudeJSON(
194
+ "You are a React Native dependency expert. Respond only with valid JSON.",
195
+ prompt
196
+ );
197
+ } catch {
198
+ return false;
199
+ }
200
+
201
+ if (!verdict?.commands?.length || verdict.confidence < 40) {
202
+ console.log(chalk.dim(` Diagnosis: ${verdict?.diagnosis || "unknown"} (confidence: ${verdict?.confidence}%)`));
203
+ return false;
204
+ }
205
+
206
+ console.log(chalk.dim(` Diagnosis: ${verdict.diagnosis}`));
207
+ console.log(chalk.dim(` Applying ${verdict.commands.length} fix command(s)...`));
208
+
209
+ for (const cmd of verdict.commands) {
210
+ console.log(chalk.dim(` → ${cmd}`));
211
+ const result = await runCommand(cmd + " 2>&1", projectPath, 120000);
212
+ if (result.returncode !== 0) {
213
+ const errLine = result.stdout.split("\n").find(l => l.includes("error")) || result.stdout.slice(-200);
214
+ console.log(chalk.yellow(` ⚠ Command failed: ${errLine}`));
215
+ }
216
+ }
217
+
218
+ return true;
219
+ }
220
+
221
+ // ── Fix npm install errors ──────────────────────────────────────────────────
222
+ async function fixInstallErrors(projectPath, errorText) {
223
+ // Try --force as a last resort for install errors
224
+ if (errorText.includes("ERESOLVE") || errorText.includes("peer")) {
225
+ console.log(chalk.dim(" Retrying with --force..."));
226
+ const result = await runCommand("npm install --force 2>&1", projectPath, 120000);
227
+ const stillBroken = extractNpmErrors(result.stdout);
228
+ return !stillBroken;
229
+ }
230
+ return false;
231
+ }
232
+
233
+ // ── Extract real npm errors (ignore peer dep warnings) ─────────────────────
234
+ function extractNpmErrors(output) {
235
+ const lines = output.split("\n");
236
+ const errors = lines.filter(l =>
237
+ l.includes("npm error") && !l.includes("ERESOLVE") && !l.includes("peer")
238
+ );
239
+ return errors.length ? errors.join("\n") : null;
240
+ }
@@ -5,6 +5,18 @@ import path from "path";
5
5
  import { runArchitectAgent } from "./architect.js";
6
6
  import { runDeveloperAgent } from "./developer.js";
7
7
  import { runQAAgent, runFullAppQA } from "./qa.js";
8
+ import { runExpoHealthCheck } from "./expo-health.js";
9
+ import { createConnection } from "net";
10
+
11
+ function isPortOpen(port) {
12
+ return new Promise(resolve => {
13
+ const sock = createConnection({ port, host: "127.0.0.1" });
14
+ sock.setTimeout(600);
15
+ sock.once("connect", () => { sock.destroy(); resolve(true); });
16
+ sock.once("error", () => resolve(false));
17
+ sock.once("timeout", () => resolve(false));
18
+ });
19
+ }
8
20
  import {
9
21
  initBoard,
10
22
  getNextTask,
@@ -84,28 +96,11 @@ export async function runOrchestrator(config) {
84
96
  const logFile = path.join(projectPath, ".claudeboard-logs.txt");
85
97
  const logStream = fs.createWriteStream(logFile, { flags: "a" });
86
98
 
87
- // Check if expo project exists
88
- const hasPackageJson = fs.existsSync(path.join(projectPath, "package.json"));
89
-
90
- if (hasPackageJson) {
91
- console.log(chalk.dim(" Starting Expo Web for QA screenshots...\n"));
92
- expoProcess = startProcess(
93
- "npx",
94
- ["expo", "start", "--web", "--port", String(expoPort)],
95
- projectPath,
96
- (log) => {
97
- logStream.write(log);
98
- if (log.includes("Metro waiting") || log.includes("localhost:" + expoPort)) {
99
- expoReady = true;
100
- }
101
- }
102
- );
103
-
104
- // Wait up to 30s for expo to start
105
- await waitForPort(expoPort, 30000);
106
- expoReady = true;
107
- console.log(chalk.dim(` Expo Web running at http://localhost:${expoPort}\n`));
108
- }
99
+ // ── EXPO HEALTH CHECK: install deps + start + auto-fix errors ─────────────
100
+ // Runs before development loop so the app is visible from task #1
101
+ const expoHealth = await runExpoHealthCheck(projectPath, expoPort);
102
+ expoReady = expoHealth.ready;
103
+ expoProcess = expoHealth.process;
109
104
 
110
105
  // ── PHASE 2: DEVELOPMENT LOOP ─────────────────────────────────────────────
111
106
  console.log(chalk.bold.cyan("[ PHASE 2: DEVELOPMENT ]\n"));
@@ -145,6 +140,17 @@ export async function runOrchestrator(config) {
145
140
 
146
141
  consecutiveFailures = 0;
147
142
 
143
+ // ── Re-check Expo health if it's supposed to be running but isn't ────────
144
+ if (expoReady) {
145
+ const portOpen = await isPortOpen(expoPort);
146
+ if (!portOpen) {
147
+ console.log(chalk.yellow(" ⚠ Expo seems to have crashed — running health check..."));
148
+ const recheck = await runExpoHealthCheck(projectPath, expoPort);
149
+ expoReady = recheck.ready;
150
+ expoProcess = recheck.process;
151
+ }
152
+ }
153
+
148
154
  // Run QA agent (only if expo is running and this is a UI/feature task)
149
155
  const shouldRunQA = expoReady && ["feature", "bug"].includes(task.type);
150
156