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