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