@gethmy/agent 1.10.8 → 1.10.9

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 (3) hide show
  1. package/dist/cli.js +359 -239
  2. package/dist/index.js +359 -239
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -423,6 +423,7 @@ var init_types = __esm(() => {
423
423
  autoFix: true,
424
424
  maxFixAttempts: 1,
425
425
  deepReview: false,
426
+ revertGuard: true,
426
427
  devServerBasePort: 4200,
427
428
  timeout: 120000,
428
429
  failColumn: "To Do"
@@ -1208,6 +1209,23 @@ var init_pm = __esm(() => {
1208
1209
  import { execFileSync as execFileSync3, execSync as execSync2 } from "node:child_process";
1209
1210
  import { existsSync as existsSync2, rmSync } from "node:fs";
1210
1211
  import { resolve } from "node:path";
1212
+ function fetchBaseBranch(repoRoot, baseBranch, attempts = 3, fetchImpl = (root, branch) => execFileSync3("git", ["fetch", "origin", branch], {
1213
+ cwd: root,
1214
+ stdio: "pipe"
1215
+ })) {
1216
+ let lastErr;
1217
+ for (let attempt = 1;attempt <= attempts; attempt++) {
1218
+ try {
1219
+ fetchImpl(repoRoot, baseBranch);
1220
+ return;
1221
+ } catch (err) {
1222
+ lastErr = err;
1223
+ log.warn(TAG5, `fetch origin ${baseBranch} failed (attempt ${attempt}/${attempts})`);
1224
+ }
1225
+ }
1226
+ const detail = lastErr instanceof Error ? lastErr.message : String(lastErr);
1227
+ throw new WorktreeBaseError(`Could not fetch origin/${baseBranch} after ${attempts} attempts — ` + `refusing to build on a stale base. ${detail}`);
1228
+ }
1211
1229
  function createWorktree(basePath, baseBranch, branchName) {
1212
1230
  const repoRoot = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
1213
1231
  encoding: "utf-8"
@@ -1223,14 +1241,7 @@ function createWorktree(basePath, baseBranch, branchName) {
1223
1241
  stdio: "pipe"
1224
1242
  });
1225
1243
  } catch {}
1226
- try {
1227
- execFileSync3("git", ["fetch", "origin", baseBranch], {
1228
- cwd: repoRoot,
1229
- stdio: "pipe"
1230
- });
1231
- } catch {
1232
- log.warn(TAG5, "Failed to fetch latest — continuing with local state");
1233
- }
1244
+ fetchBaseBranch(repoRoot, baseBranch);
1234
1245
  log.info(TAG5, `Creating worktree: ${worktreeDir} (branch: ${branchName})`);
1235
1246
  try {
1236
1247
  execFileSync3("git", [
@@ -1318,10 +1329,16 @@ function makeBranchName(shortId, title, prefix = "agent-attempts/") {
1318
1329
  const slug = title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
1319
1330
  return `${prefix}${shortId}-${slug || "task"}`;
1320
1331
  }
1321
- var TAG5 = "worktree";
1332
+ var TAG5 = "worktree", WorktreeBaseError;
1322
1333
  var init_worktree = __esm(() => {
1323
1334
  init_log();
1324
1335
  init_pm();
1336
+ WorktreeBaseError = class WorktreeBaseError extends Error {
1337
+ constructor(message) {
1338
+ super(message);
1339
+ this.name = "WorktreeBaseError";
1340
+ }
1341
+ };
1325
1342
  });
1326
1343
 
1327
1344
  // src/review-worktree.ts
@@ -2199,41 +2216,91 @@ var init_project_type = __esm(() => {
2199
2216
  _cache = new Map;
2200
2217
  });
2201
2218
 
2219
+ // src/revert-guard.ts
2220
+ import { execFileSync as execFileSync7 } from "node:child_process";
2221
+ function isTestFile(path) {
2222
+ return TEST_FILE.test(path);
2223
+ }
2224
+ function filterTestFiles(paths) {
2225
+ return paths.filter(isTestFile);
2226
+ }
2227
+ function refetchBase(worktreePath, baseBranch) {
2228
+ try {
2229
+ execFileSync7("git", ["fetch", "origin", baseBranch], {
2230
+ cwd: worktreePath,
2231
+ stdio: "pipe"
2232
+ });
2233
+ } catch {
2234
+ log.warn(TAG12, "Failed to re-fetch base for revert guard — using last fetch");
2235
+ }
2236
+ }
2237
+ function listDeletedFilesAgainstBase(worktreePath, baseBranch) {
2238
+ try {
2239
+ const out = execFileSync7("git", ["diff", "--diff-filter=D", "--name-only", `origin/${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8" });
2240
+ return out.split(`
2241
+ `).map((l) => l.trim()).filter((l) => l.length > 0);
2242
+ } catch (err) {
2243
+ log.warn(TAG12, `Failed to list deleted files: ${err instanceof Error ? err.message : err}`);
2244
+ return [];
2245
+ }
2246
+ }
2247
+ function findDeletedTestFiles(worktreePath, baseBranch) {
2248
+ refetchBase(worktreePath, baseBranch);
2249
+ return filterTestFiles(listDeletedFilesAgainstBase(worktreePath, baseBranch));
2250
+ }
2251
+ var TAG12 = "revert-guard", TEST_FILE;
2252
+ var init_revert_guard = __esm(() => {
2253
+ init_log();
2254
+ TEST_FILE = /(?:^|\/)__tests__\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
2255
+ });
2256
+
2202
2257
  // src/verification.ts
2203
- import { execFileSync as execFileSync7, spawn } from "node:child_process";
2258
+ import { execFileSync as execFileSync8, spawn } from "node:child_process";
2204
2259
  async function runVerification(worktreePath, config, workerId) {
2205
2260
  const result = {
2206
2261
  passed: true,
2207
2262
  buildErrors: [],
2208
2263
  lintWarnings: [],
2209
- reviewFindings: []
2264
+ reviewFindings: [],
2265
+ revertWarnings: []
2210
2266
  };
2267
+ if (config.verification.revertGuard) {
2268
+ log.info(TAG13, `[worker:${workerId}] Checking for reverted merged work...`);
2269
+ const deletedTests = findDeletedTestFiles(worktreePath, config.worktree.baseBranch);
2270
+ if (deletedTests.length > 0) {
2271
+ result.revertWarnings = deletedTests.map((f) => `Branch deletes test file '${f}' relative to current ${config.worktree.baseBranch} — ` + "likely an accidental revert of already-merged work. Restore the test or rebase on current main.");
2272
+ log.warn(TAG13, `[worker:${workerId}] Revert guard tripped: ${deletedTests.length} deleted test file(s)`);
2273
+ result.passed = false;
2274
+ } else {
2275
+ log.info(TAG13, `[worker:${workerId}] Revert guard passed`);
2276
+ }
2277
+ }
2211
2278
  if (config.verification.build) {
2212
- log.info(TAG12, `[worker:${workerId}] Running build...`);
2279
+ log.info(TAG13, `[worker:${workerId}] Running build...`);
2213
2280
  result.buildErrors = runBuild(worktreePath, config.verification.timeout);
2214
2281
  if (result.buildErrors.length > 0) {
2215
- log.warn(TAG12, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
2282
+ log.warn(TAG13, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
2216
2283
  result.passed = false;
2217
2284
  } else {
2218
- log.info(TAG12, `[worker:${workerId}] Build passed`);
2285
+ log.info(TAG13, `[worker:${workerId}] Build passed`);
2219
2286
  }
2220
2287
  }
2221
2288
  if (config.verification.lint) {
2222
- log.info(TAG12, `[worker:${workerId}] Running lint...`);
2289
+ log.info(TAG13, `[worker:${workerId}] Running lint...`);
2223
2290
  result.lintWarnings = runLint(worktreePath, config.verification.timeout);
2224
2291
  if (result.lintWarnings.length > 0) {
2225
- log.warn(TAG12, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
2292
+ log.warn(TAG13, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
2226
2293
  } else {
2227
- log.info(TAG12, `[worker:${workerId}] Lint passed`);
2294
+ log.info(TAG13, `[worker:${workerId}] Lint passed`);
2228
2295
  }
2229
2296
  }
2230
2297
  if (config.verification.deepReview) {
2231
- log.info(TAG12, `[worker:${workerId}] Running deep review...`);
2298
+ log.info(TAG13, `[worker:${workerId}] Running deep review...`);
2232
2299
  result.reviewFindings = await runDeepReview(worktreePath, config, workerId);
2233
2300
  if (result.reviewFindings.length > 0) {
2234
- log.warn(TAG12, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
2301
+ log.warn(TAG13, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
2235
2302
  } else {
2236
- log.info(TAG12, `[worker:${workerId}] Deep review passed`);
2303
+ log.info(TAG13, `[worker:${workerId}] Deep review passed`);
2237
2304
  }
2238
2305
  }
2239
2306
  return result;
@@ -2241,11 +2308,11 @@ async function runVerification(worktreePath, config, workerId) {
2241
2308
  function runBuild(worktreePath, timeout) {
2242
2309
  const command = buildCommand(worktreePath);
2243
2310
  if (!command) {
2244
- log.warn(TAG12, `No known build toolchain for ${worktreePath} — skipping build`);
2311
+ log.warn(TAG13, `No known build toolchain for ${worktreePath} — skipping build`);
2245
2312
  return [];
2246
2313
  }
2247
2314
  try {
2248
- execFileSync7(command.cmd, command.args, {
2315
+ execFileSync8(command.cmd, command.args, {
2249
2316
  cwd: worktreePath,
2250
2317
  timeout,
2251
2318
  stdio: "pipe"
@@ -2258,11 +2325,11 @@ function runBuild(worktreePath, timeout) {
2258
2325
  function runLint(worktreePath, timeout) {
2259
2326
  const command = lintCommand(worktreePath);
2260
2327
  if (!command) {
2261
- log.info(TAG12, `No lint step for detected toolchain in ${worktreePath} — skipping lint`);
2328
+ log.info(TAG13, `No lint step for detected toolchain in ${worktreePath} — skipping lint`);
2262
2329
  return [];
2263
2330
  }
2264
2331
  try {
2265
- execFileSync7(command.cmd, command.args, {
2332
+ execFileSync8(command.cmd, command.args, {
2266
2333
  cwd: worktreePath,
2267
2334
  timeout,
2268
2335
  stdio: "pipe"
@@ -2274,7 +2341,7 @@ function runLint(worktreePath, timeout) {
2274
2341
  }
2275
2342
  async function runDeepReview(worktreePath, config, workerId) {
2276
2343
  if (!supportsDevServer(worktreePath)) {
2277
- log.info(TAG12, `[worker:${workerId}] Detected non-web toolchain — skipping deep review`);
2344
+ log.info(TAG13, `[worker:${workerId}] Detected non-web toolchain — skipping deep review`);
2278
2345
  return [];
2279
2346
  }
2280
2347
  const port = config.verification.devServerBasePort + workerId;
@@ -2289,12 +2356,12 @@ async function runDeepReview(worktreePath, config, workerId) {
2289
2356
  await waitForDevServer(devServer, 30000);
2290
2357
  await probeDevServer(port);
2291
2358
  } catch (err) {
2292
- log.error(TAG12, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
2359
+ log.error(TAG13, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
2293
2360
  return [];
2294
2361
  }
2295
2362
  let diff = "";
2296
2363
  try {
2297
- diff = execFileSync7("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
2364
+ diff = execFileSync8("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
2298
2365
  } catch {
2299
2366
  diff = "(unable to retrieve diff)";
2300
2367
  }
@@ -2311,7 +2378,7 @@ async function runDeepReview(worktreePath, config, workerId) {
2311
2378
  ].join(`
2312
2379
  `);
2313
2380
  const leanSources = config.claude.leanSettingSources;
2314
- const output = execFileSync7("claude", [
2381
+ const output = execFileSync8("claude", [
2315
2382
  "--print",
2316
2383
  "--model",
2317
2384
  "sonnet",
@@ -2328,7 +2395,7 @@ async function runDeepReview(worktreePath, config, workerId) {
2328
2395
  });
2329
2396
  return parseReviewFindings(output);
2330
2397
  } catch (err) {
2331
- log.error(TAG12, `Deep review failed: ${err instanceof Error ? err.message : err}`);
2398
+ log.error(TAG13, `Deep review failed: ${err instanceof Error ? err.message : err}`);
2332
2399
  return [];
2333
2400
  } finally {
2334
2401
  if (devServer && !devServer.killed) {
@@ -2364,8 +2431,8 @@ function attemptAutoFix(worktreePath, config, errors) {
2364
2431
  "--",
2365
2432
  fixPrompt
2366
2433
  ];
2367
- log.info(TAG12, "Spawning Claude for auto-fix...");
2368
- execFileSync7("claude", args, {
2434
+ log.info(TAG13, "Spawning Claude for auto-fix...");
2435
+ execFileSync8("claude", args, {
2369
2436
  cwd: worktreePath,
2370
2437
  timeout: config.verification.timeout,
2371
2438
  stdio: "pipe"
@@ -2378,6 +2445,9 @@ async function reportFindings(client, cardId, result, recovery) {
2378
2445
  const url = recovery.branchUrl ? ` (${recovery.branchUrl})` : "";
2379
2446
  items.push(`Recovery: \`${cmd}\`${url}`);
2380
2447
  }
2448
+ for (const warn of result.revertWarnings) {
2449
+ items.push(`Revert: ${warn}`);
2450
+ }
2381
2451
  for (const err of result.buildErrors) {
2382
2452
  items.push(`Build: ${err}`);
2383
2453
  }
@@ -2395,7 +2465,7 @@ async function reportFindings(client, cardId, result, recovery) {
2395
2465
  try {
2396
2466
  await client.createSubtask(cardId, title);
2397
2467
  } catch (err) {
2398
- log.error(TAG12, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
2468
+ log.error(TAG13, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
2399
2469
  }
2400
2470
  }));
2401
2471
  if (overflow > 0) {
@@ -2403,7 +2473,7 @@ async function reportFindings(client, cardId, result, recovery) {
2403
2473
  await client.createSubtask(cardId, `...and ${overflow} more issues`);
2404
2474
  } catch {}
2405
2475
  }
2406
- log.info(TAG12, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
2476
+ log.info(TAG13, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
2407
2477
  }
2408
2478
  function parseErrorOutput(err) {
2409
2479
  const stderr = err?.stderr?.toString() ?? "";
@@ -2487,11 +2557,12 @@ async function probeDevServer(port, timeoutMs = 5000) {
2487
2557
  clearTimeout(timer);
2488
2558
  }
2489
2559
  }
2490
- var TAG12 = "verification", DevServerReadinessError;
2560
+ var TAG13 = "verification", DevServerReadinessError;
2491
2561
  var init_verification = __esm(() => {
2492
2562
  init_log();
2493
2563
  init_pm();
2494
2564
  init_project_type();
2565
+ init_revert_guard();
2495
2566
  DevServerReadinessError = class DevServerReadinessError extends Error {
2496
2567
  constructor(message) {
2497
2568
  super(message);
@@ -2501,7 +2572,7 @@ var init_verification = __esm(() => {
2501
2572
  });
2502
2573
 
2503
2574
  // src/completion.ts
2504
- import { execFileSync as execFileSync8 } from "node:child_process";
2575
+ import { execFileSync as execFileSync9 } from "node:child_process";
2505
2576
  function formatTokenCount(tokens) {
2506
2577
  if (tokens >= 1e6)
2507
2578
  return `${(tokens / 1e6).toFixed(1)}M`;
@@ -2527,11 +2598,12 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2527
2598
  passed: true,
2528
2599
  buildErrors: [],
2529
2600
  lintWarnings: [],
2530
- reviewFindings: []
2601
+ reviewFindings: [],
2602
+ revertWarnings: []
2531
2603
  };
2532
2604
  const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
2533
2605
  if (!hasCommits) {
2534
- log.warn(TAG13, `No commits on branch ${branchName} — skipping completion`);
2606
+ log.warn(TAG14, `No commits on branch ${branchName} — skipping completion`);
2535
2607
  await client.endAgentSession(card.id, {
2536
2608
  status: "completed",
2537
2609
  progressPercent: 100,
@@ -2540,13 +2612,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2540
2612
  cleanupWorktree(worktreePath, branchName);
2541
2613
  return true;
2542
2614
  }
2543
- log.info(TAG13, `Pushing branch ${branchName} (pre-verify)...`);
2615
+ log.info(TAG14, `Pushing branch ${branchName} (pre-verify)...`);
2544
2616
  let lastPushedSha = null;
2545
2617
  try {
2546
2618
  pushBranch(branchName, worktreePath);
2547
2619
  lastPushedSha = readHeadSha(worktreePath);
2548
2620
  } catch (err) {
2549
- log.error(TAG13, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2621
+ log.error(TAG14, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2550
2622
  }
2551
2623
  const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
2552
2624
  if (config.verification.enabled) {
@@ -2561,7 +2633,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2561
2633
  let autoFixAttempts = 0;
2562
2634
  if (!result.passed && config.verification.autoFix) {
2563
2635
  for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
2564
- log.info(TAG13, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
2636
+ log.info(TAG14, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
2565
2637
  await client.updateAgentProgress(card.id, {
2566
2638
  agentIdentifier: agentIdentifier(workerId),
2567
2639
  agentName: AGENT_NAME,
@@ -2574,14 +2646,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2574
2646
  result = await runVerification(worktreePath, config, workerId);
2575
2647
  autoFixAttempts = attempt + 1;
2576
2648
  if (result.passed) {
2577
- log.info(TAG13, `Auto-fix succeeded on attempt ${attempt + 1}`);
2649
+ log.info(TAG14, `Auto-fix succeeded on attempt ${attempt + 1}`);
2578
2650
  const sha = readHeadSha(worktreePath);
2579
2651
  if (sha && sha !== lastPushedSha) {
2580
2652
  try {
2581
2653
  pushBranch(branchName, worktreePath);
2582
2654
  lastPushedSha = sha;
2583
2655
  } catch (err) {
2584
- log.warn(TAG13, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2656
+ log.warn(TAG14, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2585
2657
  }
2586
2658
  }
2587
2659
  break;
@@ -2590,14 +2662,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2590
2662
  }
2591
2663
  verificationResult = result;
2592
2664
  if (!result.passed) {
2593
- log.warn(TAG13, `Verification failed for #${card.short_id} — reporting findings`);
2665
+ log.warn(TAG14, `Verification failed for #${card.short_id} — reporting findings`);
2594
2666
  const failSha = readHeadSha(worktreePath);
2595
2667
  if (failSha && failSha !== lastPushedSha) {
2596
2668
  try {
2597
2669
  pushBranch(branchName, worktreePath);
2598
2670
  lastPushedSha = failSha;
2599
2671
  } catch (err) {
2600
- log.warn(TAG13, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2672
+ log.warn(TAG14, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
2601
2673
  }
2602
2674
  }
2603
2675
  const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
@@ -2608,7 +2680,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2608
2680
  recoveryBranch: branchName
2609
2681
  });
2610
2682
  } catch (err) {
2611
- log.debug(TAG13, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
2683
+ log.debug(TAG14, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
2612
2684
  }
2613
2685
  await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
2614
2686
  await moveCardToColumn(client, card, config.verification.failColumn);
@@ -2622,7 +2694,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2622
2694
  cleanupWorktree(worktreePath, branchName);
2623
2695
  return false;
2624
2696
  }
2625
- log.info(TAG13, `Verification passed for #${card.short_id}`);
2697
+ log.info(TAG14, `Verification passed for #${card.short_id}`);
2626
2698
  }
2627
2699
  let prUrl = null;
2628
2700
  if (config.completion.createPR) {
@@ -2635,7 +2707,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2635
2707
  try {
2636
2708
  await onMovedToCompletion(card);
2637
2709
  } catch (err) {
2638
- log.warn(TAG13, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2710
+ log.warn(TAG14, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2639
2711
  }
2640
2712
  }
2641
2713
  }
@@ -2669,11 +2741,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2669
2741
  });
2670
2742
  }
2671
2743
  cleanupWorktree(worktreePath, branchName);
2672
- log.info(TAG13, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
2744
+ log.info(TAG14, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
2673
2745
  return true;
2674
2746
  }
2675
2747
  function buildVerificationFailureSummary(result, autoFixAttempts) {
2676
2748
  const counts = [];
2749
+ if (result.revertWarnings.length > 0) {
2750
+ counts.push(`${result.revertWarnings.length} reverted test(s)`);
2751
+ }
2677
2752
  if (result.buildErrors.length > 0) {
2678
2753
  counts.push(`${result.buildErrors.length} build error(s)`);
2679
2754
  }
@@ -2689,7 +2764,7 @@ function buildVerificationFailureSummary(result, autoFixAttempts) {
2689
2764
  }
2690
2765
  function readHeadSha(worktreePath) {
2691
2766
  try {
2692
- return execFileSync8("git", ["rev-parse", "HEAD"], {
2767
+ return execFileSync9("git", ["rev-parse", "HEAD"], {
2693
2768
  cwd: worktreePath,
2694
2769
  encoding: "utf-8"
2695
2770
  }).trim();
@@ -2699,7 +2774,7 @@ function readHeadSha(worktreePath) {
2699
2774
  }
2700
2775
  function checkHasCommits(worktreePath, baseBranch) {
2701
2776
  try {
2702
- const count = execFileSync8("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2777
+ const count = execFileSync9("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2703
2778
  return parseInt(count, 10) > 0;
2704
2779
  } catch {
2705
2780
  return false;
@@ -2708,7 +2783,7 @@ function checkHasCommits(worktreePath, baseBranch) {
2708
2783
  async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
2709
2784
  let commitLog = "";
2710
2785
  try {
2711
- commitLog = execFileSync8("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2786
+ commitLog = execFileSync9("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
2712
2787
  } catch {}
2713
2788
  const SUMMARY_MARKER = `---
2714
2789
  **Agent completed**`;
@@ -2753,12 +2828,12 @@ ${commitLog}
2753
2828
  description: baseDesc + parts.join(`
2754
2829
  `)
2755
2830
  });
2756
- log.info(TAG13, `Posted completion summary to #${card.short_id}`);
2831
+ log.info(TAG14, `Posted completion summary to #${card.short_id}`);
2757
2832
  } catch (err) {
2758
- log.error(TAG13, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
2833
+ log.error(TAG14, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
2759
2834
  }
2760
2835
  }
2761
- var TAG13 = "completion";
2836
+ var TAG14 = "completion";
2762
2837
  var init_completion = __esm(() => {
2763
2838
  init_board_helpers();
2764
2839
  init_episode_writer();
@@ -2798,7 +2873,7 @@ function signalGroup(proc, signal) {
2798
2873
  } catch (err) {
2799
2874
  const code = err.code;
2800
2875
  if (code !== "ESRCH") {
2801
- log.warn(TAG14, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
2876
+ log.warn(TAG15, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
2802
2877
  }
2803
2878
  }
2804
2879
  }
@@ -2823,7 +2898,7 @@ async function terminateGroup(proc, opts) {
2823
2898
  return;
2824
2899
  signalGroup(proc, "SIGKILL");
2825
2900
  }
2826
- var TAG14 = "pgroup";
2901
+ var TAG15 = "pgroup";
2827
2902
  var init_process_group = __esm(() => {
2828
2903
  init_log();
2829
2904
  });
@@ -2926,7 +3001,7 @@ class ProgressTracker {
2926
3001
  }
2927
3002
  onToolStart(name, input) {
2928
3003
  this.toolCallCount++;
2929
- log.debug(TAG15, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
3004
+ log.debug(TAG16, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
2930
3005
  const filePath = this.extractString(input, "file_path");
2931
3006
  if (filePath) {
2932
3007
  if (EDIT_TOOLS.has(name)) {
@@ -2997,7 +3072,7 @@ class ProgressTracker {
2997
3072
  transitionTo(newPhase) {
2998
3073
  if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
2999
3074
  return;
3000
- log.info(TAG15, `Phase: ${this.phase} → ${newPhase}`);
3075
+ log.info(TAG16, `Phase: ${this.phase} → ${newPhase}`);
3001
3076
  this.phase = newPhase;
3002
3077
  this.progress = Math.max(this.progress, PHASES[newPhase].min);
3003
3078
  this.lastAction = "";
@@ -3096,7 +3171,7 @@ class ProgressTracker {
3096
3171
  }
3097
3172
  sendUpdate(currentTask) {
3098
3173
  this.lastUpdateAt = Date.now();
3099
- log.debug(TAG15, `Progress: ${this.progress}% — ${currentTask}`);
3174
+ log.debug(TAG16, `Progress: ${this.progress}% — ${currentTask}`);
3100
3175
  this.client.updateAgentProgress(this.cardId, {
3101
3176
  agentIdentifier: agentIdentifier(this.workerId),
3102
3177
  agentName: AGENT_NAME,
@@ -3113,7 +3188,7 @@ class ProgressTracker {
3113
3188
  modelName: this.lastCost?.modelName,
3114
3189
  numTurns: this.lastCost?.numTurns ?? 0
3115
3190
  }).catch((err) => {
3116
- log.warn(TAG15, `Failed to send progress update: ${err}`);
3191
+ log.warn(TAG16, `Failed to send progress update: ${err}`);
3117
3192
  });
3118
3193
  this.flushActivityLog();
3119
3194
  }
@@ -3154,7 +3229,7 @@ class ProgressTracker {
3154
3229
  toolName: e.toolName ?? undefined
3155
3230
  }))
3156
3231
  }).catch((err) => {
3157
- log.warn(TAG15, `Failed to flush activity log: ${err}`);
3232
+ log.warn(TAG16, `Failed to flush activity log: ${err}`);
3158
3233
  this.logBuffer.unshift(...raw);
3159
3234
  if (this.logBuffer.length > MAX_LOG_BUFFER) {
3160
3235
  this.logBuffer.length = MAX_LOG_BUFFER;
@@ -3185,7 +3260,7 @@ class ProgressTracker {
3185
3260
  return null;
3186
3261
  }
3187
3262
  }
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;
3263
+ var TAG16 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_LOG_BUFFER = 500, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
3189
3264
  var init_progress_tracker = __esm(() => {
3190
3265
  init_log();
3191
3266
  init_types();
@@ -3221,6 +3296,41 @@ var init_progress_tracker = __esm(() => {
3221
3296
 
3222
3297
  // src/review-completion.ts
3223
3298
  import { readFileSync as readFileSync2, statSync } from "node:fs";
3299
+ function clampSubtaskTitle(title) {
3300
+ return title.length > MAX_SUBTASK_TITLE ? `${title.slice(0, MAX_SUBTASK_TITLE - 3)}...` : title;
3301
+ }
3302
+ function renderFindingBlock(f) {
3303
+ const locationLine = f.location ? `
3304
+ Location: ${f.location}` : "";
3305
+ return `**[${f.severity}] ${f.title}**
3306
+ ${f.description}${locationLine}`;
3307
+ }
3308
+ function buildFindingComments(findings) {
3309
+ const header = `**Review findings — ${findings.length} blocking issue(s) to resolve.**`;
3310
+ const sep = `
3311
+
3312
+ `;
3313
+ const bodies = [];
3314
+ let current = header;
3315
+ for (const f of findings) {
3316
+ let block = renderFindingBlock(f);
3317
+ const maxBlock = COMMENT_BODY_BUDGET - header.length - sep.length;
3318
+ if (block.length > maxBlock) {
3319
+ const suffix = `
3320
+ …[truncated]`;
3321
+ block = `${block.slice(0, Math.max(0, maxBlock - suffix.length))}${suffix}`;
3322
+ }
3323
+ if (current.length + sep.length + block.length > COMMENT_BODY_BUDGET) {
3324
+ bodies.push(current);
3325
+ current = `${header}${sep}${block}`;
3326
+ } else {
3327
+ current = `${current}${sep}${block}`;
3328
+ }
3329
+ }
3330
+ if (current.length > header.length)
3331
+ bodies.push(current);
3332
+ return bodies;
3333
+ }
3224
3334
  function tailRunLog(path, bytes = RUN_LOG_TAIL_BYTES) {
3225
3335
  try {
3226
3336
  const size = statSync(path).size;
@@ -3260,7 +3370,7 @@ function parseReviewOutput(stdout) {
3260
3370
  try {
3261
3371
  const parsed = JSON.parse(raw);
3262
3372
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
3263
- log.debug(TAG16, "Parsed review output from fenced JSON block");
3373
+ log.debug(TAG17, "Parsed review output from fenced JSON block");
3264
3374
  return extractResult(parsed);
3265
3375
  }
3266
3376
  } catch {}
@@ -3286,21 +3396,21 @@ function parseReviewOutput(stdout) {
3286
3396
  try {
3287
3397
  const parsed = JSON.parse(candidates[i]);
3288
3398
  if (parsed && typeof parsed === "object" && "verdict" in parsed) {
3289
- log.debug(TAG16, "Parsed review output from raw JSON object");
3399
+ log.debug(TAG17, "Parsed review output from raw JSON object");
3290
3400
  return extractResult(parsed);
3291
3401
  }
3292
3402
  } catch {}
3293
3403
  }
3294
3404
  const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
3295
3405
  if (verdictMatch) {
3296
- log.warn(TAG16, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
3406
+ log.warn(TAG17, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
3297
3407
  return {
3298
3408
  verdict: verdictMatch[1].toLowerCase(),
3299
3409
  summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
3300
3410
  findings: []
3301
3411
  };
3302
3412
  }
3303
- log.warn(TAG16, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
3413
+ log.warn(TAG17, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
3304
3414
  return {
3305
3415
  verdict: "error",
3306
3416
  summary: stdout.slice(0, 500),
@@ -3333,7 +3443,7 @@ async function postReviewComment(client, card, commentType, body) {
3333
3443
  try {
3334
3444
  await client.addComment(card.id, body, { commentType });
3335
3445
  } catch (err) {
3336
- log.error(TAG16, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3446
+ log.error(TAG17, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3337
3447
  }
3338
3448
  }
3339
3449
  async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore) {
@@ -3347,11 +3457,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
3347
3457
  const currentCycle = getReviewCycle(freshDesc) + 1;
3348
3458
  const maxCycles = config.review.maxReviewCycles;
3349
3459
  if (result.verdict === "error") {
3350
- log.warn(TAG16, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
3460
+ log.warn(TAG17, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
3351
3461
  try {
3352
3462
  await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
3353
3463
  } catch (err) {
3354
- log.warn(TAG16, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
3464
+ log.warn(TAG17, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
3355
3465
  }
3356
3466
  if (config.review.postFindings) {
3357
3467
  const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
@@ -3394,7 +3504,7 @@ ${runLogTail}
3394
3504
  renameRemoteBranch(branchName, newRef, worktreePath);
3395
3505
  approvedBranch = newRef;
3396
3506
  } catch (err) {
3397
- log.warn(TAG16, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
3507
+ log.warn(TAG17, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
3398
3508
  }
3399
3509
  }
3400
3510
  if (config.review.createPR && approvedBranch) {
@@ -3403,6 +3513,21 @@ ${runLogTail}
3403
3513
  }
3404
3514
  }
3405
3515
  await addLabelByName(client, card, config.review.approvedLabel, config.review.approvedLabelColor);
3516
+ if (prUrl) {
3517
+ try {
3518
+ const { card: latest } = await client.getCard(card.id);
3519
+ const desc = latest.description || "";
3520
+ if (!extractPrUrl(desc)) {
3521
+ const separator = desc ? `
3522
+ ` : "";
3523
+ await client.updateCard(card.id, {
3524
+ description: `${desc}${separator}PR: ${prUrl}`
3525
+ });
3526
+ }
3527
+ } catch (err) {
3528
+ log.warn(TAG17, `Failed to persist PR URL to #${card.short_id} description: ${err instanceof Error ? err.message : err}`);
3529
+ }
3530
+ }
3406
3531
  if (config.review.postFindings) {
3407
3532
  const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
3408
3533
  const body = [
@@ -3421,14 +3546,14 @@ ${runLogTail}
3421
3546
  progressPercent: 100,
3422
3547
  ...buildTokenPayload(sessionStats)
3423
3548
  });
3424
- log.info(TAG16, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
3549
+ log.info(TAG17, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
3425
3550
  } else {
3426
3551
  const criticalFindings = result.findings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
3427
3552
  const majorFindings = result.findings.filter((f) => f.severity === "major").slice(0, MAX_FINDINGS);
3428
3553
  const linkedFindings = [...criticalFindings, ...majorFindings];
3429
3554
  const minorFindings = result.findings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
3430
3555
  if (currentCycle >= maxCycles) {
3431
- log.warn(TAG16, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
3556
+ log.warn(TAG17, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
3432
3557
  await moveCardToColumn(client, card, config.review.moveToColumn);
3433
3558
  const body = [
3434
3559
  "**Review — needs human review.**",
@@ -3466,28 +3591,21 @@ ${runLogTail}
3466
3591
  if (config.review.postFindings) {
3467
3592
  await Promise.all(linkedFindings.map(async (finding) => {
3468
3593
  try {
3469
- const locationLine = finding.location ? `
3470
- **Location:** ${finding.location}` : "";
3471
- const newCard = await client.createCard(card.project_id, {
3472
- title: `[Review: ${finding.severity}] ${finding.title}`,
3473
- description: `Found during review of #${card.short_id} (${finding.severity}):
3474
-
3475
- ${finding.description}${locationLine}`
3476
- });
3477
- const newCardId = newCard?.card?.id;
3478
- if (newCardId) {
3479
- await client.addLinkToCard(card.id, newCardId, "relates_to");
3480
- }
3594
+ await client.createSubtask(card.id, clampSubtaskTitle(`[${finding.severity}] ${finding.title}`));
3481
3595
  } catch (err) {
3482
- log.error(TAG16, `Failed to create finding card: ${err instanceof Error ? err.message : err}`);
3596
+ log.error(TAG17, `Failed to create finding subtask: ${err instanceof Error ? err.message : err}`);
3483
3597
  }
3484
3598
  }));
3599
+ if (linkedFindings.length > 0) {
3600
+ for (const body2 of buildFindingComments(linkedFindings)) {
3601
+ await postReviewComment(client, card, "finding", body2);
3602
+ }
3603
+ }
3485
3604
  await Promise.all(minorFindings.map(async (finding) => {
3486
3605
  try {
3487
- const title = finding.title.length > 120 ? `${finding.title.slice(0, 117)}...` : finding.title;
3488
- await client.createSubtask(card.id, title);
3606
+ await client.createSubtask(card.id, clampSubtaskTitle(finding.title));
3489
3607
  } catch (err) {
3490
- log.error(TAG16, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
3608
+ log.error(TAG17, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
3491
3609
  }
3492
3610
  }));
3493
3611
  const baseDesc = stripReviewSummary(freshDesc);
@@ -3495,7 +3613,7 @@ ${finding.description}${locationLine}`
3495
3613
  try {
3496
3614
  await client.updateCard(card.id, { description: updatedDesc });
3497
3615
  } catch (err) {
3498
- log.error(TAG16, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
3616
+ log.error(TAG17, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
3499
3617
  }
3500
3618
  const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
3501
3619
  const body = [
@@ -3511,9 +3629,9 @@ ${finding.description}${locationLine}`
3511
3629
  if (config.planning.enabled && card.plan_id) {
3512
3630
  try {
3513
3631
  await client.updateCard(card.id, { needsPlanRefresh: true });
3514
- log.info(TAG16, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
3632
+ log.info(TAG17, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
3515
3633
  } catch (err) {
3516
- log.warn(TAG16, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3634
+ log.warn(TAG17, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
3517
3635
  }
3518
3636
  }
3519
3637
  await moveCardToColumn(client, card, config.review.failColumn);
@@ -3527,10 +3645,10 @@ ${finding.description}${locationLine}`
3527
3645
  recoveryBranch
3528
3646
  });
3529
3647
  } catch (err) {
3530
- log.debug(TAG16, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
3648
+ log.debug(TAG17, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
3531
3649
  }
3532
3650
  if (recoveryBranch) {
3533
- log.info(TAG16, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
3651
+ log.info(TAG17, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
3534
3652
  }
3535
3653
  await client.endAgentSession(card.id, {
3536
3654
  status: "failed",
@@ -3539,7 +3657,7 @@ ${finding.description}${locationLine}`
3539
3657
  recoveryBranch,
3540
3658
  ...buildTokenPayload(sessionStats)
3541
3659
  });
3542
- log.info(TAG16, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
3660
+ log.info(TAG17, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
3543
3661
  }
3544
3662
  if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
3545
3663
  const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
@@ -3561,7 +3679,7 @@ ${finding.description}${locationLine}`
3561
3679
  cleanupWorktree(worktreePath, branchName);
3562
3680
  }
3563
3681
  }
3564
- var TAG16 = "review-completion", MAX_FINDINGS = 10, REVIEW_MARKER = `---
3682
+ var TAG17 = "review-completion", MAX_FINDINGS = 10, MAX_SUBTASK_TITLE = 120, COMMENT_BODY_BUDGET = 9500, REVIEW_MARKER = `---
3565
3683
  **Review:`, RUN_LOG_TAIL_BYTES = 2048;
3566
3684
  var init_review_completion = __esm(() => {
3567
3685
  init_board_helpers();
@@ -3974,7 +4092,7 @@ class StateStore {
3974
4092
  const raw = readFileSync3(this.path, "utf-8");
3975
4093
  const parsed = JSON.parse(raw);
3976
4094
  if (parsed?.version !== SCHEMA_VERSION) {
3977
- log.warn(TAG17, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — starting fresh`);
4095
+ log.warn(TAG18, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — starting fresh`);
3978
4096
  return emptyState();
3979
4097
  }
3980
4098
  return {
@@ -3987,7 +4105,7 @@ class StateStore {
3987
4105
  daily: parsed.daily ?? []
3988
4106
  };
3989
4107
  } catch (err) {
3990
- log.error(TAG17, `failed to read state file: ${err instanceof Error ? err.message : err}`);
4108
+ log.error(TAG18, `failed to read state file: ${err instanceof Error ? err.message : err}`);
3991
4109
  return emptyState();
3992
4110
  }
3993
4111
  }
@@ -4143,7 +4261,7 @@ class StateStore {
4143
4261
  return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
4144
4262
  }
4145
4263
  }
4146
- var TAG17 = "state-store", SCHEMA_VERSION = 1;
4264
+ var TAG18 = "state-store", SCHEMA_VERSION = 1;
4147
4265
  var init_state_store = __esm(() => {
4148
4266
  init_log();
4149
4267
  });
@@ -4170,7 +4288,7 @@ function normalizeToolResultContent(raw) {
4170
4288
  return String(raw);
4171
4289
  }
4172
4290
  }
4173
- var TAG18 = "stream-parser", StreamParser;
4291
+ var TAG19 = "stream-parser", StreamParser;
4174
4292
  var init_stream_parser = __esm(() => {
4175
4293
  init_log();
4176
4294
  StreamParser = class StreamParser extends EventEmitter {
@@ -4214,14 +4332,14 @@ var init_stream_parser = __esm(() => {
4214
4332
  try {
4215
4333
  msg = JSON.parse(line);
4216
4334
  } catch {
4217
- log.debug(TAG18, `Non-JSON line: ${line.slice(0, 100)}`);
4335
+ log.debug(TAG19, `Non-JSON line: ${line.slice(0, 100)}`);
4218
4336
  return;
4219
4337
  }
4220
4338
  try {
4221
4339
  this.handleMessage(msg);
4222
4340
  } catch (err) {
4223
4341
  const errMsg = err instanceof Error ? err.message : String(err);
4224
- log.warn(TAG18, `Error handling stream event: ${errMsg}`);
4342
+ log.warn(TAG19, `Error handling stream event: ${errMsg}`);
4225
4343
  this.emit("parse_error", errMsg);
4226
4344
  }
4227
4345
  }
@@ -4303,7 +4421,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
4303
4421
  const msg2 = err instanceof Error ? err.message : String(err);
4304
4422
  if (i < attempts - 1) {
4305
4423
  const wait = backoffMs * 2 ** i;
4306
- log.warn(TAG19, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
4424
+ log.warn(TAG20, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
4307
4425
  await new Promise((r) => setTimeout(r, wait));
4308
4426
  }
4309
4427
  }
@@ -4325,10 +4443,10 @@ async function runTransition(client, card, plan, opts = {}) {
4325
4443
  if (opts.strictColumn) {
4326
4444
  throw new TransitionError("move", 1, msg);
4327
4445
  }
4328
- log.warn(TAG19, `#${shortId}: ${msg} — skipping move`);
4446
+ log.warn(TAG20, `#${shortId}: ${msg} — skipping move`);
4329
4447
  } else if (card.column_id !== target.id) {
4330
4448
  await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
4331
- log.info(TAG19, `#${shortId} → "${target.name}"`);
4449
+ log.info(TAG20, `#${shortId} → "${target.name}"`);
4332
4450
  card.column_id = target.id;
4333
4451
  }
4334
4452
  }
@@ -4341,7 +4459,7 @@ async function runTransition(client, card, plan, opts = {}) {
4341
4459
  continue;
4342
4460
  await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
4343
4461
  existing.add(labelId);
4344
- log.info(TAG19, `#${shortId} +label "${name}"`);
4462
+ log.info(TAG20, `#${shortId} +label "${name}"`);
4345
4463
  }
4346
4464
  card.labelIds = Array.from(existing);
4347
4465
  }
@@ -4353,17 +4471,17 @@ async function runTransition(client, card, plan, opts = {}) {
4353
4471
  continue;
4354
4472
  await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
4355
4473
  existing.delete(match.id);
4356
- log.info(TAG19, `#${shortId} -label "${name}"`);
4474
+ log.info(TAG20, `#${shortId} -label "${name}"`);
4357
4475
  }
4358
4476
  card.labelIds = Array.from(existing);
4359
4477
  }
4360
4478
  if (plan.updateCard) {
4361
4479
  await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
4362
- log.info(TAG19, `#${shortId} updated`);
4480
+ log.info(TAG20, `#${shortId} updated`);
4363
4481
  }
4364
4482
  if (plan.endSession) {
4365
4483
  await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
4366
- log.info(TAG19, `#${shortId} session ended (${plan.endSession.status})`);
4484
+ log.info(TAG20, `#${shortId} session ended (${plan.endSession.status})`);
4367
4485
  }
4368
4486
  if (opts.store && opts.runId) {
4369
4487
  try {
@@ -4376,11 +4494,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
4376
4494
  const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
4377
4495
  return result?.label?.id ?? null;
4378
4496
  } catch (err) {
4379
- log.warn(TAG19, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
4497
+ log.warn(TAG20, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
4380
4498
  return null;
4381
4499
  }
4382
4500
  }
4383
- var TAG19 = "transition", TransitionError;
4501
+ var TAG20 = "transition", TransitionError;
4384
4502
  var init_transitions = __esm(() => {
4385
4503
  init_log();
4386
4504
  TransitionError = class TransitionError extends Error {
@@ -4398,7 +4516,7 @@ var init_transitions = __esm(() => {
4398
4516
  });
4399
4517
 
4400
4518
  // src/review-worker.ts
4401
- import { execFileSync as execFileSync9 } from "node:child_process";
4519
+ import { execFileSync as execFileSync10 } from "node:child_process";
4402
4520
 
4403
4521
  class ReviewWorker {
4404
4522
  config;
@@ -4464,7 +4582,7 @@ class ReviewWorker {
4464
4582
  }
4465
4583
  }
4466
4584
  get tag() {
4467
- return `${TAG20}:${this.id}`;
4585
+ return `${TAG21}:${this.id}`;
4468
4586
  }
4469
4587
  get isIdle() {
4470
4588
  return this.state === "idle";
@@ -4521,7 +4639,7 @@ class ReviewWorker {
4521
4639
  let localDiff = null;
4522
4640
  if (localMode) {
4523
4641
  log.info(this.tag, `No branch found for #${card.short_id}, attempting local review`);
4524
- this.worktreePath = execFileSync9("git", ["rev-parse", "--show-toplevel"], {
4642
+ this.worktreePath = execFileSync10("git", ["rev-parse", "--show-toplevel"], {
4525
4643
  encoding: "utf-8",
4526
4644
  timeout: 5000
4527
4645
  }).trim();
@@ -4588,7 +4706,7 @@ class ReviewWorker {
4588
4706
  if (localMode) {
4589
4707
  diff = localDiff ?? "";
4590
4708
  } else {
4591
- diff = execFileSync9("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
4709
+ diff = execFileSync10("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
4592
4710
  }
4593
4711
  } catch {
4594
4712
  diff = "(unable to retrieve diff)";
@@ -4868,7 +4986,7 @@ class ReviewWorker {
4868
4986
  }
4869
4987
  resolveLocalChanges(repoRoot, shortId) {
4870
4988
  try {
4871
- const localChanges = execFileSync9("git", ["diff", "HEAD"], {
4989
+ const localChanges = execFileSync10("git", ["diff", "HEAD"], {
4872
4990
  cwd: repoRoot,
4873
4991
  encoding: "utf-8",
4874
4992
  timeout: 5000
@@ -4880,7 +4998,7 @@ class ReviewWorker {
4880
4998
  log.warn(this.tag, "Failed to check uncommitted changes");
4881
4999
  }
4882
5000
  try {
4883
- const matchingCommits = execFileSync9("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 1e4 }).trim();
5001
+ const matchingCommits = execFileSync10("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 1e4 }).trim();
4884
5002
  if (matchingCommits) {
4885
5003
  const hashes = matchingCommits.split(`
4886
5004
  `).filter((h) => /^[0-9a-f]{4,40}$/i.test(h));
@@ -4890,7 +5008,7 @@ class ReviewWorker {
4890
5008
  const diffs = [];
4891
5009
  for (const hash of hashes) {
4892
5010
  try {
4893
- const commitDiff = execFileSync9("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30000 });
5011
+ const commitDiff = execFileSync10("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30000 });
4894
5012
  if (commitDiff)
4895
5013
  diffs.push(commitDiff);
4896
5014
  } catch {
@@ -4933,7 +5051,7 @@ class ReviewWorker {
4933
5051
  this.lastSessionStats = null;
4934
5052
  }
4935
5053
  }
4936
- var TAG20 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
5054
+ var TAG21 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
4937
5055
  var init_review_worker = __esm(() => {
4938
5056
  init_board_helpers();
4939
5057
  init_completion();
@@ -4982,7 +5100,7 @@ class SleepGuard {
4982
5100
  if (!this.child.killed)
4983
5101
  this.child.kill("SIGTERM");
4984
5102
  this.child = null;
4985
- log.info(TAG21, "sleep assertion released");
5103
+ log.info(TAG22, "sleep assertion released");
4986
5104
  }
4987
5105
  }
4988
5106
  start() {
@@ -4997,7 +5115,7 @@ class SleepGuard {
4997
5115
  spawned = true;
4998
5116
  });
4999
5117
  child.on("error", (err) => {
5000
- log.warn(TAG21, `caffeinate unavailable: ${err.message}`);
5118
+ log.warn(TAG22, `caffeinate unavailable: ${err.message}`);
5001
5119
  if (this.child === child)
5002
5120
  this.child = null;
5003
5121
  });
@@ -5010,13 +5128,13 @@ class SleepGuard {
5010
5128
  });
5011
5129
  child.unref();
5012
5130
  this.child = child;
5013
- log.info(TAG21, "sleep assertion acquired (caffeinate -i)");
5131
+ log.info(TAG22, "sleep assertion acquired (caffeinate -i)");
5014
5132
  } catch (err) {
5015
- log.warn(TAG21, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
5133
+ log.warn(TAG22, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
5016
5134
  }
5017
5135
  }
5018
5136
  }
5019
- var TAG21 = "sleep-guard";
5137
+ var TAG22 = "sleep-guard";
5020
5138
  var init_sleep_guard = __esm(() => {
5021
5139
  init_log();
5022
5140
  });
@@ -5027,7 +5145,7 @@ async function fetchBlocksLinks(client, cardId) {
5027
5145
  const { links } = await client.getCardLinks(cardId);
5028
5146
  return links.filter((l) => l.link_type === "blocks");
5029
5147
  } catch (err) {
5030
- log.warn(TAG22, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
5148
+ log.warn(TAG23, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
5031
5149
  return null;
5032
5150
  }
5033
5151
  }
@@ -5059,27 +5177,27 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
5059
5177
  const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
5060
5178
  if (successors.length === 0)
5061
5179
  return;
5062
- log.info(TAG22, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
5180
+ log.info(TAG23, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
5063
5181
  for (const link of successors) {
5064
5182
  const successorId = link.target_card.id;
5065
5183
  try {
5066
5184
  const { card } = await deps.client.getCard(successorId);
5067
5185
  if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
5068
- log.info(TAG22, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
5186
+ log.info(TAG23, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
5069
5187
  await deps.client.updateCard(successorId, {
5070
5188
  assignedAgentId: deps.agentId
5071
5189
  });
5072
5190
  } else {
5073
- log.debug(TAG22, `successor #${card.short_id} assigned to different entity — skipping`);
5191
+ log.debug(TAG23, `successor #${card.short_id} assigned to different entity — skipping`);
5074
5192
  continue;
5075
5193
  }
5076
5194
  await deps.enqueue(successorId);
5077
5195
  } catch (err) {
5078
- log.warn(TAG22, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
5196
+ log.warn(TAG23, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
5079
5197
  }
5080
5198
  }
5081
5199
  }
5082
- var TAG22 = "unblock";
5200
+ var TAG23 = "unblock";
5083
5201
  var init_unblock = __esm(() => {
5084
5202
  init_log();
5085
5203
  });
@@ -5134,11 +5252,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
5134
5252
  Do NOT push to main. All your work stays on \`${branchName}\`.
5135
5253
  When finished, call harmony_end_agent_session with status="completed".`
5136
5254
  });
5137
- log.info(TAG23, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
5255
+ log.info(TAG24, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
5138
5256
  return result.prompt + pastEpisodesSection;
5139
5257
  } catch (err) {
5140
5258
  const msg = err instanceof Error ? err.message : String(err);
5141
- log.warn(TAG23, `Failed to generate prompt via API, using fallback: ${msg}`);
5259
+ log.warn(TAG24, `Failed to generate prompt via API, using fallback: ${msg}`);
5142
5260
  const commentsSection = await renderCommentsSection(client, card.id);
5143
5261
  return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
5144
5262
  }
@@ -5156,7 +5274,7 @@ async function renderCommentsSection(client, cardId) {
5156
5274
 
5157
5275
  ${section}` : "";
5158
5276
  } catch (err) {
5159
- log.warn(TAG23, "comment-thread fetch failed", {
5277
+ log.warn(TAG24, "comment-thread fetch failed", {
5160
5278
  event: "comment_fetch_failed",
5161
5279
  error: err instanceof Error ? err.message : String(err)
5162
5280
  });
@@ -5206,7 +5324,7 @@ ${description}`.trim();
5206
5324
  ## Similar past tasks
5207
5325
  ${bullets}`;
5208
5326
  } catch (err) {
5209
- log.warn(TAG23, "past-episodes recall failed", {
5327
+ log.warn(TAG24, "past-episodes recall failed", {
5210
5328
  event: "episode_recall_failed",
5211
5329
  error: err instanceof Error ? err.message : String(err)
5212
5330
  });
@@ -5247,7 +5365,7 @@ ${subtaskStr}
5247
5365
  You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
5248
5366
  Do NOT push to main. All your work stays on \`${branchName}\`.`;
5249
5367
  }
5250
- var TAG23 = "prompt";
5368
+ var TAG24 = "prompt";
5251
5369
  var init_prompt = __esm(() => {
5252
5370
  init_dist();
5253
5371
  init_log();
@@ -5326,7 +5444,7 @@ class Worker {
5326
5444
  }
5327
5445
  }
5328
5446
  get tag() {
5329
- return `${TAG24}:${this.id}`;
5447
+ return `${TAG25}:${this.id}`;
5330
5448
  }
5331
5449
  get isIdle() {
5332
5450
  return this.state === "idle";
@@ -5385,7 +5503,7 @@ class Worker {
5385
5503
  });
5386
5504
  const sid = session && typeof session === "object" && "id" in session ? session.id : null;
5387
5505
  if (!sid) {
5388
- log.warn(TAG24, "startAgentSession returned no session id");
5506
+ log.warn(TAG25, "startAgentSession returned no session id");
5389
5507
  }
5390
5508
  this.sessionId = sid;
5391
5509
  await this.recordPhase("preparing");
@@ -5461,6 +5579,8 @@ class Worker {
5461
5579
  const rawStderr = err?.stderr;
5462
5580
  const errClass = classifyRunError(typeof rawStderr === "string" && rawStderr ? rawStderr : msg);
5463
5581
  const apiError = errClass.kind !== null;
5582
+ const baseError = err instanceof WorktreeBaseError;
5583
+ const noBudgetBurn = apiError || baseError;
5464
5584
  if (apiError) {
5465
5585
  try {
5466
5586
  this.onApiError?.(errClass);
@@ -5475,7 +5595,7 @@ class Worker {
5475
5595
  this.worktreePath = null;
5476
5596
  }
5477
5597
  const failureReason = apiError ? errClass.kind : "other";
5478
- const failureSummary = apiError ? describeApiError(errClass.kind) : `Run failed: ${msg.slice(0, 300)}`;
5598
+ const failureSummary = apiError ? describeApiError(errClass.kind) : baseError ? "Could not fetch current base branch — requeued without counting an attempt" : `Run failed: ${msg.slice(0, 300)}`;
5479
5599
  try {
5480
5600
  await runTransition(this.client, card, {
5481
5601
  move: { columnName: this.config.pickupColumns[0] ?? "To Do" },
@@ -5496,7 +5616,7 @@ class Worker {
5496
5616
  ...this.runLedger()
5497
5617
  });
5498
5618
  } catch {}
5499
- if (apiError) {
5619
+ if (noBudgetBurn) {
5500
5620
  await this.stateStore.decrementAttempt(card.id);
5501
5621
  } else {
5502
5622
  await this.recordOutcome(card.id, "failure");
@@ -5893,7 +6013,7 @@ class Worker {
5893
6013
  this.runTurns = 0;
5894
6014
  }
5895
6015
  }
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;
6016
+ var TAG25 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
5897
6017
  var init_worker = __esm(() => {
5898
6018
  init_board_helpers();
5899
6019
  init_completion();
@@ -5963,39 +6083,39 @@ class Pool {
5963
6083
  }
5964
6084
  async enqueue(card, column, labels, subtasks, mode = "implement") {
5965
6085
  if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
5966
- log.debug(TAG25, `Card ${card.id} already queued or active, skipping`);
6086
+ log.debug(TAG26, `Card ${card.id} already queued or active, skipping`);
5967
6087
  return;
5968
6088
  }
5969
6089
  if (mode === "implement") {
5970
6090
  if (this.authPaused) {
5971
- log.debug(TAG25, `#${card.short_id} held — agent paused (auth error)`);
6091
+ log.debug(TAG26, `#${card.short_id} held — agent paused (auth error)`);
5972
6092
  await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
5973
6093
  return;
5974
6094
  }
5975
6095
  const cooldownMs = this.apiCooldownRemainingMs();
5976
6096
  if (cooldownMs > 0) {
5977
- log.debug(TAG25, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
6097
+ log.debug(TAG26, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
5978
6098
  await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
5979
6099
  return;
5980
6100
  }
5981
6101
  const decision = this.budget.check(card.id);
5982
6102
  if (!decision.allow) {
5983
6103
  if (decision.reason === "daily_budget") {
5984
- log.warn(TAG25, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
6104
+ log.warn(TAG26, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
5985
6105
  await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
5986
6106
  } else {
5987
- log.debug(TAG25, `#${card.short_id} gave up: ${decision.detail}`);
6107
+ log.debug(TAG26, `#${card.short_id} gave up: ${decision.detail}`);
5988
6108
  }
5989
6109
  return;
5990
6110
  }
5991
6111
  const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
5992
6112
  if (blockers === null) {
5993
- log.warn(TAG25, `#${card.short_id} blocker check failed — deferring to next tick`);
6113
+ log.warn(TAG26, `#${card.short_id} blocker check failed — deferring to next tick`);
5994
6114
  return;
5995
6115
  }
5996
6116
  if (blockers.length > 0) {
5997
6117
  const list = blockers.map((b) => `#${b.shortId}`).join(", ");
5998
- log.info(TAG25, `#${card.short_id} blocked by ${list} — waiting`);
6118
+ log.info(TAG26, `#${card.short_id} blocked by ${list} — waiting`);
5999
6119
  await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
6000
6120
  return;
6001
6121
  }
@@ -6024,7 +6144,7 @@ class Pool {
6024
6144
  });
6025
6145
  this.lastWaitingEmit.set(cardId, currentTask);
6026
6146
  } catch (err) {
6027
- log.debug(TAG25, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
6147
+ log.debug(TAG26, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
6028
6148
  }
6029
6149
  }
6030
6150
  noteApiError(err) {
@@ -6032,7 +6152,7 @@ class Pool {
6032
6152
  return;
6033
6153
  if (err.kind === "auth") {
6034
6154
  if (!this.authPaused) {
6035
- log.error(TAG25, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
6155
+ log.error(TAG26, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
6036
6156
  }
6037
6157
  this.authPaused = true;
6038
6158
  return;
@@ -6041,7 +6161,7 @@ class Pool {
6041
6161
  const until = Date.now() + cooldownMs;
6042
6162
  if (until > this.apiCooldownUntil) {
6043
6163
  this.apiCooldownUntil = until;
6044
- log.warn(TAG25, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
6164
+ log.warn(TAG26, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
6045
6165
  }
6046
6166
  }
6047
6167
  apiCooldownRemainingMs() {
@@ -6054,13 +6174,13 @@ class Pool {
6054
6174
  const removed = queue.remove(cardId);
6055
6175
  if (removed) {
6056
6176
  this.cardDataCache.delete(cardId);
6057
- log.info(TAG25, `Removed #${removed.shortId} from ${removed.mode} queue`);
6177
+ log.info(TAG26, `Removed #${removed.shortId} from ${removed.mode} queue`);
6058
6178
  return;
6059
6179
  }
6060
6180
  }
6061
6181
  const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
6062
6182
  if (worker) {
6063
- log.info(TAG25, `Cancelling worker ${worker.id} for card ${cardId}`);
6183
+ log.info(TAG26, `Cancelling worker ${worker.id} for card ${cardId}`);
6064
6184
  await worker.cancel();
6065
6185
  }
6066
6186
  }
@@ -6093,10 +6213,10 @@ class Pool {
6093
6213
  async handleAgentCommand(cardId, command) {
6094
6214
  const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
6095
6215
  if (!worker) {
6096
- log.debug(TAG25, `No active worker for card ${cardId}, ignoring ${command}`);
6216
+ log.debug(TAG26, `No active worker for card ${cardId}, ignoring ${command}`);
6097
6217
  return;
6098
6218
  }
6099
- log.info(TAG25, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
6219
+ log.info(TAG26, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
6100
6220
  switch (command) {
6101
6221
  case "pause":
6102
6222
  await worker.pause();
@@ -6144,7 +6264,7 @@ class Pool {
6144
6264
  };
6145
6265
  }
6146
6266
  async shutdown() {
6147
- log.info(TAG25, "Shutting down pool...");
6267
+ log.info(TAG26, "Shutting down pool...");
6148
6268
  this.shuttingDown = true;
6149
6269
  const active = [
6150
6270
  ...this.implWorkers.filter((w) => w.isActive),
@@ -6152,7 +6272,7 @@ class Pool {
6152
6272
  ];
6153
6273
  await Promise.all(active.map((w) => w.cancel()));
6154
6274
  this.sleepGuard.stop();
6155
- log.info(TAG25, "Pool shutdown complete");
6275
+ log.info(TAG26, "Pool shutdown complete");
6156
6276
  }
6157
6277
  cardDataCache = new Map;
6158
6278
  tryDispatchFor(workers, queue, label) {
@@ -6160,7 +6280,7 @@ class Pool {
6160
6280
  return false;
6161
6281
  const idle = workers.find((w) => w.isIdle);
6162
6282
  if (!idle) {
6163
- log.debug(TAG25, `No idle ${label} workers (queue: ${queue.length})`);
6283
+ log.debug(TAG26, `No idle ${label} workers (queue: ${queue.length})`);
6164
6284
  return false;
6165
6285
  }
6166
6286
  const next = queue.dequeue();
@@ -6168,18 +6288,18 @@ class Pool {
6168
6288
  return false;
6169
6289
  const data = this.cardDataCache.get(next.cardId);
6170
6290
  if (!data) {
6171
- log.warn(TAG25, `No cached data for card ${next.cardId}, skipping`);
6291
+ log.warn(TAG26, `No cached data for card ${next.cardId}, skipping`);
6172
6292
  return false;
6173
6293
  }
6174
6294
  this.cardDataCache.delete(next.cardId);
6175
6295
  this.lastWaitingEmit.delete(next.cardId);
6176
- log.info(TAG25, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
6296
+ log.info(TAG26, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
6177
6297
  this.sleepGuard.acquire();
6178
6298
  idle.run(data.card, data.column, data.labels, data.subtasks);
6179
6299
  return true;
6180
6300
  }
6181
6301
  }
6182
- var TAG25 = "pool";
6302
+ var TAG26 = "pool";
6183
6303
  var init_pool = __esm(() => {
6184
6304
  init_error_classifier();
6185
6305
  init_log();
@@ -6221,7 +6341,7 @@ function load(path) {
6221
6341
  return parsed;
6222
6342
  return {};
6223
6343
  } catch (err) {
6224
- log.warn(TAG26, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
6344
+ log.warn(TAG27, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
6225
6345
  return {};
6226
6346
  }
6227
6347
  }
@@ -6239,7 +6359,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
6239
6359
  registry[projectId] = { ...entry, updatedAt: Date.now() };
6240
6360
  save(path, registry);
6241
6361
  } catch (err) {
6242
- log.warn(TAG26, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6362
+ log.warn(TAG27, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6243
6363
  }
6244
6364
  }
6245
6365
  function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
@@ -6255,10 +6375,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
6255
6375
  delete registry[projectId];
6256
6376
  save(path, registry);
6257
6377
  } catch (err) {
6258
- log.warn(TAG26, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6378
+ log.warn(TAG27, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
6259
6379
  }
6260
6380
  }
6261
- var TAG26 = "port-registry";
6381
+ var TAG27 = "port-registry";
6262
6382
  var init_port_registry = __esm(() => {
6263
6383
  init_log();
6264
6384
  });
@@ -6279,7 +6399,7 @@ async function fetchCardSafely(client, cardId) {
6279
6399
  const { card } = await client.getCard(cardId);
6280
6400
  return card;
6281
6401
  } catch (err) {
6282
- log.warn(TAG27, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
6402
+ log.warn(TAG28, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
6283
6403
  return null;
6284
6404
  }
6285
6405
  }
@@ -6289,7 +6409,7 @@ async function recoverOrphans(store, client, config) {
6289
6409
  return [];
6290
6410
  }
6291
6411
  const outcomes = [];
6292
- log.info(TAG27, `recovering ${active.length} orphan run(s) from prior daemon`);
6412
+ log.info(TAG28, `recovering ${active.length} orphan run(s) from prior daemon`);
6293
6413
  for (const run of active) {
6294
6414
  const outcome = {
6295
6415
  runId: run.runId,
@@ -6301,11 +6421,11 @@ async function recoverOrphans(store, client, config) {
6301
6421
  };
6302
6422
  outcomes.push(outcome);
6303
6423
  if (isProcessAlive(run.daemonPid, process.pid)) {
6304
- log.warn(TAG27, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
6424
+ log.warn(TAG28, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
6305
6425
  outcome.actions.push("skipped: daemon pid still alive");
6306
6426
  continue;
6307
6427
  }
6308
- log.info(TAG27, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
6428
+ log.info(TAG28, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
6309
6429
  await recoverRun(run, store, client, config, outcome);
6310
6430
  }
6311
6431
  return outcomes;
@@ -6323,7 +6443,7 @@ async function recoverRun(run, store, client, config, outcome) {
6323
6443
  } catch (err) {
6324
6444
  const msg = err instanceof Error ? err.message : String(err);
6325
6445
  outcome.errors.push(`endAgentSession: ${msg}`);
6326
- log.warn(TAG27, `endAgentSession failed for ${run.cardId}: ${msg}`);
6446
+ log.warn(TAG28, `endAgentSession failed for ${run.cardId}: ${msg}`);
6327
6447
  }
6328
6448
  const card = await fetchCardSafely(client, run.cardId);
6329
6449
  if (card) {
@@ -6366,9 +6486,9 @@ async function recoverRun(run, store, client, config, outcome) {
6366
6486
  const msg = err instanceof Error ? err.message : String(err);
6367
6487
  outcome.errors.push(`endRun: ${msg}`);
6368
6488
  }
6369
- log.info(TAG27, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
6489
+ log.info(TAG28, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
6370
6490
  }
6371
- var TAG27 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
6491
+ var TAG28 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
6372
6492
  var init_recovery = __esm(() => {
6373
6493
  init_board_helpers();
6374
6494
  init_log();
@@ -6416,7 +6536,7 @@ class Reconciler {
6416
6536
  clearInterval(this.timer);
6417
6537
  this.timer = null;
6418
6538
  }
6419
- log.info(TAG28, "Heartbeat stopped");
6539
+ log.info(TAG29, "Heartbeat stopped");
6420
6540
  }
6421
6541
  async recoverStaleRuns() {
6422
6542
  if (!this.stateStore || !this.agentConfig)
@@ -6433,7 +6553,7 @@ class Reconciler {
6433
6553
  if (!daemonDead && !(heartbeatStale && ourZombie))
6434
6554
  continue;
6435
6555
  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`;
6436
- log.warn(TAG28, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
6556
+ log.warn(TAG29, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
6437
6557
  await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
6438
6558
  runId: run.runId,
6439
6559
  cardId: run.cardId,
@@ -6460,11 +6580,11 @@ class Reconciler {
6460
6580
  const stalledAt = Date.parse(card.updated_at ?? "");
6461
6581
  if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
6462
6582
  continue;
6463
- log.warn(TAG28, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
6583
+ log.warn(TAG29, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
6464
6584
  try {
6465
6585
  await this.client.moveCard(card.id, pickupCol.id);
6466
6586
  } catch (err) {
6467
- log.error(TAG28, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6587
+ log.error(TAG29, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6468
6588
  }
6469
6589
  }
6470
6590
  }
@@ -6487,11 +6607,11 @@ class Reconciler {
6487
6607
  const parkedAt = Date.parse(card.updated_at ?? "");
6488
6608
  if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
6489
6609
  continue;
6490
- log.warn(TAG28, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
6610
+ log.warn(TAG29, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
6491
6611
  try {
6492
6612
  await this.client.moveCard(card.id, pickupCol.id);
6493
6613
  } catch (err) {
6494
- log.error(TAG28, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6614
+ log.error(TAG29, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
6495
6615
  }
6496
6616
  }
6497
6617
  }
@@ -6520,18 +6640,18 @@ class Reconciler {
6520
6640
  const subtasks = card.subtasks ?? [];
6521
6641
  const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
6522
6642
  if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
6523
- log.debug(TAG28, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
6643
+ log.debug(TAG29, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
6524
6644
  continue;
6525
6645
  }
6526
6646
  if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
6527
- log.debug(TAG28, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
6647
+ log.debug(TAG29, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
6528
6648
  continue;
6529
6649
  }
6530
6650
  if (mode === "review" && !extractBranchFromDescription(card.description)) {
6531
- log.debug(TAG28, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
6651
+ log.debug(TAG29, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
6532
6652
  continue;
6533
6653
  }
6534
- log.info(TAG28, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
6654
+ log.info(TAG29, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
6535
6655
  await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
6536
6656
  }
6537
6657
  }
@@ -6541,18 +6661,18 @@ class Reconciler {
6541
6661
  await this.recoverStrandedInProgress(cards, columns, knownCardIds);
6542
6662
  for (const knownId of knownCardIds) {
6543
6663
  if (!allAgentCardIds.has(knownId)) {
6544
- log.info(TAG28, `Missed unassign: ${knownId} — removing`);
6664
+ log.info(TAG29, `Missed unassign: ${knownId} — removing`);
6545
6665
  await this.pool.removeCard(knownId);
6546
6666
  }
6547
6667
  }
6548
6668
  await this.releaseStalledApprovals(cards, columns, knownCardIds);
6549
- log.debug(TAG28, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
6669
+ log.debug(TAG29, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
6550
6670
  } catch (err) {
6551
- log.error(TAG28, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
6671
+ log.error(TAG29, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
6552
6672
  }
6553
6673
  }
6554
6674
  }
6555
- var TAG28 = "reconcile";
6675
+ var TAG29 = "reconcile";
6556
6676
  var init_reconcile = __esm(() => {
6557
6677
  init_board_helpers();
6558
6678
  init_log();
@@ -6590,7 +6710,7 @@ function prettyBanner(config, version) {
6590
6710
  checks.push({ kind: "ok", message });
6591
6711
  },
6592
6712
  warn(message) {
6593
- log.warn(TAG29, message);
6713
+ log.warn(TAG30, message);
6594
6714
  checks.push({ kind: "warn", message: message.split(`
6595
6715
  `, 1)[0] });
6596
6716
  },
@@ -6615,25 +6735,25 @@ function prettyBanner(config, version) {
6615
6735
  };
6616
6736
  }
6617
6737
  function jsonBanner(config, version) {
6618
- log.info(TAG29, `Harmony Agent Daemon v${version} starting...`);
6619
- log.info(TAG29, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
6738
+ log.info(TAG30, `Harmony Agent Daemon v${version} starting...`);
6739
+ log.info(TAG30, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
6620
6740
  if (config.agent.review.enabled) {
6621
- log.info(TAG29, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
6741
+ log.info(TAG30, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
6622
6742
  }
6623
6743
  let failed = false;
6624
6744
  return {
6625
6745
  setProjectName(_name) {},
6626
6746
  setGitProvider(provider) {
6627
- log.info(TAG29, `Git provider: ${provider}`);
6747
+ log.info(TAG30, `Git provider: ${provider}`);
6628
6748
  },
6629
6749
  setHttpPort(port) {
6630
- log.info(TAG29, `HTTP server on port ${port}`);
6750
+ log.info(TAG30, `HTTP server on port ${port}`);
6631
6751
  },
6632
6752
  check(message) {
6633
- log.info(TAG29, message);
6753
+ log.info(TAG30, message);
6634
6754
  },
6635
6755
  warn(message) {
6636
- log.warn(TAG29, message);
6756
+ log.warn(TAG30, message);
6637
6757
  },
6638
6758
  fail() {
6639
6759
  failed = true;
@@ -6641,7 +6761,7 @@ function jsonBanner(config, version) {
6641
6761
  async ready(message) {
6642
6762
  if (failed)
6643
6763
  return;
6644
- log.info(TAG29, message);
6764
+ log.info(TAG30, message);
6645
6765
  }
6646
6766
  };
6647
6767
  }
@@ -6718,7 +6838,7 @@ function cyan(s) {
6718
6838
  function yellow(s) {
6719
6839
  return `${ANSI.yellow}${s}${ANSI.reset}`;
6720
6840
  }
6721
- var TAG29 = "daemon", RULE_WIDTH = 70, ANSI;
6841
+ var TAG30 = "daemon", RULE_WIDTH = 70, ANSI;
6722
6842
  var init_startup_banner = __esm(() => {
6723
6843
  init_log();
6724
6844
  ANSI = {
@@ -6865,18 +6985,18 @@ class Watcher {
6865
6985
  }
6866
6986
  async start() {
6867
6987
  if (!isPretty()) {
6868
- log.info(TAG30, "Connecting to Supabase realtime (broadcast)...");
6988
+ log.info(TAG31, "Connecting to Supabase realtime (broadcast)...");
6869
6989
  }
6870
6990
  this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
6871
6991
  const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
6872
6992
  const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
6873
- log.debug(TAG30, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
6993
+ log.debug(TAG31, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
6874
6994
  this.onCardBroadcast({
6875
6995
  event: "card_update",
6876
6996
  payload: msg.payload ?? {}
6877
6997
  });
6878
6998
  }).on("broadcast", { event: "card_created" }, (msg) => {
6879
- log.debug(TAG30, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
6999
+ log.debug(TAG31, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
6880
7000
  this.onCardBroadcast({
6881
7001
  event: "card_created",
6882
7002
  payload: msg.payload ?? {}
@@ -6886,29 +7006,29 @@ class Watcher {
6886
7006
  const cardId = payload.card_id;
6887
7007
  const command = payload.command;
6888
7008
  if (cardId && command) {
6889
- log.info(TAG30, `Broadcast: agent_command ${command} for ${cardId}`);
7009
+ log.info(TAG31, `Broadcast: agent_command ${command} for ${cardId}`);
6890
7010
  this.onAgentCommand?.({ cardId, command });
6891
7011
  }
6892
7012
  }).subscribe((status) => {
6893
7013
  if (status === "SUBSCRIBED") {
6894
7014
  this.connected = true;
6895
7015
  if (!isPretty() || !this.suppressStartupLogs) {
6896
- log.info(TAG30, "Broadcast subscription active");
7016
+ log.info(TAG31, "Broadcast subscription active");
6897
7017
  }
6898
7018
  this.maybeResolveReady();
6899
7019
  } else if (status === "CHANNEL_ERROR") {
6900
7020
  this.connected = false;
6901
- log.error(TAG30, "Broadcast channel error — will rely on reconciliation");
7021
+ log.error(TAG31, "Broadcast channel error — will rely on reconciliation");
6902
7022
  } else if (status === "TIMED_OUT") {
6903
7023
  this.connected = false;
6904
- log.warn(TAG30, "Broadcast subscription timed out — retrying...");
7024
+ log.warn(TAG31, "Broadcast subscription timed out — retrying...");
6905
7025
  } else if (status === "CLOSED") {
6906
7026
  this.connected = false;
6907
7027
  }
6908
7028
  });
6909
7029
  this.channel = channel;
6910
7030
  presenceChannel.on("presence", { event: "sync" }, () => {
6911
- log.debug(TAG30, "Presence sync");
7031
+ log.debug(TAG31, "Presence sync");
6912
7032
  }).subscribe(async (status) => {
6913
7033
  if (status === "SUBSCRIBED") {
6914
7034
  await presenceChannel.track({
@@ -6921,7 +7041,7 @@ class Watcher {
6921
7041
  agentName: this.identity.agentName
6922
7042
  });
6923
7043
  if (!isPretty() || !this.suppressStartupLogs) {
6924
- log.info(TAG30, "Presence tracked on board-presence channel");
7044
+ log.info(TAG31, "Presence tracked on board-presence channel");
6925
7045
  }
6926
7046
  this.presenceTracked = true;
6927
7047
  this.maybeResolveReady();
@@ -6943,10 +7063,10 @@ class Watcher {
6943
7063
  this.supabase = null;
6944
7064
  }
6945
7065
  this.connected = false;
6946
- log.info(TAG30, "Broadcast subscription stopped");
7066
+ log.info(TAG31, "Broadcast subscription stopped");
6947
7067
  }
6948
7068
  }
6949
- var TAG30 = "watcher";
7069
+ var TAG31 = "watcher";
6950
7070
  var init_watcher = __esm(() => {
6951
7071
  init_log();
6952
7072
  });
@@ -6959,7 +7079,7 @@ __export(exports_worktree_gc, {
6959
7079
  isTransientGitNetworkError: () => isTransientGitNetworkError,
6960
7080
  WorktreeGc: () => WorktreeGc
6961
7081
  });
6962
- import { execFileSync as execFileSync10 } from "node:child_process";
7082
+ import { execFileSync as execFileSync11 } from "node:child_process";
6963
7083
  import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
6964
7084
  import { resolve as resolve3 } from "node:path";
6965
7085
  function isTransientGitNetworkError(message) {
@@ -7027,16 +7147,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
7027
7147
  }
7028
7148
  }
7029
7149
  try {
7030
- execFileSync10("git", ["worktree", "prune", "--expire=now"], {
7150
+ execFileSync11("git", ["worktree", "prune", "--expire=now"], {
7031
7151
  cwd: repoRoot,
7032
7152
  stdio: "pipe"
7033
7153
  });
7034
7154
  } catch {}
7035
7155
  if (result.removed.length > 0) {
7036
- log.info(TAG31, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
7156
+ log.info(TAG32, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
7037
7157
  }
7038
7158
  if (result.errors.length > 0) {
7039
- log.warn(TAG31, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
7159
+ log.warn(TAG32, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
7040
7160
  }
7041
7161
  return result;
7042
7162
  }
@@ -7058,7 +7178,7 @@ function pruneFailedRemoteBranches(opts) {
7058
7178
  return result;
7059
7179
  }
7060
7180
  try {
7061
- execFileSync10("git", ["fetch", "--prune", "origin"], {
7181
+ execFileSync11("git", ["fetch", "--prune", "origin"], {
7062
7182
  cwd: repoRoot,
7063
7183
  stdio: "pipe",
7064
7184
  ...GIT_NETWORK_EXEC
@@ -7066,7 +7186,7 @@ function pruneFailedRemoteBranches(opts) {
7066
7186
  } catch (err) {
7067
7187
  const detail = gitErrorDetail(err);
7068
7188
  if (isTransientGitNetworkError(detail)) {
7069
- log.debug(TAG31, `Remote branch GC skipped — remote unreachable: ${detail}`);
7189
+ log.debug(TAG32, `Remote branch GC skipped — remote unreachable: ${detail}`);
7070
7190
  return result;
7071
7191
  }
7072
7192
  result.errors.push({ ref: "fetch", error: detail });
@@ -7074,7 +7194,7 @@ function pruneFailedRemoteBranches(opts) {
7074
7194
  const refPattern = `refs/remotes/origin/${opts.prefix}*`;
7075
7195
  let listing = "";
7076
7196
  try {
7077
- listing = execFileSync10("git", [
7197
+ listing = execFileSync11("git", [
7078
7198
  "for-each-ref",
7079
7199
  "--format=%(refname:strip=3) %(committerdate:unix)",
7080
7200
  refPattern
@@ -7105,11 +7225,11 @@ function pruneFailedRemoteBranches(opts) {
7105
7225
  continue;
7106
7226
  }
7107
7227
  if (clock() > sweepDeadline) {
7108
- log.debug(TAG31, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
7228
+ log.debug(TAG32, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
7109
7229
  break;
7110
7230
  }
7111
7231
  try {
7112
- execFileSync10("git", ["push", "origin", `:refs/heads/${ref}`], {
7232
+ execFileSync11("git", ["push", "origin", `:refs/heads/${ref}`], {
7113
7233
  cwd: repoRoot,
7114
7234
  stdio: "pipe",
7115
7235
  ...GIT_NETWORK_EXEC
@@ -7118,17 +7238,17 @@ function pruneFailedRemoteBranches(opts) {
7118
7238
  } catch (err) {
7119
7239
  const detail = gitErrorDetail(err);
7120
7240
  if (isTransientGitNetworkError(detail)) {
7121
- log.debug(TAG31, `Remote branch GC interrupted — remote unreachable: ${detail}`);
7241
+ log.debug(TAG32, `Remote branch GC interrupted — remote unreachable: ${detail}`);
7122
7242
  break;
7123
7243
  }
7124
7244
  result.errors.push({ ref, error: detail });
7125
7245
  }
7126
7246
  }
7127
7247
  if (result.removed.length > 0) {
7128
- log.info(TAG31, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
7248
+ log.info(TAG32, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
7129
7249
  }
7130
7250
  if (result.errors.length > 0) {
7131
- log.warn(TAG31, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
7251
+ log.warn(TAG32, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
7132
7252
  }
7133
7253
  return result;
7134
7254
  }
@@ -7159,27 +7279,27 @@ class WorktreeGc {
7159
7279
  try {
7160
7280
  runWorktreeGc(this.basePath, this.store);
7161
7281
  } catch (err) {
7162
- log.warn(TAG31, `GC tick failed: ${err instanceof Error ? err.message : err}`);
7282
+ log.warn(TAG32, `GC tick failed: ${err instanceof Error ? err.message : err}`);
7163
7283
  }
7164
7284
  if (this.remoteOpts) {
7165
7285
  try {
7166
7286
  pruneFailedRemoteBranches(this.remoteOpts);
7167
7287
  } catch (err) {
7168
- log.warn(TAG31, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
7288
+ log.warn(TAG32, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
7169
7289
  }
7170
7290
  }
7171
7291
  }
7172
7292
  }
7173
7293
  function getRepoRoot2() {
7174
7294
  try {
7175
- return execFileSync10("git", ["rev-parse", "--show-toplevel"], {
7295
+ return execFileSync11("git", ["rev-parse", "--show-toplevel"], {
7176
7296
  encoding: "utf-8"
7177
7297
  }).trim();
7178
7298
  } catch {
7179
7299
  return null;
7180
7300
  }
7181
7301
  }
7182
- 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;
7302
+ var TAG32 = "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;
7183
7303
  var init_worktree_gc = __esm(() => {
7184
7304
  init_log();
7185
7305
  init_worktree();
@@ -7213,12 +7333,12 @@ __export(exports_src, {
7213
7333
  validatePrerequisites: () => validatePrerequisites,
7214
7334
  main: () => main
7215
7335
  });
7216
- import { execFileSync as execFileSync11 } from "node:child_process";
7336
+ import { execFileSync as execFileSync12 } from "node:child_process";
7217
7337
  import { randomUUID as randomUUID2 } from "node:crypto";
7218
7338
  import { createRequire as createRequire2 } from "node:module";
7219
7339
  async function validatePrerequisites(config, banner) {
7220
7340
  try {
7221
- const ver = execFileSync11("claude", ["--version"], {
7341
+ const ver = execFileSync12("claude", ["--version"], {
7222
7342
  encoding: "utf-8"
7223
7343
  }).trim();
7224
7344
  banner.check(`Claude CLI ${ver}`);
@@ -7233,14 +7353,14 @@ async function validatePrerequisites(config, banner) {
7233
7353
  validateGitProviderCli(provider);
7234
7354
  }
7235
7355
  try {
7236
- const status = execFileSync11("git", ["status", "--porcelain"], {
7356
+ const status = execFileSync12("git", ["status", "--porcelain"], {
7237
7357
  encoding: "utf-8"
7238
7358
  }).trim();
7239
7359
  if (status) {
7240
7360
  banner.warn(`Working directory has uncommitted changes:
7241
7361
  ${status}`);
7242
7362
  }
7243
- execFileSync11("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
7363
+ execFileSync12("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
7244
7364
  encoding: "utf-8",
7245
7365
  stdio: "pipe"
7246
7366
  });
@@ -7283,7 +7403,7 @@ async function main() {
7283
7403
  } catch (err) {
7284
7404
  if (err instanceof ConfigValidationError) {
7285
7405
  banner.fail();
7286
- log.error(TAG32, err.message);
7406
+ log.error(TAG33, err.message);
7287
7407
  process.exit(1);
7288
7408
  }
7289
7409
  throw err;
@@ -7392,7 +7512,7 @@ async function main() {
7392
7512
  if (shuttingDown)
7393
7513
  return;
7394
7514
  shuttingDown = true;
7395
- log.info(TAG32, `Received ${signal}, shutting down gracefully...`);
7515
+ log.info(TAG33, `Received ${signal}, shutting down gracefully...`);
7396
7516
  reconciler.stop();
7397
7517
  mergeMonitor?.stop();
7398
7518
  worktreeGc.stop();
@@ -7402,18 +7522,18 @@ async function main() {
7402
7522
  }
7403
7523
  await watcher.stop();
7404
7524
  await pool.shutdown();
7405
- log.info(TAG32, "Daemon stopped.");
7525
+ log.info(TAG33, "Daemon stopped.");
7406
7526
  process.exit(exitCode);
7407
7527
  };
7408
7528
  process.on("SIGINT", () => shutdown("SIGINT"));
7409
7529
  process.on("SIGTERM", () => shutdown("SIGTERM"));
7410
7530
  process.on("uncaughtException", (err) => {
7411
- log.error(TAG32, `Uncaught exception: ${err.message}`);
7531
+ log.error(TAG33, `Uncaught exception: ${err.message}`);
7412
7532
  exitCode = 1;
7413
7533
  shutdown("uncaughtException");
7414
7534
  });
7415
7535
  process.on("unhandledRejection", (reason) => {
7416
- log.error(TAG32, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
7536
+ log.error(TAG33, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
7417
7537
  exitCode = 1;
7418
7538
  shutdown("unhandledRejection");
7419
7539
  });
@@ -7466,35 +7586,35 @@ async function handleBroadcast(event, client, pool, config, agentId) {
7466
7586
  if (assignedAgentId === undefined)
7467
7587
  return;
7468
7588
  if (assignedAgentId === agentId) {
7469
- log.info(TAG32, `Broadcast: card ${cardId} assigned to agent`);
7589
+ log.info(TAG33, `Broadcast: card ${cardId} assigned to agent`);
7470
7590
  try {
7471
7591
  await pool.resetAttemptsForReassign(cardId);
7472
7592
  await tryEnqueueCard(cardId, client, pool, config, agentId);
7473
7593
  } catch (err) {
7474
- log.error(TAG32, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
7594
+ log.error(TAG33, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
7475
7595
  }
7476
7596
  } else if (pool.isCardKnown(cardId)) {
7477
- log.info(TAG32, `Broadcast: card ${cardId} unassigned from agent`);
7597
+ log.info(TAG33, `Broadcast: card ${cardId} unassigned from agent`);
7478
7598
  await pool.removeCard(cardId);
7479
7599
  }
7480
7600
  }
7481
7601
  async function tryEnqueueCard(cardId, client, pool, config, agentId) {
7482
7602
  const { card } = await client.getCard(cardId);
7483
7603
  if (card.assigned_agent_id !== agentId) {
7484
- log.debug(TAG32, `Card ${cardId} no longer assigned to agent — skipping`);
7604
+ log.debug(TAG33, `Card ${cardId} no longer assigned to agent — skipping`);
7485
7605
  return;
7486
7606
  }
7487
7607
  const board = await client.getBoard(config.projectId, { summary: true });
7488
7608
  const columns = board.columns;
7489
7609
  const column = columns.find((c) => c.id === card.column_id);
7490
7610
  if (!column) {
7491
- log.warn(TAG32, `Column not found for card ${cardId}`);
7611
+ log.warn(TAG33, `Column not found for card ${cardId}`);
7492
7612
  return;
7493
7613
  }
7494
7614
  const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
7495
7615
  const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
7496
7616
  if (!isPickupColumn && !isReviewColumn) {
7497
- log.info(TAG32, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
7617
+ log.info(TAG33, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
7498
7618
  return;
7499
7619
  }
7500
7620
  const mode = isReviewColumn ? "review" : "implement";
@@ -7502,16 +7622,16 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
7502
7622
  const cardLabels = resolveCardLabels(card, labelMap);
7503
7623
  const subtasks = card.subtasks ?? [];
7504
7624
  if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
7505
- log.debug(TAG32, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
7625
+ log.debug(TAG33, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
7506
7626
  return;
7507
7627
  }
7508
7628
  if (mode === "review" && !extractBranchFromDescription(card.description)) {
7509
- log.info(TAG32, `Card #${card.short_id} has no branch reference — skipping auto-review`);
7629
+ log.info(TAG33, `Card #${card.short_id} has no branch reference — skipping auto-review`);
7510
7630
  return;
7511
7631
  }
7512
7632
  await pool.enqueue(card, column, cardLabels, subtasks, mode);
7513
7633
  }
7514
- var TAG32 = "daemon", PKG_VERSION;
7634
+ var TAG33 = "daemon", PKG_VERSION;
7515
7635
  var init_src = __esm(() => {
7516
7636
  init_board_helpers();
7517
7637
  init_config();