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.
@@ -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 {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  const MODEL = "claude-sonnet-4-20250514";
7
- const MAX_TOKENS = 8096;
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
- * Returns parsed object or throws
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|```/g, "").trim();
70
+ const clean = text.replace(/```json\n?|```/g, "").trim();
70
71
  return JSON.parse(clean);
71
- } catch (err) {
72
- throw new Error(`Failed to parse JSON from Claude: ${text.slice(0, 200)}`);
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
  /**
@@ -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
- // ── PHASE 1: ARCHITECTURE ──────────────────────────────────────────────────
54
- if (!skipArchitect) {
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("--skip-architect", "Skip architecture phase (tasks already exist)")
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}\n`));
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
- skipArchitect: !!opts.skipArchitect,
163
+ forceRestart: !!opts.restart,
160
164
  });
161
165
  });
162
166