@gethmy/agent 1.10.3 → 1.10.5

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.
Files changed (4) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +414 -228
  3. package/dist/index.js +414 -228
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -396,9 +396,14 @@ var init_types = __esm(() => {
396
396
  postSummary: true
397
397
  },
398
398
  claude: {
399
- model: "opus",
400
- escalateModel: "claude-fable-5",
399
+ model: "claude-opus-4-8",
400
+ escalateModel: "claude-opus-4-8",
401
401
  escalateAfterAttempts: 2,
402
+ tiers: {
403
+ simple: "claude-haiku-4-5",
404
+ advanced: "claude-sonnet-4-6",
405
+ research: "claude-opus-4-8"
406
+ },
402
407
  reviewModel: "sonnet",
403
408
  maxTurns: 80,
404
409
  reviewMaxTurns: 60,
@@ -2076,8 +2081,127 @@ var init_git_diff_stat = __esm(() => {
2076
2081
  init_log();
2077
2082
  });
2078
2083
 
2084
+ // src/project-type.ts
2085
+ import { execFileSync as execFileSync6 } from "node:child_process";
2086
+ import { existsSync as existsSync4, readdirSync } from "node:fs";
2087
+ function detect(dir) {
2088
+ const cached2 = _cache.get(dir);
2089
+ if (cached2)
2090
+ return cached2;
2091
+ const result = detectUncached(dir);
2092
+ _cache.set(dir, result);
2093
+ log.info(TAG11, `Detected project type in ${dir}: ${result.kind}`);
2094
+ return result;
2095
+ }
2096
+ function detectUncached(dir) {
2097
+ if (existsSync4(`${dir}/package.json`))
2098
+ return { kind: "node" };
2099
+ if (existsSync4(`${dir}/Package.swift`))
2100
+ return { kind: "swift-spm" };
2101
+ const entries = safeReaddir(dir);
2102
+ const workspace = entries.find((e) => e.endsWith(".xcworkspace"));
2103
+ if (workspace) {
2104
+ return {
2105
+ kind: "swift-xcode",
2106
+ xcodeContainer: `${dir}/${workspace}`,
2107
+ xcodeIsWorkspace: true
2108
+ };
2109
+ }
2110
+ const project = entries.find((e) => e.endsWith(".xcodeproj"));
2111
+ if (project) {
2112
+ return {
2113
+ kind: "swift-xcode",
2114
+ xcodeContainer: `${dir}/${project}`,
2115
+ xcodeIsWorkspace: false
2116
+ };
2117
+ }
2118
+ return { kind: "unknown" };
2119
+ }
2120
+ function safeReaddir(dir) {
2121
+ try {
2122
+ return readdirSync(dir);
2123
+ } catch {
2124
+ return [];
2125
+ }
2126
+ }
2127
+ function buildCommand(dir) {
2128
+ const pt = detect(dir);
2129
+ switch (pt.kind) {
2130
+ case "node": {
2131
+ const [cmd, args] = spawnRunArgs("build");
2132
+ return { cmd, args };
2133
+ }
2134
+ case "swift-spm":
2135
+ return { cmd: "swift", args: ["build"] };
2136
+ case "swift-xcode":
2137
+ return xcodeBuildCommand(pt);
2138
+ case "unknown":
2139
+ return null;
2140
+ }
2141
+ }
2142
+ function lintCommand(dir) {
2143
+ const pt = detect(dir);
2144
+ switch (pt.kind) {
2145
+ case "node": {
2146
+ const [cmd, args] = spawnRunArgs("lint");
2147
+ return { cmd, args };
2148
+ }
2149
+ case "swift-spm":
2150
+ case "swift-xcode":
2151
+ case "unknown":
2152
+ return null;
2153
+ }
2154
+ }
2155
+ function supportsDevServer(dir) {
2156
+ return detect(dir).kind === "node";
2157
+ }
2158
+ function xcodeBuildCommand(pt) {
2159
+ const container = pt.xcodeContainer;
2160
+ if (!container)
2161
+ return null;
2162
+ const scheme = resolveXcodeScheme(pt);
2163
+ if (!scheme) {
2164
+ log.warn(TAG11, "Could not resolve an Xcode scheme — skipping build (best-effort)");
2165
+ return null;
2166
+ }
2167
+ const containerFlag = pt.xcodeIsWorkspace ? "-workspace" : "-project";
2168
+ return {
2169
+ cmd: "xcodebuild",
2170
+ args: [
2171
+ containerFlag,
2172
+ container,
2173
+ "-scheme",
2174
+ scheme,
2175
+ "-destination",
2176
+ "generic/platform=iOS",
2177
+ "CODE_SIGNING_ALLOWED=NO",
2178
+ "build"
2179
+ ]
2180
+ };
2181
+ }
2182
+ function resolveXcodeScheme(pt) {
2183
+ if (!pt.xcodeContainer)
2184
+ return null;
2185
+ const flag = pt.xcodeIsWorkspace ? "-workspace" : "-project";
2186
+ try {
2187
+ const out = execFileSync6("xcodebuild", ["-list", "-json", flag, pt.xcodeContainer], { encoding: "utf-8", timeout: 30000, stdio: "pipe" });
2188
+ const parsed = JSON.parse(out);
2189
+ const schemes = pt.xcodeIsWorkspace ? parsed.workspace?.schemes ?? [] : parsed.project?.schemes ?? [];
2190
+ return schemes[0] ?? null;
2191
+ } catch (err) {
2192
+ log.warn(TAG11, `xcodebuild -list failed: ${err instanceof Error ? err.message : err}`);
2193
+ return null;
2194
+ }
2195
+ }
2196
+ var TAG11 = "project-type", _cache;
2197
+ var init_project_type = __esm(() => {
2198
+ init_log();
2199
+ init_pm();
2200
+ _cache = new Map;
2201
+ });
2202
+
2079
2203
  // src/verification.ts
2080
- import { execFileSync as execFileSync6, spawn } from "node:child_process";
2204
+ import { execFileSync as execFileSync7, spawn } from "node:child_process";
2081
2205
  async function runVerification(worktreePath, config, workerId) {
2082
2206
  const result = {
2083
2207
  passed: true,
@@ -2086,39 +2210,43 @@ async function runVerification(worktreePath, config, workerId) {
2086
2210
  reviewFindings: []
2087
2211
  };
2088
2212
  if (config.verification.build) {
2089
- log.info(TAG11, `[worker:${workerId}] Running build...`);
2213
+ log.info(TAG12, `[worker:${workerId}] Running build...`);
2090
2214
  result.buildErrors = runBuild(worktreePath, config.verification.timeout);
2091
2215
  if (result.buildErrors.length > 0) {
2092
- log.warn(TAG11, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
2216
+ log.warn(TAG12, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
2093
2217
  result.passed = false;
2094
2218
  } else {
2095
- log.info(TAG11, `[worker:${workerId}] Build passed`);
2219
+ log.info(TAG12, `[worker:${workerId}] Build passed`);
2096
2220
  }
2097
2221
  }
2098
2222
  if (config.verification.lint) {
2099
- log.info(TAG11, `[worker:${workerId}] Running lint...`);
2223
+ log.info(TAG12, `[worker:${workerId}] Running lint...`);
2100
2224
  result.lintWarnings = runLint(worktreePath, config.verification.timeout);
2101
2225
  if (result.lintWarnings.length > 0) {
2102
- log.warn(TAG11, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
2226
+ log.warn(TAG12, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
2103
2227
  } else {
2104
- log.info(TAG11, `[worker:${workerId}] Lint passed`);
2228
+ log.info(TAG12, `[worker:${workerId}] Lint passed`);
2105
2229
  }
2106
2230
  }
2107
2231
  if (config.verification.deepReview) {
2108
- log.info(TAG11, `[worker:${workerId}] Running deep review...`);
2232
+ log.info(TAG12, `[worker:${workerId}] Running deep review...`);
2109
2233
  result.reviewFindings = await runDeepReview(worktreePath, config, workerId);
2110
2234
  if (result.reviewFindings.length > 0) {
2111
- log.warn(TAG11, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
2235
+ log.warn(TAG12, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
2112
2236
  } else {
2113
- log.info(TAG11, `[worker:${workerId}] Deep review passed`);
2237
+ log.info(TAG12, `[worker:${workerId}] Deep review passed`);
2114
2238
  }
2115
2239
  }
2116
2240
  return result;
2117
2241
  }
2118
2242
  function runBuild(worktreePath, timeout) {
2243
+ const command = buildCommand(worktreePath);
2244
+ if (!command) {
2245
+ log.warn(TAG12, `No known build toolchain for ${worktreePath} — skipping build`);
2246
+ return [];
2247
+ }
2119
2248
  try {
2120
- const [cmd, args] = spawnRunArgs("build");
2121
- execFileSync6(cmd, args, {
2249
+ execFileSync7(command.cmd, command.args, {
2122
2250
  cwd: worktreePath,
2123
2251
  timeout,
2124
2252
  stdio: "pipe"
@@ -2129,9 +2257,13 @@ function runBuild(worktreePath, timeout) {
2129
2257
  }
2130
2258
  }
2131
2259
  function runLint(worktreePath, timeout) {
2260
+ const command = lintCommand(worktreePath);
2261
+ if (!command) {
2262
+ log.info(TAG12, `No lint step for detected toolchain in ${worktreePath} — skipping lint`);
2263
+ return [];
2264
+ }
2132
2265
  try {
2133
- const [cmd, args] = spawnRunArgs("lint");
2134
- execFileSync6(cmd, args, {
2266
+ execFileSync7(command.cmd, command.args, {
2135
2267
  cwd: worktreePath,
2136
2268
  timeout,
2137
2269
  stdio: "pipe"
@@ -2142,6 +2274,10 @@ function runLint(worktreePath, timeout) {
2142
2274
  }
2143
2275
  }
2144
2276
  async function runDeepReview(worktreePath, config, workerId) {
2277
+ if (!supportsDevServer(worktreePath)) {
2278
+ log.info(TAG12, `[worker:${workerId}] Detected non-web toolchain — skipping deep review`);
2279
+ return [];
2280
+ }
2145
2281
  const port = config.verification.devServerBasePort + workerId;
2146
2282
  let devServer = null;
2147
2283
  try {
@@ -2154,12 +2290,12 @@ async function runDeepReview(worktreePath, config, workerId) {
2154
2290
  await waitForDevServer(devServer, 30000);
2155
2291
  await probeDevServer(port);
2156
2292
  } catch (err) {
2157
- log.error(TAG11, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
2293
+ log.error(TAG12, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
2158
2294
  return [];
2159
2295
  }
2160
2296
  let diff = "";
2161
2297
  try {
2162
- diff = execFileSync6("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
2298
+ diff = execFileSync7("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
2163
2299
  } catch {
2164
2300
  diff = "(unable to retrieve diff)";
2165
2301
  }
@@ -2176,7 +2312,7 @@ async function runDeepReview(worktreePath, config, workerId) {
2176
2312
  ].join(`
2177
2313
  `);
2178
2314
  const leanSources = config.claude.leanSettingSources;
2179
- const output = execFileSync6("claude", [
2315
+ const output = execFileSync7("claude", [
2180
2316
  "--print",
2181
2317
  "--model",
2182
2318
  "sonnet",
@@ -2193,7 +2329,7 @@ async function runDeepReview(worktreePath, config, workerId) {
2193
2329
  });
2194
2330
  return parseReviewFindings(output);
2195
2331
  } catch (err) {
2196
- log.error(TAG11, `Deep review failed: ${err instanceof Error ? err.message : err}`);
2332
+ log.error(TAG12, `Deep review failed: ${err instanceof Error ? err.message : err}`);
2197
2333
  return [];
2198
2334
  } finally {
2199
2335
  if (devServer && !devServer.killed) {
@@ -2229,8 +2365,8 @@ function attemptAutoFix(worktreePath, config, errors) {
2229
2365
  "--",
2230
2366
  fixPrompt
2231
2367
  ];
2232
- log.info(TAG11, "Spawning Claude for auto-fix...");
2233
- execFileSync6("claude", args, {
2368
+ log.info(TAG12, "Spawning Claude for auto-fix...");
2369
+ execFileSync7("claude", args, {
2234
2370
  cwd: worktreePath,
2235
2371
  timeout: config.verification.timeout,
2236
2372
  stdio: "pipe"
@@ -2260,7 +2396,7 @@ async function reportFindings(client, cardId, result, recovery) {
2260
2396
  try {
2261
2397
  await client.createSubtask(cardId, title);
2262
2398
  } catch (err) {
2263
- log.error(TAG11, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
2399
+ log.error(TAG12, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
2264
2400
  }
2265
2401
  }));
2266
2402
  if (overflow > 0) {
@@ -2268,7 +2404,7 @@ async function reportFindings(client, cardId, result, recovery) {
2268
2404
  await client.createSubtask(cardId, `...and ${overflow} more issues`);
2269
2405
  } catch {}
2270
2406
  }
2271
- log.info(TAG11, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
2407
+ log.info(TAG12, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
2272
2408
  }
2273
2409
  function parseErrorOutput(err) {
2274
2410
  const stderr = err?.stderr?.toString() ?? "";
@@ -2352,10 +2488,11 @@ async function probeDevServer(port, timeoutMs = 5000) {
2352
2488
  clearTimeout(timer);
2353
2489
  }
2354
2490
  }
2355
- var TAG11 = "verification", DevServerReadinessError;
2491
+ var TAG12 = "verification", DevServerReadinessError;
2356
2492
  var init_verification = __esm(() => {
2357
2493
  init_log();
2358
2494
  init_pm();
2495
+ init_project_type();
2359
2496
  DevServerReadinessError = class DevServerReadinessError extends Error {
2360
2497
  constructor(message) {
2361
2498
  super(message);
@@ -2365,7 +2502,7 @@ var init_verification = __esm(() => {
2365
2502
  });
2366
2503
 
2367
2504
  // src/completion.ts
2368
- import { execFileSync as execFileSync7 } from "node:child_process";
2505
+ import { execFileSync as execFileSync8 } from "node:child_process";
2369
2506
  function formatTokenCount(tokens) {
2370
2507
  if (tokens >= 1e6)
2371
2508
  return `${(tokens / 1e6).toFixed(1)}M`;
@@ -2395,7 +2532,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2395
2532
  };
2396
2533
  const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
2397
2534
  if (!hasCommits) {
2398
- log.warn(TAG12, `No commits on branch ${branchName} — skipping completion`);
2535
+ log.warn(TAG13, `No commits on branch ${branchName} — skipping completion`);
2399
2536
  await client.endAgentSession(card.id, {
2400
2537
  status: "completed",
2401
2538
  progressPercent: 100,
@@ -2404,13 +2541,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2404
2541
  cleanupWorktree(worktreePath, branchName);
2405
2542
  return true;
2406
2543
  }
2407
- log.info(TAG12, `Pushing branch ${branchName} (pre-verify)...`);
2544
+ log.info(TAG13, `Pushing branch ${branchName} (pre-verify)...`);
2408
2545
  let lastPushedSha = null;
2409
2546
  try {
2410
2547
  pushBranch(branchName, worktreePath);
2411
2548
  lastPushedSha = readHeadSha(worktreePath);
2412
2549
  } catch (err) {
2413
- log.error(TAG12, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2550
+ log.error(TAG13, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2414
2551
  }
2415
2552
  const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
2416
2553
  if (config.verification.enabled) {
@@ -2425,7 +2562,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2425
2562
  let autoFixAttempts = 0;
2426
2563
  if (!result.passed && config.verification.autoFix) {
2427
2564
  for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
2428
- log.info(TAG12, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
2565
+ log.info(TAG13, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
2429
2566
  await client.updateAgentProgress(card.id, {
2430
2567
  agentIdentifier: agentIdentifier(workerId),
2431
2568
  agentName: AGENT_NAME,
@@ -2438,14 +2575,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2438
2575
  result = await runVerification(worktreePath, config, workerId);
2439
2576
  autoFixAttempts = attempt + 1;
2440
2577
  if (result.passed) {
2441
- log.info(TAG12, `Auto-fix succeeded on attempt ${attempt + 1}`);
2578
+ log.info(TAG13, `Auto-fix succeeded on attempt ${attempt + 1}`);
2442
2579
  const sha = readHeadSha(worktreePath);
2443
2580
  if (sha && sha !== lastPushedSha) {
2444
2581
  try {
2445
2582
  pushBranch(branchName, worktreePath);
2446
2583
  lastPushedSha = sha;
2447
2584
  } catch (err) {
2448
- log.warn(TAG12, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2585
+ log.warn(TAG13, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2449
2586
  }
2450
2587
  }
2451
2588
  break;
@@ -2454,14 +2591,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2454
2591
  }
2455
2592
  verificationResult = result;
2456
2593
  if (!result.passed) {
2457
- log.warn(TAG12, `Verification failed for #${card.short_id} — reporting findings`);
2594
+ log.warn(TAG13, `Verification failed for #${card.short_id} — reporting findings`);
2458
2595
  const failSha = readHeadSha(worktreePath);
2459
2596
  if (failSha && failSha !== lastPushedSha) {
2460
2597
  try {
2461
2598
  pushBranch(branchName, worktreePath);
2462
2599
  lastPushedSha = failSha;
2463
2600
  } catch (err) {
2464
- log.warn(TAG12, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2601
+ log.warn(TAG13, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2465
2602
  }
2466
2603
  }
2467
2604
  const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
@@ -2472,7 +2609,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2472
2609
  recoveryBranch: branchName
2473
2610
  });
2474
2611
  } catch (err) {
2475
- log.debug(TAG12, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
2612
+ log.debug(TAG13, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
2476
2613
  }
2477
2614
  await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
2478
2615
  await moveCardToColumn(client, card, config.verification.failColumn);
@@ -2486,7 +2623,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2486
2623
  cleanupWorktree(worktreePath, branchName);
2487
2624
  return false;
2488
2625
  }
2489
- log.info(TAG12, `Verification passed for #${card.short_id}`);
2626
+ log.info(TAG13, `Verification passed for #${card.short_id}`);
2490
2627
  }
2491
2628
  let prUrl = null;
2492
2629
  if (config.completion.createPR) {
@@ -2499,7 +2636,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2499
2636
  try {
2500
2637
  await onMovedToCompletion(card);
2501
2638
  } catch (err) {
2502
- log.warn(TAG12, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2639
+ log.warn(TAG13, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2503
2640
  }
2504
2641
  }
2505
2642
  }
@@ -2533,7 +2670,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2533
2670
  });
2534
2671
  }
2535
2672
  cleanupWorktree(worktreePath, branchName);
2536
- log.info(TAG12, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
2673
+ log.info(TAG13, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
2537
2674
  return true;
2538
2675
  }
2539
2676
  function buildVerificationFailureSummary(result, autoFixAttempts) {
@@ -2553,7 +2690,7 @@ function buildVerificationFailureSummary(result, autoFixAttempts) {
2553
2690
  }
2554
2691
  function readHeadSha(worktreePath) {
2555
2692
  try {
2556
- return execFileSync7("git", ["rev-parse", "HEAD"], {
2693
+ return execFileSync8("git", ["rev-parse", "HEAD"], {
2557
2694
  cwd: worktreePath,
2558
2695
  encoding: "utf-8"
2559
2696
  }).trim();
@@ -2563,7 +2700,7 @@ function readHeadSha(worktreePath) {
2563
2700
  }
2564
2701
  function checkHasCommits(worktreePath, baseBranch) {
2565
2702
  try {
2566
- const count = execFileSync7("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2703
+ const count = execFileSync8("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2567
2704
  return parseInt(count, 10) > 0;
2568
2705
  } catch {
2569
2706
  return false;
@@ -2572,7 +2709,7 @@ function checkHasCommits(worktreePath, baseBranch) {
2572
2709
  async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
2573
2710
  let commitLog = "";
2574
2711
  try {
2575
- commitLog = execFileSync7("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2712
+ commitLog = execFileSync8("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2576
2713
  } catch {}
2577
2714
  const SUMMARY_MARKER = `---
2578
2715
  **Agent completed**`;
@@ -2617,12 +2754,12 @@ ${commitLog}
2617
2754
  description: baseDesc + parts.join(`
2618
2755
  `)
2619
2756
  });
2620
- log.info(TAG12, `Posted completion summary to #${card.short_id}`);
2757
+ log.info(TAG13, `Posted completion summary to #${card.short_id}`);
2621
2758
  } catch (err) {
2622
- log.error(TAG12, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
2759
+ log.error(TAG13, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
2623
2760
  }
2624
2761
  }
2625
- var TAG12 = "completion";
2762
+ var TAG13 = "completion";
2626
2763
  var init_completion = __esm(() => {
2627
2764
  init_board_helpers();
2628
2765
  init_episode_writer();
@@ -2642,7 +2779,12 @@ function spawnInGroup(command, args, options = {}) {
2642
2779
  return spawn2(command, args, {
2643
2780
  ...options,
2644
2781
  detached: true,
2645
- stdio: options.stdio ?? ["ignore", "pipe", "pipe"]
2782
+ stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
2783
+ env: {
2784
+ ...process.env,
2785
+ ...options.env,
2786
+ CMUX_CLAUDE_HOOKS_DISABLED: "1"
2787
+ }
2646
2788
  });
2647
2789
  }
2648
2790
  function signalGroup(proc, signal) {
@@ -2657,7 +2799,7 @@ function signalGroup(proc, signal) {
2657
2799
  } catch (err) {
2658
2800
  const code = err.code;
2659
2801
  if (code !== "ESRCH") {
2660
- log.warn(TAG13, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
2802
+ log.warn(TAG14, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
2661
2803
  }
2662
2804
  }
2663
2805
  }
@@ -2682,7 +2824,7 @@ async function terminateGroup(proc, opts) {
2682
2824
  return;
2683
2825
  signalGroup(proc, "SIGKILL");
2684
2826
  }
2685
- var TAG13 = "pgroup";
2827
+ var TAG14 = "pgroup";
2686
2828
  var init_process_group = __esm(() => {
2687
2829
  init_log();
2688
2830
  });
@@ -2785,7 +2927,7 @@ class ProgressTracker {
2785
2927
  }
2786
2928
  onToolStart(name, input) {
2787
2929
  this.toolCallCount++;
2788
- log.debug(TAG14, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
2930
+ log.debug(TAG15, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
2789
2931
  const filePath = this.extractString(input, "file_path");
2790
2932
  if (filePath) {
2791
2933
  if (EDIT_TOOLS.has(name)) {
@@ -2856,7 +2998,7 @@ class ProgressTracker {
2856
2998
  transitionTo(newPhase) {
2857
2999
  if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
2858
3000
  return;
2859
- log.info(TAG14, `Phase: ${this.phase} → ${newPhase}`);
3001
+ log.info(TAG15, `Phase: ${this.phase} → ${newPhase}`);
2860
3002
  this.phase = newPhase;
2861
3003
  this.progress = Math.max(this.progress, PHASES[newPhase].min);
2862
3004
  this.lastAction = "";
@@ -2955,7 +3097,7 @@ class ProgressTracker {
2955
3097
  }
2956
3098
  sendUpdate(currentTask) {
2957
3099
  this.lastUpdateAt = Date.now();
2958
- log.debug(TAG14, `Progress: ${this.progress}% — ${currentTask}`);
3100
+ log.debug(TAG15, `Progress: ${this.progress}% — ${currentTask}`);
2959
3101
  this.client.updateAgentProgress(this.cardId, {
2960
3102
  agentIdentifier: agentIdentifier(this.workerId),
2961
3103
  agentName: AGENT_NAME,
@@ -2972,7 +3114,7 @@ class ProgressTracker {
2972
3114
  modelName: this.lastCost?.modelName,
2973
3115
  numTurns: this.lastCost?.numTurns ?? 0
2974
3116
  }).catch((err) => {
2975
- log.warn(TAG14, `Failed to send progress update: ${err}`);
3117
+ log.warn(TAG15, `Failed to send progress update: ${err}`);
2976
3118
  });
2977
3119
  this.flushActivityLog();
2978
3120
  }
@@ -3013,7 +3155,7 @@ class ProgressTracker {
3013
3155
  toolName: e.toolName ?? undefined
3014
3156
  }))
3015
3157
  }).catch((err) => {
3016
- log.warn(TAG14, `Failed to flush activity log: ${err}`);
3158
+ log.warn(TAG15, `Failed to flush activity log: ${err}`);
3017
3159
  this.logBuffer.unshift(...raw);
3018
3160
  if (this.logBuffer.length > MAX_LOG_BUFFER) {
3019
3161
  this.logBuffer.length = MAX_LOG_BUFFER;
@@ -3044,7 +3186,7 @@ class ProgressTracker {
3044
3186
  return null;
3045
3187
  }
3046
3188
  }
3047
- var TAG14 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_LOG_BUFFER = 500, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
3189
+ var TAG15 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_LOG_BUFFER = 500, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
3048
3190
  var init_progress_tracker = __esm(() => {
3049
3191
  init_log();
3050
3192
  init_types();
@@ -3119,7 +3261,7 @@ function parseReviewOutput(stdout) {
3119
3261
  try {
3120
3262
  const parsed = JSON.parse(raw);
3121
3263
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
3122
- log.debug(TAG15, "Parsed review output from fenced JSON block");
3264
+ log.debug(TAG16, "Parsed review output from fenced JSON block");
3123
3265
  return extractResult(parsed);
3124
3266
  }
3125
3267
  } catch {}
@@ -3145,21 +3287,21 @@ function parseReviewOutput(stdout) {
3145
3287
  try {
3146
3288
  const parsed = JSON.parse(candidates[i]);
3147
3289
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
3148
- log.debug(TAG15, "Parsed review output from raw JSON object");
3290
+ log.debug(TAG16, "Parsed review output from raw JSON object");
3149
3291
  return extractResult(parsed);
3150
3292
  }
3151
3293
  } catch {}
3152
3294
  }
3153
3295
  const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
3154
3296
  if (verdictMatch) {
3155
- log.warn(TAG15, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
3297
+ log.warn(TAG16, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
3156
3298
  return {
3157
3299
  verdict: verdictMatch[1].toLowerCase(),
3158
3300
  summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
3159
3301
  findings: []
3160
3302
  };
3161
3303
  }
3162
- log.warn(TAG15, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
3304
+ log.warn(TAG16, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
3163
3305
  return {
3164
3306
  verdict: "error",
3165
3307
  summary: stdout.slice(0, 500),
@@ -3192,7 +3334,7 @@ async function postReviewComment(client, card, commentType, body) {
3192
3334
  try {
3193
3335
  await client.addComment(card.id, body, { commentType });
3194
3336
  } catch (err) {
3195
- log.error(TAG15, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3337
+ log.error(TAG16, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3196
3338
  }
3197
3339
  }
3198
3340
  async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore) {
@@ -3206,11 +3348,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
3206
3348
  const currentCycle = getReviewCycle(freshDesc) + 1;
3207
3349
  const maxCycles = config.review.maxReviewCycles;
3208
3350
  if (result.verdict === "error") {
3209
- log.warn(TAG15, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
3351
+ log.warn(TAG16, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
3210
3352
  try {
3211
3353
  await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
3212
3354
  } catch (err) {
3213
- log.warn(TAG15, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
3355
+ log.warn(TAG16, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
3214
3356
  }
3215
3357
  if (config.review.postFindings) {
3216
3358
  const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
@@ -3253,7 +3395,7 @@ ${runLogTail}
3253
3395
  renameRemoteBranch(branchName, newRef, worktreePath);
3254
3396
  approvedBranch = newRef;
3255
3397
  } catch (err) {
3256
- log.warn(TAG15, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
3398
+ log.warn(TAG16, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
3257
3399
  }
3258
3400
  }
3259
3401
  if (config.review.createPR && approvedBranch) {
@@ -3280,14 +3422,14 @@ ${runLogTail}
3280
3422
  progressPercent: 100,
3281
3423
  ...buildTokenPayload(sessionStats)
3282
3424
  });
3283
- log.info(TAG15, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
3425
+ log.info(TAG16, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
3284
3426
  } else {
3285
3427
  const criticalFindings = result.findings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
3286
3428
  const majorFindings = result.findings.filter((f) => f.severity === "major").slice(0, MAX_FINDINGS);
3287
3429
  const linkedFindings = [...criticalFindings, ...majorFindings];
3288
3430
  const minorFindings = result.findings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
3289
3431
  if (currentCycle >= maxCycles) {
3290
- log.warn(TAG15, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
3432
+ log.warn(TAG16, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
3291
3433
  await moveCardToColumn(client, card, config.review.moveToColumn);
3292
3434
  const body = [
3293
3435
  "**Review — needs human review.**",
@@ -3338,7 +3480,7 @@ ${finding.description}${locationLine}`
3338
3480
  await client.addLinkToCard(card.id, newCardId, "relates_to");
3339
3481
  }
3340
3482
  } catch (err) {
3341
- log.error(TAG15, `Failed to create finding card: ${err instanceof Error ? err.message : err}`);
3483
+ log.error(TAG16, `Failed to create finding card: ${err instanceof Error ? err.message : err}`);
3342
3484
  }
3343
3485
  }));
3344
3486
  await Promise.all(minorFindings.map(async (finding) => {
@@ -3346,7 +3488,7 @@ ${finding.description}${locationLine}`
3346
3488
  const title = finding.title.length > 120 ? `${finding.title.slice(0, 117)}...` : finding.title;
3347
3489
  await client.createSubtask(card.id, title);
3348
3490
  } catch (err) {
3349
- log.error(TAG15, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
3491
+ log.error(TAG16, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
3350
3492
  }
3351
3493
  }));
3352
3494
  const baseDesc = stripReviewSummary(freshDesc);
@@ -3354,7 +3496,7 @@ ${finding.description}${locationLine}`
3354
3496
  try {
3355
3497
  await client.updateCard(card.id, { description: updatedDesc });
3356
3498
  } catch (err) {
3357
- log.error(TAG15, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
3499
+ log.error(TAG16, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
3358
3500
  }
3359
3501
  const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
3360
3502
  const body = [
@@ -3370,9 +3512,9 @@ ${finding.description}${locationLine}`
3370
3512
  if (config.planning.enabled && card.plan_id) {
3371
3513
  try {
3372
3514
  await client.updateCard(card.id, { needsPlanRefresh: true });
3373
- log.info(TAG15, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
3515
+ log.info(TAG16, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
3374
3516
  } catch (err) {
3375
- log.warn(TAG15, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3517
+ log.warn(TAG16, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3376
3518
  }
3377
3519
  }
3378
3520
  await moveCardToColumn(client, card, config.review.failColumn);
@@ -3386,10 +3528,10 @@ ${finding.description}${locationLine}`
3386
3528
  recoveryBranch
3387
3529
  });
3388
3530
  } catch (err) {
3389
- log.debug(TAG15, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
3531
+ log.debug(TAG16, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
3390
3532
  }
3391
3533
  if (recoveryBranch) {
3392
- log.info(TAG15, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
3534
+ log.info(TAG16, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
3393
3535
  }
3394
3536
  await client.endAgentSession(card.id, {
3395
3537
  status: "failed",
@@ -3398,7 +3540,7 @@ ${finding.description}${locationLine}`
3398
3540
  recoveryBranch,
3399
3541
  ...buildTokenPayload(sessionStats)
3400
3542
  });
3401
- log.info(TAG15, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
3543
+ log.info(TAG16, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
3402
3544
  }
3403
3545
  if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
3404
3546
  const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
@@ -3420,7 +3562,7 @@ ${finding.description}${locationLine}`
3420
3562
  cleanupWorktree(worktreePath, branchName);
3421
3563
  }
3422
3564
  }
3423
- var TAG15 = "review-completion", MAX_FINDINGS = 10, REVIEW_MARKER = `---
3565
+ var TAG16 = "review-completion", MAX_FINDINGS = 10, REVIEW_MARKER = `---
3424
3566
  **Review:`, RUN_LOG_TAIL_BYTES = 2048;
3425
3567
  var init_review_completion = __esm(() => {
3426
3568
  init_board_helpers();
@@ -3434,6 +3576,19 @@ var init_review_completion = __esm(() => {
3434
3576
 
3435
3577
  // ../harmony-shared/dist/cardLinks.js
3436
3578
  var init_cardLinks = () => {};
3579
+ // ../harmony-shared/dist/classification.js
3580
+ function escalateTier(tier) {
3581
+ const i = MODEL_TIERS.indexOf(tier);
3582
+ return MODEL_TIERS[Math.min(i + 1, MODEL_TIERS.length - 1)];
3583
+ }
3584
+ function isModelTier(v) {
3585
+ return typeof v === "string" && MODEL_TIERS.includes(v);
3586
+ }
3587
+ var MODEL_TIERS;
3588
+ var init_classification = __esm(() => {
3589
+ MODEL_TIERS = ["simple", "advanced", "research"];
3590
+ });
3591
+
3437
3592
  // ../harmony-shared/dist/commentSerializer.js
3438
3593
  function sanitizeHeaderField(value) {
3439
3594
  return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
@@ -3645,6 +3800,7 @@ var init_types2 = () => {};
3645
3800
  // ../harmony-shared/dist/index.js
3646
3801
  var init_dist = __esm(() => {
3647
3802
  init_cardLinks();
3803
+ init_classification();
3648
3804
  init_commentSerializer();
3649
3805
  init_constants();
3650
3806
  init_logger();
@@ -3766,7 +3922,7 @@ __export(exports_state_store, {
3766
3922
  StateStore: () => StateStore
3767
3923
  });
3768
3924
  import {
3769
- existsSync as existsSync4,
3925
+ existsSync as existsSync5,
3770
3926
  mkdirSync as mkdirSync2,
3771
3927
  readFileSync as readFileSync3,
3772
3928
  renameSync,
@@ -3808,18 +3964,18 @@ class StateStore {
3808
3964
  static open(path) {
3809
3965
  const resolved = path ?? defaultStatePath();
3810
3966
  const dir = dirname(resolved);
3811
- if (!existsSync4(dir))
3967
+ if (!existsSync5(dir))
3812
3968
  mkdirSync2(dir, { recursive: true });
3813
3969
  return new StateStore(resolved);
3814
3970
  }
3815
3971
  load() {
3816
- if (!existsSync4(this.path))
3972
+ if (!existsSync5(this.path))
3817
3973
  return emptyState();
3818
3974
  try {
3819
3975
  const raw = readFileSync3(this.path, "utf-8");
3820
3976
  const parsed = JSON.parse(raw);
3821
3977
  if (parsed?.version !== SCHEMA_VERSION) {
3822
- log.warn(TAG16, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — starting fresh`);
3978
+ log.warn(TAG17, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — starting fresh`);
3823
3979
  return emptyState();
3824
3980
  }
3825
3981
  return {
@@ -3832,7 +3988,7 @@ class StateStore {
3832
3988
  daily: parsed.daily ?? []
3833
3989
  };
3834
3990
  } catch (err) {
3835
- log.error(TAG16, `failed to read state file: ${err instanceof Error ? err.message : err}`);
3991
+ log.error(TAG17, `failed to read state file: ${err instanceof Error ? err.message : err}`);
3836
3992
  return emptyState();
3837
3993
  }
3838
3994
  }
@@ -3988,7 +4144,7 @@ class StateStore {
3988
4144
  return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
3989
4145
  }
3990
4146
  }
3991
- var TAG16 = "state-store", SCHEMA_VERSION = 1;
4147
+ var TAG17 = "state-store", SCHEMA_VERSION = 1;
3992
4148
  var init_state_store = __esm(() => {
3993
4149
  init_log();
3994
4150
  });
@@ -4015,7 +4171,7 @@ function normalizeToolResultContent(raw) {
4015
4171
  return String(raw);
4016
4172
  }
4017
4173
  }
4018
- var TAG17 = "stream-parser", StreamParser;
4174
+ var TAG18 = "stream-parser", StreamParser;
4019
4175
  var init_stream_parser = __esm(() => {
4020
4176
  init_log();
4021
4177
  StreamParser = class StreamParser extends EventEmitter {
@@ -4059,14 +4215,14 @@ var init_stream_parser = __esm(() => {
4059
4215
  try {
4060
4216
  msg = JSON.parse(line);
4061
4217
  } catch {
4062
- log.debug(TAG17, `Non-JSON line: ${line.slice(0, 100)}`);
4218
+ log.debug(TAG18, `Non-JSON line: ${line.slice(0, 100)}`);
4063
4219
  return;
4064
4220
  }
4065
4221
  try {
4066
4222
  this.handleMessage(msg);
4067
4223
  } catch (err) {
4068
4224
  const errMsg = err instanceof Error ? err.message : String(err);
4069
- log.warn(TAG17, `Error handling stream event: ${errMsg}`);
4225
+ log.warn(TAG18, `Error handling stream event: ${errMsg}`);
4070
4226
  this.emit("parse_error", errMsg);
4071
4227
  }
4072
4228
  }
@@ -4148,7 +4304,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
4148
4304
  const msg2 = err instanceof Error ? err.message : String(err);
4149
4305
  if (i < attempts - 1) {
4150
4306
  const wait = backoffMs * 2 ** i;
4151
- log.warn(TAG18, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
4307
+ log.warn(TAG19, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
4152
4308
  await new Promise((r) => setTimeout(r, wait));
4153
4309
  }
4154
4310
  }
@@ -4170,10 +4326,10 @@ async function runTransition(client, card, plan, opts = {}) {
4170
4326
  if (opts.strictColumn) {
4171
4327
  throw new TransitionError("move", 1, msg);
4172
4328
  }
4173
- log.warn(TAG18, `#${shortId}: ${msg} — skipping move`);
4329
+ log.warn(TAG19, `#${shortId}: ${msg} — skipping move`);
4174
4330
  } else if (card.column_id !== target.id) {
4175
4331
  await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
4176
- log.info(TAG18, `#${shortId} → "${target.name}"`);
4332
+ log.info(TAG19, `#${shortId} → "${target.name}"`);
4177
4333
  card.column_id = target.id;
4178
4334
  }
4179
4335
  }
@@ -4186,7 +4342,7 @@ async function runTransition(client, card, plan, opts = {}) {
4186
4342
  continue;
4187
4343
  await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
4188
4344
  existing.add(labelId);
4189
- log.info(TAG18, `#${shortId} +label "${name}"`);
4345
+ log.info(TAG19, `#${shortId} +label "${name}"`);
4190
4346
  }
4191
4347
  card.labelIds = Array.from(existing);
4192
4348
  }
@@ -4198,17 +4354,17 @@ async function runTransition(client, card, plan, opts = {}) {
4198
4354
  continue;
4199
4355
  await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
4200
4356
  existing.delete(match.id);
4201
- log.info(TAG18, `#${shortId} -label "${name}"`);
4357
+ log.info(TAG19, `#${shortId} -label "${name}"`);
4202
4358
  }
4203
4359
  card.labelIds = Array.from(existing);
4204
4360
  }
4205
4361
  if (plan.updateCard) {
4206
4362
  await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
4207
- log.info(TAG18, `#${shortId} updated`);
4363
+ log.info(TAG19, `#${shortId} updated`);
4208
4364
  }
4209
4365
  if (plan.endSession) {
4210
4366
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
4211
- log.info(TAG18, `#${shortId} session ended (${plan.endSession.status})`);
4367
+ log.info(TAG19, `#${shortId} session ended (${plan.endSession.status})`);
4212
4368
  }
4213
4369
  if (opts.store && opts.runId) {
4214
4370
  try {
@@ -4221,11 +4377,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
4221
4377
  const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
4222
4378
  return result?.label?.id ?? null;
4223
4379
  } catch (err) {
4224
- log.warn(TAG18, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
4380
+ log.warn(TAG19, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
4225
4381
  return null;
4226
4382
  }
4227
4383
  }
4228
- var TAG18 = "transition", TransitionError;
4384
+ var TAG19 = "transition", TransitionError;
4229
4385
  var init_transitions = __esm(() => {
4230
4386
  init_log();
4231
4387
  TransitionError = class TransitionError extends Error {
@@ -4243,7 +4399,7 @@ var init_transitions = __esm(() => {
4243
4399
  });
4244
4400
 
4245
4401
  // src/review-worker.ts
4246
- import { execFileSync as execFileSync8 } from "node:child_process";
4402
+ import { execFileSync as execFileSync9 } from "node:child_process";
4247
4403
 
4248
4404
  class ReviewWorker {
4249
4405
  config;
@@ -4309,7 +4465,7 @@ class ReviewWorker {
4309
4465
  }
4310
4466
  }
4311
4467
  get tag() {
4312
- return `${TAG19}:${this.id}`;
4468
+ return `${TAG20}:${this.id}`;
4313
4469
  }
4314
4470
  get isIdle() {
4315
4471
  return this.state === "idle";
@@ -4366,7 +4522,7 @@ class ReviewWorker {
4366
4522
  let localDiff = null;
4367
4523
  if (localMode) {
4368
4524
  log.info(this.tag, `No branch found for #${card.short_id}, attempting local review`);
4369
- this.worktreePath = execFileSync8("git", ["rev-parse", "--show-toplevel"], {
4525
+ this.worktreePath = execFileSync9("git", ["rev-parse", "--show-toplevel"], {
4370
4526
  encoding: "utf-8",
4371
4527
  timeout: 5000
4372
4528
  }).trim();
@@ -4433,7 +4589,7 @@ class ReviewWorker {
4433
4589
  if (localMode) {
4434
4590
  diff = localDiff ?? "";
4435
4591
  } else {
4436
- diff = execFileSync8("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
4592
+ diff = execFileSync9("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
4437
4593
  }
4438
4594
  } catch {
4439
4595
  diff = "(unable to retrieve diff)";
@@ -4713,7 +4869,7 @@ class ReviewWorker {
4713
4869
  }
4714
4870
  resolveLocalChanges(repoRoot, shortId) {
4715
4871
  try {
4716
- const localChanges = execFileSync8("git", ["diff", "HEAD"], {
4872
+ const localChanges = execFileSync9("git", ["diff", "HEAD"], {
4717
4873
  cwd: repoRoot,
4718
4874
  encoding: "utf-8",
4719
4875
  timeout: 5000
@@ -4725,7 +4881,7 @@ class ReviewWorker {
4725
4881
  log.warn(this.tag, "Failed to check uncommitted changes");
4726
4882
  }
4727
4883
  try {
4728
- const matchingCommits = execFileSync8("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 1e4 }).trim();
4884
+ const matchingCommits = execFileSync9("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 1e4 }).trim();
4729
4885
  if (matchingCommits) {
4730
4886
  const hashes = matchingCommits.split(`
4731
4887
  `).filter((h) => /^[0-9a-f]{4,40}$/i.test(h));
@@ -4735,7 +4891,7 @@ class ReviewWorker {
4735
4891
  const diffs = [];
4736
4892
  for (const hash of hashes) {
4737
4893
  try {
4738
- const commitDiff = execFileSync8("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30000 });
4894
+ const commitDiff = execFileSync9("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30000 });
4739
4895
  if (commitDiff)
4740
4896
  diffs.push(commitDiff);
4741
4897
  } catch {
@@ -4778,7 +4934,7 @@ class ReviewWorker {
4778
4934
  this.lastSessionStats = null;
4779
4935
  }
4780
4936
  }
4781
- var TAG19 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
4937
+ var TAG20 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
4782
4938
  var init_review_worker = __esm(() => {
4783
4939
  init_board_helpers();
4784
4940
  init_completion();
@@ -4827,7 +4983,7 @@ class SleepGuard {
4827
4983
  if (!this.child.killed)
4828
4984
  this.child.kill("SIGTERM");
4829
4985
  this.child = null;
4830
- log.info(TAG20, "sleep assertion released");
4986
+ log.info(TAG21, "sleep assertion released");
4831
4987
  }
4832
4988
  }
4833
4989
  start() {
@@ -4842,7 +4998,7 @@ class SleepGuard {
4842
4998
  spawned = true;
4843
4999
  });
4844
5000
  child.on("error", (err) => {
4845
- log.warn(TAG20, `caffeinate unavailable: ${err.message}`);
5001
+ log.warn(TAG21, `caffeinate unavailable: ${err.message}`);
4846
5002
  if (this.child === child)
4847
5003
  this.child = null;
4848
5004
  });
@@ -4855,13 +5011,13 @@ class SleepGuard {
4855
5011
  });
4856
5012
  child.unref();
4857
5013
  this.child = child;
4858
- log.info(TAG20, "sleep assertion acquired (caffeinate -i)");
5014
+ log.info(TAG21, "sleep assertion acquired (caffeinate -i)");
4859
5015
  } catch (err) {
4860
- log.warn(TAG20, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
5016
+ log.warn(TAG21, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
4861
5017
  }
4862
5018
  }
4863
5019
  }
4864
- var TAG20 = "sleep-guard";
5020
+ var TAG21 = "sleep-guard";
4865
5021
  var init_sleep_guard = __esm(() => {
4866
5022
  init_log();
4867
5023
  });
@@ -4872,7 +5028,7 @@ async function fetchBlocksLinks(client, cardId) {
4872
5028
  const { links } = await client.getCardLinks(cardId);
4873
5029
  return links.filter((l) => l.link_type === "blocks");
4874
5030
  } catch (err) {
4875
- log.warn(TAG21, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
5031
+ log.warn(TAG22, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
4876
5032
  return null;
4877
5033
  }
4878
5034
  }
@@ -4904,37 +5060,66 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
4904
5060
  const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
4905
5061
  if (successors.length === 0)
4906
5062
  return;
4907
- log.info(TAG21, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
5063
+ log.info(TAG22, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
4908
5064
  for (const link of successors) {
4909
5065
  const successorId = link.target_card.id;
4910
5066
  try {
4911
5067
  const { card } = await deps.client.getCard(successorId);
4912
5068
  if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
4913
- log.info(TAG21, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
5069
+ log.info(TAG22, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
4914
5070
  await deps.client.updateCard(successorId, {
4915
5071
  assignedAgentId: deps.agentId
4916
5072
  });
4917
5073
  } else {
4918
- log.debug(TAG21, `successor #${card.short_id} assigned to different entity — skipping`);
5074
+ log.debug(TAG22, `successor #${card.short_id} assigned to different entity — skipping`);
4919
5075
  continue;
4920
5076
  }
4921
5077
  await deps.enqueue(successorId);
4922
5078
  } catch (err) {
4923
- log.warn(TAG21, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
5079
+ log.warn(TAG22, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
4924
5080
  }
4925
5081
  }
4926
5082
  }
4927
- var TAG21 = "unblock";
5083
+ var TAG22 = "unblock";
4928
5084
  var init_unblock = __esm(() => {
4929
5085
  init_log();
4930
5086
  });
4931
5087
 
4932
5088
  // src/model-tier.ts
4933
- function chooseImplementModel(claude, priority, attempts) {
4934
- const highPriority = priority === "high" || priority === "urgent";
5089
+ function clampWithdrawn(model) {
5090
+ return WITHDRAWN_MODEL.test(model) ? MAX_IMPLEMENT_MODEL : model;
5091
+ }
5092
+ function chooseImplementModel(claude, card, attempts) {
5093
+ if (card.model_override) {
5094
+ return {
5095
+ model: clampWithdrawn(card.model_override),
5096
+ escalated: false,
5097
+ source: "override"
5098
+ };
5099
+ }
5100
+ if (isModelTier(card.model_tier)) {
5101
+ const retry = attempts >= claude.escalateAfterAttempts;
5102
+ const tier = retry ? escalateTier(card.model_tier) : card.model_tier;
5103
+ const mapped = claude.tiers?.[tier];
5104
+ return {
5105
+ model: clampWithdrawn(mapped && mapped.length > 0 ? mapped : claude.model),
5106
+ escalated: retry,
5107
+ source: "tier"
5108
+ };
5109
+ }
5110
+ const highPriority = card.priority === "high" || card.priority === "urgent";
4935
5111
  const escalated = highPriority || attempts >= claude.escalateAfterAttempts;
4936
- return { model: escalated ? claude.escalateModel : claude.model, escalated };
5112
+ return {
5113
+ model: clampWithdrawn(escalated ? claude.escalateModel : claude.model),
5114
+ escalated,
5115
+ source: "policy"
5116
+ };
4937
5117
  }
5118
+ var MAX_IMPLEMENT_MODEL = "claude-opus-4-8", WITHDRAWN_MODEL;
5119
+ var init_model_tier = __esm(() => {
5120
+ init_dist();
5121
+ WITHDRAWN_MODEL = /fable/i;
5122
+ });
4938
5123
 
4939
5124
  // src/prompt.ts
4940
5125
  async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId) {
@@ -4950,11 +5135,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
4950
5135
  Do NOT push to main. All your work stays on \`${branchName}\`.
4951
5136
  When finished, call harmony_end_agent_session with status="completed".`
4952
5137
  });
4953
- log.info(TAG22, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
5138
+ log.info(TAG23, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
4954
5139
  return result.prompt + pastEpisodesSection;
4955
5140
  } catch (err) {
4956
5141
  const msg = err instanceof Error ? err.message : String(err);
4957
- log.warn(TAG22, `Failed to generate prompt via API, using fallback: ${msg}`);
5142
+ log.warn(TAG23, `Failed to generate prompt via API, using fallback: ${msg}`);
4958
5143
  const commentsSection = await renderCommentsSection(client, card.id);
4959
5144
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
4960
5145
  }
@@ -4972,7 +5157,7 @@ async function renderCommentsSection(client, cardId) {
4972
5157
 
4973
5158
  ${section}` : "";
4974
5159
  } catch (err) {
4975
- log.warn(TAG22, "comment-thread fetch failed", {
5160
+ log.warn(TAG23, "comment-thread fetch failed", {
4976
5161
  event: "comment_fetch_failed",
4977
5162
  error: err instanceof Error ? err.message : String(err)
4978
5163
  });
@@ -5022,7 +5207,7 @@ ${description}`.trim();
5022
5207
  ## Similar past tasks
5023
5208
  ${bullets}`;
5024
5209
  } catch (err) {
5025
- log.warn(TAG22, "past-episodes recall failed", {
5210
+ log.warn(TAG23, "past-episodes recall failed", {
5026
5211
  event: "episode_recall_failed",
5027
5212
  error: err instanceof Error ? err.message : String(err)
5028
5213
  });
@@ -5063,7 +5248,7 @@ ${subtaskStr}
5063
5248
  You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
5064
5249
  Do NOT push to main. All your work stays on \`${branchName}\`.`;
5065
5250
  }
5066
- var TAG22 = "prompt";
5251
+ var TAG23 = "prompt";
5067
5252
  var init_prompt = __esm(() => {
5068
5253
  init_dist();
5069
5254
  init_log();
@@ -5142,7 +5327,7 @@ class Worker {
5142
5327
  }
5143
5328
  }
5144
5329
  get tag() {
5145
- return `${TAG23}:${this.id}`;
5330
+ return `${TAG24}:${this.id}`;
5146
5331
  }
5147
5332
  get isIdle() {
5148
5333
  return this.state === "idle";
@@ -5201,7 +5386,7 @@ class Worker {
5201
5386
  });
5202
5387
  const sid = session && typeof session === "object" && "id" in session ? session.id : null;
5203
5388
  if (!sid) {
5204
- log.warn(TAG23, "startAgentSession returned no session id");
5389
+ log.warn(TAG24, "startAgentSession returned no session id");
5205
5390
  }
5206
5391
  this.sessionId = sid;
5207
5392
  await this.recordPhase("preparing");
@@ -5377,9 +5562,9 @@ class Worker {
5377
5562
  }
5378
5563
  selectImplementModel(card) {
5379
5564
  const attempts = this.stateStore.getCard(card.id)?.attempts ?? 1;
5380
- const { model, escalated } = chooseImplementModel(this.config.claude, card.priority, attempts);
5381
- if (escalated) {
5382
- log.info(this.tag, `Escalating implement model to "${model}" (attempts=${attempts}, priority=${card.priority ?? "none"})`);
5565
+ const { model, escalated, source } = chooseImplementModel(this.config.claude, card, attempts);
5566
+ if (source !== "policy" || escalated) {
5567
+ log.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${card.model_tier ?? "none"})`);
5383
5568
  }
5384
5569
  return model;
5385
5570
  }
@@ -5709,12 +5894,13 @@ class Worker {
5709
5894
  this.runTurns = 0;
5710
5895
  }
5711
5896
  }
5712
- var TAG23 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
5897
+ var TAG24 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
5713
5898
  var init_worker = __esm(() => {
5714
5899
  init_board_helpers();
5715
5900
  init_completion();
5716
5901
  init_error_classifier();
5717
5902
  init_log();
5903
+ init_model_tier();
5718
5904
  init_plan_phase();
5719
5905
  init_process_group();
5720
5906
  init_progress_tracker();
@@ -5778,39 +5964,39 @@ class Pool {
5778
5964
  }
5779
5965
  async enqueue(card, column, labels, subtasks, mode = "implement") {
5780
5966
  if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
5781
- log.debug(TAG24, `Card ${card.id} already queued or active, skipping`);
5967
+ log.debug(TAG25, `Card ${card.id} already queued or active, skipping`);
5782
5968
  return;
5783
5969
  }
5784
5970
  if (mode === "implement") {
5785
5971
  if (this.authPaused) {
5786
- log.debug(TAG24, `#${card.short_id} held — agent paused (auth error)`);
5972
+ log.debug(TAG25, `#${card.short_id} held — agent paused (auth error)`);
5787
5973
  await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
5788
5974
  return;
5789
5975
  }
5790
5976
  const cooldownMs = this.apiCooldownRemainingMs();
5791
5977
  if (cooldownMs > 0) {
5792
- log.debug(TAG24, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
5978
+ log.debug(TAG25, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
5793
5979
  await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
5794
5980
  return;
5795
5981
  }
5796
5982
  const decision = this.budget.check(card.id);
5797
5983
  if (!decision.allow) {
5798
5984
  if (decision.reason === "daily_budget") {
5799
- log.warn(TAG24, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
5985
+ log.warn(TAG25, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
5800
5986
  await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
5801
5987
  } else {
5802
- log.debug(TAG24, `#${card.short_id} gave up: ${decision.detail}`);
5988
+ log.debug(TAG25, `#${card.short_id} gave up: ${decision.detail}`);
5803
5989
  }
5804
5990
  return;
5805
5991
  }
5806
5992
  const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
5807
5993
  if (blockers === null) {
5808
- log.warn(TAG24, `#${card.short_id} blocker check failed — deferring to next tick`);
5994
+ log.warn(TAG25, `#${card.short_id} blocker check failed — deferring to next tick`);
5809
5995
  return;
5810
5996
  }
5811
5997
  if (blockers.length > 0) {
5812
5998
  const list = blockers.map((b) => `#${b.shortId}`).join(", ");
5813
- log.info(TAG24, `#${card.short_id} blocked by ${list} — waiting`);
5999
+ log.info(TAG25, `#${card.short_id} blocked by ${list} — waiting`);
5814
6000
  await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
5815
6001
  return;
5816
6002
  }
@@ -5839,7 +6025,7 @@ class Pool {
5839
6025
  });
5840
6026
  this.lastWaitingEmit.set(cardId, currentTask);
5841
6027
  } catch (err) {
5842
- log.debug(TAG24, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
6028
+ log.debug(TAG25, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
5843
6029
  }
5844
6030
  }
5845
6031
  noteApiError(err) {
@@ -5847,7 +6033,7 @@ class Pool {
5847
6033
  return;
5848
6034
  if (err.kind === "auth") {
5849
6035
  if (!this.authPaused) {
5850
- log.error(TAG24, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
6036
+ log.error(TAG25, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
5851
6037
  }
5852
6038
  this.authPaused = true;
5853
6039
  return;
@@ -5856,7 +6042,7 @@ class Pool {
5856
6042
  const until = Date.now() + cooldownMs;
5857
6043
  if (until > this.apiCooldownUntil) {
5858
6044
  this.apiCooldownUntil = until;
5859
- log.warn(TAG24, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
6045
+ log.warn(TAG25, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
5860
6046
  }
5861
6047
  }
5862
6048
  apiCooldownRemainingMs() {
@@ -5869,13 +6055,13 @@ class Pool {
5869
6055
  const removed = queue.remove(cardId);
5870
6056
  if (removed) {
5871
6057
  this.cardDataCache.delete(cardId);
5872
- log.info(TAG24, `Removed #${removed.shortId} from ${removed.mode} queue`);
6058
+ log.info(TAG25, `Removed #${removed.shortId} from ${removed.mode} queue`);
5873
6059
  return;
5874
6060
  }
5875
6061
  }
5876
6062
  const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
5877
6063
  if (worker) {
5878
- log.info(TAG24, `Cancelling worker ${worker.id} for card ${cardId}`);
6064
+ log.info(TAG25, `Cancelling worker ${worker.id} for card ${cardId}`);
5879
6065
  await worker.cancel();
5880
6066
  }
5881
6067
  }
@@ -5903,10 +6089,10 @@ class Pool {
5903
6089
  async handleAgentCommand(cardId, command) {
5904
6090
  const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
5905
6091
  if (!worker) {
5906
- log.debug(TAG24, `No active worker for card ${cardId}, ignoring ${command}`);
6092
+ log.debug(TAG25, `No active worker for card ${cardId}, ignoring ${command}`);
5907
6093
  return;
5908
6094
  }
5909
- log.info(TAG24, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
6095
+ log.info(TAG25, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
5910
6096
  switch (command) {
5911
6097
  case "pause":
5912
6098
  await worker.pause();
@@ -5954,7 +6140,7 @@ class Pool {
5954
6140
  };
5955
6141
  }
5956
6142
  async shutdown() {
5957
- log.info(TAG24, "Shutting down pool...");
6143
+ log.info(TAG25, "Shutting down pool...");
5958
6144
  this.shuttingDown = true;
5959
6145
  const active = [
5960
6146
  ...this.implWorkers.filter((w) => w.isActive),
@@ -5962,7 +6148,7 @@ class Pool {
5962
6148
  ];
5963
6149
  await Promise.all(active.map((w) => w.cancel()));
5964
6150
  this.sleepGuard.stop();
5965
- log.info(TAG24, "Pool shutdown complete");
6151
+ log.info(TAG25, "Pool shutdown complete");
5966
6152
  }
5967
6153
  cardDataCache = new Map;
5968
6154
  tryDispatchFor(workers, queue, label) {
@@ -5970,7 +6156,7 @@ class Pool {
5970
6156
  return false;
5971
6157
  const idle = workers.find((w) => w.isIdle);
5972
6158
  if (!idle) {
5973
- log.debug(TAG24, `No idle ${label} workers (queue: ${queue.length})`);
6159
+ log.debug(TAG25, `No idle ${label} workers (queue: ${queue.length})`);
5974
6160
  return false;
5975
6161
  }
5976
6162
  const next = queue.dequeue();
@@ -5978,18 +6164,18 @@ class Pool {
5978
6164
  return false;
5979
6165
  const data = this.cardDataCache.get(next.cardId);
5980
6166
  if (!data) {
5981
- log.warn(TAG24, `No cached data for card ${next.cardId}, skipping`);
6167
+ log.warn(TAG25, `No cached data for card ${next.cardId}, skipping`);
5982
6168
  return false;
5983
6169
  }
5984
6170
  this.cardDataCache.delete(next.cardId);
5985
6171
  this.lastWaitingEmit.delete(next.cardId);
5986
- log.info(TAG24, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
6172
+ log.info(TAG25, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
5987
6173
  this.sleepGuard.acquire();
5988
6174
  idle.run(data.card, data.column, data.labels, data.subtasks);
5989
6175
  return true;
5990
6176
  }
5991
6177
  }
5992
- var TAG24 = "pool";
6178
+ var TAG25 = "pool";
5993
6179
  var init_pool = __esm(() => {
5994
6180
  init_error_classifier();
5995
6181
  init_log();
@@ -6010,7 +6196,7 @@ __export(exports_port_registry, {
6010
6196
  clearDaemonPort: () => clearDaemonPort
6011
6197
  });
6012
6198
  import {
6013
- existsSync as existsSync5,
6199
+ existsSync as existsSync6,
6014
6200
  mkdirSync as mkdirSync3,
6015
6201
  readFileSync as readFileSync4,
6016
6202
  renameSync as renameSync2,
@@ -6022,7 +6208,7 @@ function defaultRegistryPath() {
6022
6208
  return join4(homedir4(), ".harmony-mcp", "agent-ports.json");
6023
6209
  }
6024
6210
  function load(path) {
6025
- if (!existsSync5(path))
6211
+ if (!existsSync6(path))
6026
6212
  return {};
6027
6213
  try {
6028
6214
  const raw = readFileSync4(path, "utf-8");
@@ -6031,13 +6217,13 @@ function load(path) {
6031
6217
  return parsed;
6032
6218
  return {};
6033
6219
  } catch (err) {
6034
- log.warn(TAG25, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
6220
+ log.warn(TAG26, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
6035
6221
  return {};
6036
6222
  }
6037
6223
  }
6038
6224
  function save(path, registry) {
6039
6225
  const dir = dirname2(path);
6040
- if (!existsSync5(dir))
6226
+ if (!existsSync6(dir))
6041
6227
  mkdirSync3(dir, { recursive: true });
6042
6228
  const tmp = `${path}.tmp`;
6043
6229
  writeFileSync2(tmp, JSON.stringify(registry, null, 2), "utf-8");
@@ -6049,7 +6235,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
6049
6235
  registry[projectId] = { ...entry, updatedAt: Date.now() };
6050
6236
  save(path, registry);
6051
6237
  } catch (err) {
6052
- log.warn(TAG25, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6238
+ log.warn(TAG26, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6053
6239
  }
6054
6240
  }
6055
6241
  function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
@@ -6065,10 +6251,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
6065
6251
  delete registry[projectId];
6066
6252
  save(path, registry);
6067
6253
  } catch (err) {
6068
- log.warn(TAG25, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6254
+ log.warn(TAG26, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6069
6255
  }
6070
6256
  }
6071
- var TAG25 = "port-registry";
6257
+ var TAG26 = "port-registry";
6072
6258
  var init_port_registry = __esm(() => {
6073
6259
  init_log();
6074
6260
  });
@@ -6089,7 +6275,7 @@ async function fetchCardSafely(client, cardId) {
6089
6275
  const { card } = await client.getCard(cardId);
6090
6276
  return card;
6091
6277
  } catch (err) {
6092
- log.warn(TAG26, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
6278
+ log.warn(TAG27, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
6093
6279
  return null;
6094
6280
  }
6095
6281
  }
@@ -6099,7 +6285,7 @@ async function recoverOrphans(store, client, config) {
6099
6285
  return [];
6100
6286
  }
6101
6287
  const outcomes = [];
6102
- log.info(TAG26, `recovering ${active.length} orphan run(s) from prior daemon`);
6288
+ log.info(TAG27, `recovering ${active.length} orphan run(s) from prior daemon`);
6103
6289
  for (const run of active) {
6104
6290
  const outcome = {
6105
6291
  runId: run.runId,
@@ -6111,11 +6297,11 @@ async function recoverOrphans(store, client, config) {
6111
6297
  };
6112
6298
  outcomes.push(outcome);
6113
6299
  if (isProcessAlive(run.daemonPid, process.pid)) {
6114
- log.warn(TAG26, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
6300
+ log.warn(TAG27, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
6115
6301
  outcome.actions.push("skipped: daemon pid still alive");
6116
6302
  continue;
6117
6303
  }
6118
- log.info(TAG26, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
6304
+ log.info(TAG27, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
6119
6305
  await recoverRun(run, store, client, config, outcome);
6120
6306
  }
6121
6307
  return outcomes;
@@ -6133,7 +6319,7 @@ async function recoverRun(run, store, client, config, outcome) {
6133
6319
  } catch (err) {
6134
6320
  const msg = err instanceof Error ? err.message : String(err);
6135
6321
  outcome.errors.push(`endAgentSession: ${msg}`);
6136
- log.warn(TAG26, `endAgentSession failed for ${run.cardId}: ${msg}`);
6322
+ log.warn(TAG27, `endAgentSession failed for ${run.cardId}: ${msg}`);
6137
6323
  }
6138
6324
  const card = await fetchCardSafely(client, run.cardId);
6139
6325
  if (card) {
@@ -6176,9 +6362,9 @@ async function recoverRun(run, store, client, config, outcome) {
6176
6362
  const msg = err instanceof Error ? err.message : String(err);
6177
6363
  outcome.errors.push(`endRun: ${msg}`);
6178
6364
  }
6179
- log.info(TAG26, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
6365
+ log.info(TAG27, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
6180
6366
  }
6181
- var TAG26 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
6367
+ var TAG27 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
6182
6368
  var init_recovery = __esm(() => {
6183
6369
  init_board_helpers();
6184
6370
  init_log();
@@ -6226,7 +6412,7 @@ class Reconciler {
6226
6412
  clearInterval(this.timer);
6227
6413
  this.timer = null;
6228
6414
  }
6229
- log.info(TAG27, "Heartbeat stopped");
6415
+ log.info(TAG28, "Heartbeat stopped");
6230
6416
  }
6231
6417
  async recoverStaleRuns() {
6232
6418
  if (!this.stateStore || !this.agentConfig)
@@ -6243,7 +6429,7 @@ class Reconciler {
6243
6429
  if (!daemonDead && !(heartbeatStale && ourZombie))
6244
6430
  continue;
6245
6431
  const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
6246
- log.warn(TAG27, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
6432
+ log.warn(TAG28, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
6247
6433
  await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
6248
6434
  runId: run.runId,
6249
6435
  cardId: run.cardId,
@@ -6270,11 +6456,11 @@ class Reconciler {
6270
6456
  const stalledAt = Date.parse(card.updated_at ?? "");
6271
6457
  if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
6272
6458
  continue;
6273
- log.warn(TAG27, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
6459
+ log.warn(TAG28, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
6274
6460
  try {
6275
6461
  await this.client.moveCard(card.id, pickupCol.id);
6276
6462
  } catch (err) {
6277
- log.error(TAG27, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6463
+ log.error(TAG28, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6278
6464
  }
6279
6465
  }
6280
6466
  }
@@ -6297,11 +6483,11 @@ class Reconciler {
6297
6483
  const parkedAt = Date.parse(card.updated_at ?? "");
6298
6484
  if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
6299
6485
  continue;
6300
- log.warn(TAG27, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
6486
+ log.warn(TAG28, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
6301
6487
  try {
6302
6488
  await this.client.moveCard(card.id, pickupCol.id);
6303
6489
  } catch (err) {
6304
- log.error(TAG27, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6490
+ log.error(TAG28, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6305
6491
  }
6306
6492
  }
6307
6493
  }
@@ -6330,18 +6516,18 @@ class Reconciler {
6330
6516
  const subtasks = card.subtasks ?? [];
6331
6517
  const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
6332
6518
  if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
6333
- log.debug(TAG27, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
6519
+ log.debug(TAG28, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
6334
6520
  continue;
6335
6521
  }
6336
6522
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
6337
- log.debug(TAG27, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
6523
+ log.debug(TAG28, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
6338
6524
  continue;
6339
6525
  }
6340
6526
  if (mode === "review" && !extractBranchFromDescription(card.description)) {
6341
- log.debug(TAG27, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
6527
+ log.debug(TAG28, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
6342
6528
  continue;
6343
6529
  }
6344
- log.info(TAG27, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
6530
+ log.info(TAG28, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
6345
6531
  await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
6346
6532
  }
6347
6533
  }
@@ -6351,18 +6537,18 @@ class Reconciler {
6351
6537
  await this.recoverStrandedInProgress(cards, columns, knownCardIds);
6352
6538
  for (const knownId of knownCardIds) {
6353
6539
  if (!allAgentCardIds.has(knownId)) {
6354
- log.info(TAG27, `Missed unassign: ${knownId} — removing`);
6540
+ log.info(TAG28, `Missed unassign: ${knownId} — removing`);
6355
6541
  await this.pool.removeCard(knownId);
6356
6542
  }
6357
6543
  }
6358
6544
  await this.releaseStalledApprovals(cards, columns, knownCardIds);
6359
- log.debug(TAG27, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
6545
+ log.debug(TAG28, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
6360
6546
  } catch (err) {
6361
- log.error(TAG27, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
6547
+ log.error(TAG28, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
6362
6548
  }
6363
6549
  }
6364
6550
  }
6365
- var TAG27 = "reconcile";
6551
+ var TAG28 = "reconcile";
6366
6552
  var init_reconcile = __esm(() => {
6367
6553
  init_board_helpers();
6368
6554
  init_log();
@@ -6400,7 +6586,7 @@ function prettyBanner(config, version) {
6400
6586
  checks.push({ kind: "ok", message });
6401
6587
  },
6402
6588
  warn(message) {
6403
- log.warn(TAG28, message);
6589
+ log.warn(TAG29, message);
6404
6590
  checks.push({ kind: "warn", message: message.split(`
6405
6591
  `, 1)[0] });
6406
6592
  },
@@ -6425,25 +6611,25 @@ function prettyBanner(config, version) {
6425
6611
  };
6426
6612
  }
6427
6613
  function jsonBanner(config, version) {
6428
- log.info(TAG28, `Harmony Agent Daemon v${version} starting...`);
6429
- log.info(TAG28, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
6614
+ log.info(TAG29, `Harmony Agent Daemon v${version} starting...`);
6615
+ log.info(TAG29, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
6430
6616
  if (config.agent.review.enabled) {
6431
- log.info(TAG28, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
6617
+ log.info(TAG29, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
6432
6618
  }
6433
6619
  let failed = false;
6434
6620
  return {
6435
6621
  setProjectName(_name) {},
6436
6622
  setGitProvider(provider) {
6437
- log.info(TAG28, `Git provider: ${provider}`);
6623
+ log.info(TAG29, `Git provider: ${provider}`);
6438
6624
  },
6439
6625
  setHttpPort(port) {
6440
- log.info(TAG28, `HTTP server on port ${port}`);
6626
+ log.info(TAG29, `HTTP server on port ${port}`);
6441
6627
  },
6442
6628
  check(message) {
6443
- log.info(TAG28, message);
6629
+ log.info(TAG29, message);
6444
6630
  },
6445
6631
  warn(message) {
6446
- log.warn(TAG28, message);
6632
+ log.warn(TAG29, message);
6447
6633
  },
6448
6634
  fail() {
6449
6635
  failed = true;
@@ -6451,7 +6637,7 @@ function jsonBanner(config, version) {
6451
6637
  async ready(message) {
6452
6638
  if (failed)
6453
6639
  return;
6454
- log.info(TAG28, message);
6640
+ log.info(TAG29, message);
6455
6641
  }
6456
6642
  };
6457
6643
  }
@@ -6528,7 +6714,7 @@ function cyan(s) {
6528
6714
  function yellow(s) {
6529
6715
  return `${ANSI.yellow}${s}${ANSI.reset}`;
6530
6716
  }
6531
- var TAG28 = "daemon", RULE_WIDTH = 70, ANSI;
6717
+ var TAG29 = "daemon", RULE_WIDTH = 70, ANSI;
6532
6718
  var init_startup_banner = __esm(() => {
6533
6719
  init_log();
6534
6720
  ANSI = {
@@ -6675,18 +6861,18 @@ class Watcher {
6675
6861
  }
6676
6862
  async start() {
6677
6863
  if (!isPretty()) {
6678
- log.info(TAG29, "Connecting to Supabase realtime (broadcast)...");
6864
+ log.info(TAG30, "Connecting to Supabase realtime (broadcast)...");
6679
6865
  }
6680
6866
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
6681
6867
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
6682
6868
  const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
6683
- log.debug(TAG29, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
6869
+ log.debug(TAG30, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
6684
6870
  this.onCardBroadcast({
6685
6871
  event: "card_update",
6686
6872
  payload: msg.payload ?? {}
6687
6873
  });
6688
6874
  }).on("broadcast", { event: "card_created" }, (msg) => {
6689
- log.debug(TAG29, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
6875
+ log.debug(TAG30, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
6690
6876
  this.onCardBroadcast({
6691
6877
  event: "card_created",
6692
6878
  payload: msg.payload ?? {}
@@ -6696,29 +6882,29 @@ class Watcher {
6696
6882
  const cardId = payload.card_id;
6697
6883
  const command = payload.command;
6698
6884
  if (cardId && command) {
6699
- log.info(TAG29, `Broadcast: agent_command ${command} for ${cardId}`);
6885
+ log.info(TAG30, `Broadcast: agent_command ${command} for ${cardId}`);
6700
6886
  this.onAgentCommand?.({ cardId, command });
6701
6887
  }
6702
6888
  }).subscribe((status) => {
6703
6889
  if (status === "SUBSCRIBED") {
6704
6890
  this.connected = true;
6705
6891
  if (!isPretty() || !this.suppressStartupLogs) {
6706
- log.info(TAG29, "Broadcast subscription active");
6892
+ log.info(TAG30, "Broadcast subscription active");
6707
6893
  }
6708
6894
  this.maybeResolveReady();
6709
6895
  } else if (status === "CHANNEL_ERROR") {
6710
6896
  this.connected = false;
6711
- log.error(TAG29, "Broadcast channel error — will rely on reconciliation");
6897
+ log.error(TAG30, "Broadcast channel error — will rely on reconciliation");
6712
6898
  } else if (status === "TIMED_OUT") {
6713
6899
  this.connected = false;
6714
- log.warn(TAG29, "Broadcast subscription timed out — retrying...");
6900
+ log.warn(TAG30, "Broadcast subscription timed out — retrying...");
6715
6901
  } else if (status === "CLOSED") {
6716
6902
  this.connected = false;
6717
6903
  }
6718
6904
  });
6719
6905
  this.channel = channel;
6720
6906
  presenceChannel.on("presence", { event: "sync" }, () => {
6721
- log.debug(TAG29, "Presence sync");
6907
+ log.debug(TAG30, "Presence sync");
6722
6908
  }).subscribe(async (status) => {
6723
6909
  if (status === "SUBSCRIBED") {
6724
6910
  await presenceChannel.track({
@@ -6731,7 +6917,7 @@ class Watcher {
6731
6917
  agentName: this.identity.agentName
6732
6918
  });
6733
6919
  if (!isPretty() || !this.suppressStartupLogs) {
6734
- log.info(TAG29, "Presence tracked on board-presence channel");
6920
+ log.info(TAG30, "Presence tracked on board-presence channel");
6735
6921
  }
6736
6922
  this.presenceTracked = true;
6737
6923
  this.maybeResolveReady();
@@ -6753,10 +6939,10 @@ class Watcher {
6753
6939
  this.supabase = null;
6754
6940
  }
6755
6941
  this.connected = false;
6756
- log.info(TAG29, "Broadcast subscription stopped");
6942
+ log.info(TAG30, "Broadcast subscription stopped");
6757
6943
  }
6758
6944
  }
6759
- var TAG29 = "watcher";
6945
+ var TAG30 = "watcher";
6760
6946
  var init_watcher = __esm(() => {
6761
6947
  init_log();
6762
6948
  });
@@ -6769,8 +6955,8 @@ __export(exports_worktree_gc, {
6769
6955
  isTransientGitNetworkError: () => isTransientGitNetworkError,
6770
6956
  WorktreeGc: () => WorktreeGc
6771
6957
  });
6772
- import { execFileSync as execFileSync9 } from "node:child_process";
6773
- import { readdirSync, statSync as statSync2 } from "node:fs";
6958
+ import { execFileSync as execFileSync10 } from "node:child_process";
6959
+ import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
6774
6960
  import { resolve as resolve3 } from "node:path";
6775
6961
  function isTransientGitNetworkError(message) {
6776
6962
  return TRANSIENT_GIT_NETWORK_ERROR.test(message);
@@ -6795,7 +6981,7 @@ function runWorktreeGc(basePath, store, opts = {}) {
6795
6981
  const baseAbs = resolve3(repoRoot, basePath);
6796
6982
  let entries;
6797
6983
  try {
6798
- entries = readdirSync(baseAbs);
6984
+ entries = readdirSync2(baseAbs);
6799
6985
  } catch {
6800
6986
  return result;
6801
6987
  }
@@ -6837,16 +7023,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
6837
7023
  }
6838
7024
  }
6839
7025
  try {
6840
- execFileSync9("git", ["worktree", "prune", "--expire=now"], {
7026
+ execFileSync10("git", ["worktree", "prune", "--expire=now"], {
6841
7027
  cwd: repoRoot,
6842
7028
  stdio: "pipe"
6843
7029
  });
6844
7030
  } catch {}
6845
7031
  if (result.removed.length > 0) {
6846
- log.info(TAG30, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
7032
+ log.info(TAG31, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
6847
7033
  }
6848
7034
  if (result.errors.length > 0) {
6849
- log.warn(TAG30, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
7035
+ log.warn(TAG31, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
6850
7036
  }
6851
7037
  return result;
6852
7038
  }
@@ -6868,7 +7054,7 @@ function pruneFailedRemoteBranches(opts) {
6868
7054
  return result;
6869
7055
  }
6870
7056
  try {
6871
- execFileSync9("git", ["fetch", "--prune", "origin"], {
7057
+ execFileSync10("git", ["fetch", "--prune", "origin"], {
6872
7058
  cwd: repoRoot,
6873
7059
  stdio: "pipe",
6874
7060
  ...GIT_NETWORK_EXEC
@@ -6876,7 +7062,7 @@ function pruneFailedRemoteBranches(opts) {
6876
7062
  } catch (err) {
6877
7063
  const detail = gitErrorDetail(err);
6878
7064
  if (isTransientGitNetworkError(detail)) {
6879
- log.debug(TAG30, `Remote branch GC skipped — remote unreachable: ${detail}`);
7065
+ log.debug(TAG31, `Remote branch GC skipped — remote unreachable: ${detail}`);
6880
7066
  return result;
6881
7067
  }
6882
7068
  result.errors.push({ ref: "fetch", error: detail });
@@ -6884,7 +7070,7 @@ function pruneFailedRemoteBranches(opts) {
6884
7070
  const refPattern = `refs/remotes/origin/${opts.prefix}*`;
6885
7071
  let listing = "";
6886
7072
  try {
6887
- listing = execFileSync9("git", [
7073
+ listing = execFileSync10("git", [
6888
7074
  "for-each-ref",
6889
7075
  "--format=%(refname:strip=3) %(committerdate:unix)",
6890
7076
  refPattern
@@ -6915,11 +7101,11 @@ function pruneFailedRemoteBranches(opts) {
6915
7101
  continue;
6916
7102
  }
6917
7103
  if (clock() > sweepDeadline) {
6918
- log.debug(TAG30, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
7104
+ log.debug(TAG31, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
6919
7105
  break;
6920
7106
  }
6921
7107
  try {
6922
- execFileSync9("git", ["push", "origin", `:refs/heads/${ref}`], {
7108
+ execFileSync10("git", ["push", "origin", `:refs/heads/${ref}`], {
6923
7109
  cwd: repoRoot,
6924
7110
  stdio: "pipe",
6925
7111
  ...GIT_NETWORK_EXEC
@@ -6928,17 +7114,17 @@ function pruneFailedRemoteBranches(opts) {
6928
7114
  } catch (err) {
6929
7115
  const detail = gitErrorDetail(err);
6930
7116
  if (isTransientGitNetworkError(detail)) {
6931
- log.debug(TAG30, `Remote branch GC interrupted — remote unreachable: ${detail}`);
7117
+ log.debug(TAG31, `Remote branch GC interrupted — remote unreachable: ${detail}`);
6932
7118
  break;
6933
7119
  }
6934
7120
  result.errors.push({ ref, error: detail });
6935
7121
  }
6936
7122
  }
6937
7123
  if (result.removed.length > 0) {
6938
- log.info(TAG30, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
7124
+ log.info(TAG31, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
6939
7125
  }
6940
7126
  if (result.errors.length > 0) {
6941
- log.warn(TAG30, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
7127
+ log.warn(TAG31, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
6942
7128
  }
6943
7129
  return result;
6944
7130
  }
@@ -6969,27 +7155,27 @@ class WorktreeGc {
6969
7155
  try {
6970
7156
  runWorktreeGc(this.basePath, this.store);
6971
7157
  } catch (err) {
6972
- log.warn(TAG30, `GC tick failed: ${err instanceof Error ? err.message : err}`);
7158
+ log.warn(TAG31, `GC tick failed: ${err instanceof Error ? err.message : err}`);
6973
7159
  }
6974
7160
  if (this.remoteOpts) {
6975
7161
  try {
6976
7162
  pruneFailedRemoteBranches(this.remoteOpts);
6977
7163
  } catch (err) {
6978
- log.warn(TAG30, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
7164
+ log.warn(TAG31, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
6979
7165
  }
6980
7166
  }
6981
7167
  }
6982
7168
  }
6983
7169
  function getRepoRoot2() {
6984
7170
  try {
6985
- return execFileSync9("git", ["rev-parse", "--show-toplevel"], {
7171
+ return execFileSync10("git", ["rev-parse", "--show-toplevel"], {
6986
7172
  encoding: "utf-8"
6987
7173
  }).trim();
6988
7174
  } catch {
6989
7175
  return null;
6990
7176
  }
6991
7177
  }
6992
- var TAG30 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
7178
+ var TAG31 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
6993
7179
  var init_worktree_gc = __esm(() => {
6994
7180
  init_log();
6995
7181
  init_worktree();
@@ -7023,12 +7209,12 @@ __export(exports_src, {
7023
7209
  validatePrerequisites: () => validatePrerequisites,
7024
7210
  main: () => main
7025
7211
  });
7026
- import { execFileSync as execFileSync10 } from "node:child_process";
7212
+ import { execFileSync as execFileSync11 } from "node:child_process";
7027
7213
  import { randomUUID as randomUUID2 } from "node:crypto";
7028
7214
  import { createRequire as createRequire2 } from "node:module";
7029
7215
  async function validatePrerequisites(config, banner) {
7030
7216
  try {
7031
- const ver = execFileSync10("claude", ["--version"], {
7217
+ const ver = execFileSync11("claude", ["--version"], {
7032
7218
  encoding: "utf-8"
7033
7219
  }).trim();
7034
7220
  banner.check(`Claude CLI ${ver}`);
@@ -7043,14 +7229,14 @@ async function validatePrerequisites(config, banner) {
7043
7229
  validateGitProviderCli(provider);
7044
7230
  }
7045
7231
  try {
7046
- const status = execFileSync10("git", ["status", "--porcelain"], {
7232
+ const status = execFileSync11("git", ["status", "--porcelain"], {
7047
7233
  encoding: "utf-8"
7048
7234
  }).trim();
7049
7235
  if (status) {
7050
7236
  banner.warn(`Working directory has uncommitted changes:
7051
7237
  ${status}`);
7052
7238
  }
7053
- execFileSync10("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
7239
+ execFileSync11("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
7054
7240
  encoding: "utf-8",
7055
7241
  stdio: "pipe"
7056
7242
  });
@@ -7093,7 +7279,7 @@ async function main() {
7093
7279
  } catch (err) {
7094
7280
  if (err instanceof ConfigValidationError) {
7095
7281
  banner.fail();
7096
- log.error(TAG31, err.message);
7282
+ log.error(TAG32, err.message);
7097
7283
  process.exit(1);
7098
7284
  }
7099
7285
  throw err;
@@ -7202,7 +7388,7 @@ async function main() {
7202
7388
  if (shuttingDown)
7203
7389
  return;
7204
7390
  shuttingDown = true;
7205
- log.info(TAG31, `Received ${signal}, shutting down gracefully...`);
7391
+ log.info(TAG32, `Received ${signal}, shutting down gracefully...`);
7206
7392
  reconciler.stop();
7207
7393
  mergeMonitor?.stop();
7208
7394
  worktreeGc.stop();
@@ -7212,18 +7398,18 @@ async function main() {
7212
7398
  }
7213
7399
  await watcher.stop();
7214
7400
  await pool.shutdown();
7215
- log.info(TAG31, "Daemon stopped.");
7401
+ log.info(TAG32, "Daemon stopped.");
7216
7402
  process.exit(exitCode);
7217
7403
  };
7218
7404
  process.on("SIGINT", () => shutdown("SIGINT"));
7219
7405
  process.on("SIGTERM", () => shutdown("SIGTERM"));
7220
7406
  process.on("uncaughtException", (err) => {
7221
- log.error(TAG31, `Uncaught exception: ${err.message}`);
7407
+ log.error(TAG32, `Uncaught exception: ${err.message}`);
7222
7408
  exitCode = 1;
7223
7409
  shutdown("uncaughtException");
7224
7410
  });
7225
7411
  process.on("unhandledRejection", (reason) => {
7226
- log.error(TAG31, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
7412
+ log.error(TAG32, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
7227
7413
  exitCode = 1;
7228
7414
  shutdown("unhandledRejection");
7229
7415
  });
@@ -7276,34 +7462,34 @@ async function handleBroadcast(event, client, pool, config, agentId) {
7276
7462
  if (assignedAgentId === undefined)
7277
7463
  return;
7278
7464
  if (assignedAgentId === agentId) {
7279
- log.info(TAG31, `Broadcast: card ${cardId} assigned to agent`);
7465
+ log.info(TAG32, `Broadcast: card ${cardId} assigned to agent`);
7280
7466
  try {
7281
7467
  await tryEnqueueCard(cardId, client, pool, config, agentId);
7282
7468
  } catch (err) {
7283
- log.error(TAG31, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
7469
+ log.error(TAG32, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
7284
7470
  }
7285
7471
  } else if (pool.isCardKnown(cardId)) {
7286
- log.info(TAG31, `Broadcast: card ${cardId} unassigned from agent`);
7472
+ log.info(TAG32, `Broadcast: card ${cardId} unassigned from agent`);
7287
7473
  await pool.removeCard(cardId);
7288
7474
  }
7289
7475
  }
7290
7476
  async function tryEnqueueCard(cardId, client, pool, config, agentId) {
7291
7477
  const { card } = await client.getCard(cardId);
7292
7478
  if (card.assigned_agent_id !== agentId) {
7293
- log.debug(TAG31, `Card ${cardId} no longer assigned to agent — skipping`);
7479
+ log.debug(TAG32, `Card ${cardId} no longer assigned to agent — skipping`);
7294
7480
  return;
7295
7481
  }
7296
7482
  const board = await client.getBoard(config.projectId, { summary: true });
7297
7483
  const columns = board.columns;
7298
7484
  const column = columns.find((c) => c.id === card.column_id);
7299
7485
  if (!column) {
7300
- log.warn(TAG31, `Column not found for card ${cardId}`);
7486
+ log.warn(TAG32, `Column not found for card ${cardId}`);
7301
7487
  return;
7302
7488
  }
7303
7489
  const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
7304
7490
  const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
7305
7491
  if (!isPickupColumn && !isReviewColumn) {
7306
- log.info(TAG31, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
7492
+ log.info(TAG32, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
7307
7493
  return;
7308
7494
  }
7309
7495
  const mode = isReviewColumn ? "review" : "implement";
@@ -7311,16 +7497,16 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
7311
7497
  const cardLabels = resolveCardLabels(card, labelMap);
7312
7498
  const subtasks = card.subtasks ?? [];
7313
7499
  if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
7314
- log.debug(TAG31, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
7500
+ log.debug(TAG32, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
7315
7501
  return;
7316
7502
  }
7317
7503
  if (mode === "review" && !extractBranchFromDescription(card.description)) {
7318
- log.info(TAG31, `Card #${card.short_id} has no branch reference — skipping auto-review`);
7504
+ log.info(TAG32, `Card #${card.short_id} has no branch reference — skipping auto-review`);
7319
7505
  return;
7320
7506
  }
7321
7507
  await pool.enqueue(card, column, cardLabels, subtasks, mode);
7322
7508
  }
7323
- var TAG31 = "daemon", PKG_VERSION;
7509
+ var TAG32 = "daemon", PKG_VERSION;
7324
7510
  var init_src = __esm(() => {
7325
7511
  init_board_helpers();
7326
7512
  init_config();