@gethmy/agent 1.11.0 → 1.11.2

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 +72 -15
  2. package/dist/index.js +72 -15
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1228,7 +1228,8 @@ function fetchBaseBranch(repoRoot, baseBranch, attempts = 3, fetchImpl = (root,
1228
1228
  log.warn(TAG5, `fetch origin ${baseBranch} failed (attempt ${attempt}/${attempts})`);
1229
1229
  }
1230
1230
  }
1231
- const detail = lastErr instanceof Error ? lastErr.message : String(lastErr);
1231
+ const e = lastErr;
1232
+ const detail = e?.stderr?.toString?.().trim() || (lastErr instanceof Error ? lastErr.message : String(lastErr));
1232
1233
  throw new WorktreeBaseError(`Could not fetch origin/${baseBranch} after ${attempts} attempts — ` + `refusing to build on a stale base. ${detail}`);
1233
1234
  }
1234
1235
  function createWorktree(basePath, baseBranch, branchName) {
@@ -1350,6 +1351,16 @@ var init_worktree = __esm(() => {
1350
1351
  import { execFileSync as execFileSync4, execSync as execSync3 } from "node:child_process";
1351
1352
  import { existsSync as existsSync3 } from "node:fs";
1352
1353
  import { resolve as resolve2 } from "node:path";
1354
+ function gitErrorDetail(err) {
1355
+ const e = err;
1356
+ const stderr = e?.stderr?.toString?.().trim();
1357
+ if (stderr)
1358
+ return stderr;
1359
+ const stdout = e?.stdout?.toString?.().trim();
1360
+ if (stdout)
1361
+ return stdout;
1362
+ return err instanceof Error ? err.message : String(err);
1363
+ }
1353
1364
  function checkoutExistingBranch(basePath, branchName) {
1354
1365
  const repoRoot = execFileSync4("git", ["rev-parse", "--show-toplevel"], {
1355
1366
  encoding: "utf-8"
@@ -1370,8 +1381,8 @@ function checkoutExistingBranch(basePath, branchName) {
1370
1381
  cwd: repoRoot,
1371
1382
  stdio: "pipe"
1372
1383
  });
1373
- } catch {
1374
- throw new Error(`Failed to fetch remote branch: ${branchName}`);
1384
+ } catch (err) {
1385
+ throw new Error(`Failed to fetch remote branch ${branchName}: ${gitErrorDetail(err)}`);
1375
1386
  }
1376
1387
  try {
1377
1388
  execFileSync4("git", ["branch", "-D", branchName], {
@@ -1380,15 +1391,19 @@ function checkoutExistingBranch(basePath, branchName) {
1380
1391
  });
1381
1392
  } catch {}
1382
1393
  log.info(TAG6, `Creating review worktree: ${worktreeDir} (branch: ${branchName})`);
1383
- execFileSync4("git", [
1384
- "worktree",
1385
- "add",
1386
- "--track",
1387
- "-b",
1388
- branchName,
1389
- worktreeDir,
1390
- `origin/${branchName}`
1391
- ], { cwd: repoRoot, stdio: "pipe" });
1394
+ try {
1395
+ execFileSync4("git", [
1396
+ "worktree",
1397
+ "add",
1398
+ "--track",
1399
+ "-b",
1400
+ branchName,
1401
+ worktreeDir,
1402
+ `origin/${branchName}`
1403
+ ], { cwd: repoRoot, stdio: "pipe" });
1404
+ } catch (err) {
1405
+ throw new Error(`Failed to create review worktree for ${branchName}: ${gitErrorDetail(err)}`);
1406
+ }
1392
1407
  log.info(TAG6, "Installing dependencies in review worktree...");
1393
1408
  try {
1394
1409
  execSync3(installCommand(), {
@@ -2613,6 +2628,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2613
2628
  reviewFindings: [],
2614
2629
  revertWarnings: []
2615
2630
  };
2631
+ commitUncommittedChanges(worktreePath, card);
2616
2632
  const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
2617
2633
  if (!hasCommits) {
2618
2634
  const { maxTurnsExhausted, failureSummary } = describeNoCommitFailure(sessionStats?.cost?.numTurns ?? 0, config.claude.maxTurns);
@@ -2787,6 +2803,37 @@ function readHeadSha(worktreePath) {
2787
2803
  return null;
2788
2804
  }
2789
2805
  }
2806
+ function commitUncommittedChanges(worktreePath, card) {
2807
+ let status = "";
2808
+ try {
2809
+ status = execFileSync9("git", ["status", "--porcelain"], {
2810
+ cwd: worktreePath,
2811
+ encoding: "utf-8"
2812
+ }).trim();
2813
+ } catch (err) {
2814
+ log.warn(TAG14, `git status failed in ${worktreePath}: ${err instanceof Error ? err.message : err}`);
2815
+ return false;
2816
+ }
2817
+ if (status.length === 0)
2818
+ return false;
2819
+ const title = card.title?.trim() || "agent changes";
2820
+ const message = `#${card.short_id} ${title}`;
2821
+ try {
2822
+ execFileSync9("git", ["add", "-A"], {
2823
+ cwd: worktreePath,
2824
+ encoding: "utf-8"
2825
+ });
2826
+ execFileSync9("git", ["commit", "-m", message], {
2827
+ cwd: worktreePath,
2828
+ encoding: "utf-8"
2829
+ });
2830
+ log.warn(TAG14, `Auto-committed uncommitted worktree changes for #${card.short_id} — agent ended without committing`);
2831
+ return true;
2832
+ } catch (err) {
2833
+ log.error(TAG14, `auto-commit failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2834
+ return false;
2835
+ }
2836
+ }
2790
2837
  function checkHasCommits(worktreePath, baseBranch) {
2791
2838
  try {
2792
2839
  const count = execFileSync9("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
@@ -6164,6 +6211,16 @@ class Worker {
6164
6211
  } catch {}
6165
6212
  await this.recordOutcome(card.id, "failure");
6166
6213
  } else if (this.runId && this.aborted) {
6214
+ try {
6215
+ await this.client.updateCard(card.id, { assignedAgentId: null });
6216
+ } catch (err) {
6217
+ log.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
6218
+ }
6219
+ try {
6220
+ await runTransition(this.client, card, { removeLabels: ["agent"] });
6221
+ } catch (tErr) {
6222
+ log.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
6223
+ }
6167
6224
  try {
6168
6225
  await this.stateStore.endRun(this.runId, "paused", {
6169
6226
  errorMessage: "cancelled",
@@ -7748,7 +7805,7 @@ import { resolve as resolve3 } from "node:path";
7748
7805
  function isTransientGitNetworkError(message) {
7749
7806
  return TRANSIENT_GIT_NETWORK_ERROR.test(message);
7750
7807
  }
7751
- function gitErrorDetail(err) {
7808
+ function gitErrorDetail2(err) {
7752
7809
  if (err && typeof err === "object" && "stderr" in err) {
7753
7810
  const stderr = String(err.stderr ?? "").trim();
7754
7811
  if (stderr)
@@ -7847,7 +7904,7 @@ function pruneFailedRemoteBranches(opts) {
7847
7904
  ...GIT_NETWORK_EXEC
7848
7905
  });
7849
7906
  } catch (err) {
7850
- const detail = gitErrorDetail(err);
7907
+ const detail = gitErrorDetail2(err);
7851
7908
  if (isTransientGitNetworkError(detail)) {
7852
7909
  log.debug(TAG33, `Remote branch GC skipped — remote unreachable: ${detail}`);
7853
7910
  return result;
@@ -7899,7 +7956,7 @@ function pruneFailedRemoteBranches(opts) {
7899
7956
  });
7900
7957
  result.removed.push(ref);
7901
7958
  } catch (err) {
7902
- const detail = gitErrorDetail(err);
7959
+ const detail = gitErrorDetail2(err);
7903
7960
  if (isTransientGitNetworkError(detail)) {
7904
7961
  log.debug(TAG33, `Remote branch GC interrupted — remote unreachable: ${detail}`);
7905
7962
  break;
package/dist/index.js CHANGED
@@ -1227,7 +1227,8 @@ function fetchBaseBranch(repoRoot, baseBranch, attempts = 3, fetchImpl = (root,
1227
1227
  log.warn(TAG5, `fetch origin ${baseBranch} failed (attempt ${attempt}/${attempts})`);
1228
1228
  }
1229
1229
  }
1230
- const detail = lastErr instanceof Error ? lastErr.message : String(lastErr);
1230
+ const e = lastErr;
1231
+ const detail = e?.stderr?.toString?.().trim() || (lastErr instanceof Error ? lastErr.message : String(lastErr));
1231
1232
  throw new WorktreeBaseError(`Could not fetch origin/${baseBranch} after ${attempts} attempts — ` + `refusing to build on a stale base. ${detail}`);
1232
1233
  }
1233
1234
  function createWorktree(basePath, baseBranch, branchName) {
@@ -1349,6 +1350,16 @@ var init_worktree = __esm(() => {
1349
1350
  import { execFileSync as execFileSync4, execSync as execSync3 } from "node:child_process";
1350
1351
  import { existsSync as existsSync3 } from "node:fs";
1351
1352
  import { resolve as resolve2 } from "node:path";
1353
+ function gitErrorDetail(err) {
1354
+ const e = err;
1355
+ const stderr = e?.stderr?.toString?.().trim();
1356
+ if (stderr)
1357
+ return stderr;
1358
+ const stdout = e?.stdout?.toString?.().trim();
1359
+ if (stdout)
1360
+ return stdout;
1361
+ return err instanceof Error ? err.message : String(err);
1362
+ }
1352
1363
  function checkoutExistingBranch(basePath, branchName) {
1353
1364
  const repoRoot = execFileSync4("git", ["rev-parse", "--show-toplevel"], {
1354
1365
  encoding: "utf-8"
@@ -1369,8 +1380,8 @@ function checkoutExistingBranch(basePath, branchName) {
1369
1380
  cwd: repoRoot,
1370
1381
  stdio: "pipe"
1371
1382
  });
1372
- } catch {
1373
- throw new Error(`Failed to fetch remote branch: ${branchName}`);
1383
+ } catch (err) {
1384
+ throw new Error(`Failed to fetch remote branch ${branchName}: ${gitErrorDetail(err)}`);
1374
1385
  }
1375
1386
  try {
1376
1387
  execFileSync4("git", ["branch", "-D", branchName], {
@@ -1379,15 +1390,19 @@ function checkoutExistingBranch(basePath, branchName) {
1379
1390
  });
1380
1391
  } catch {}
1381
1392
  log.info(TAG6, `Creating review worktree: ${worktreeDir} (branch: ${branchName})`);
1382
- execFileSync4("git", [
1383
- "worktree",
1384
- "add",
1385
- "--track",
1386
- "-b",
1387
- branchName,
1388
- worktreeDir,
1389
- `origin/${branchName}`
1390
- ], { cwd: repoRoot, stdio: "pipe" });
1393
+ try {
1394
+ execFileSync4("git", [
1395
+ "worktree",
1396
+ "add",
1397
+ "--track",
1398
+ "-b",
1399
+ branchName,
1400
+ worktreeDir,
1401
+ `origin/${branchName}`
1402
+ ], { cwd: repoRoot, stdio: "pipe" });
1403
+ } catch (err) {
1404
+ throw new Error(`Failed to create review worktree for ${branchName}: ${gitErrorDetail(err)}`);
1405
+ }
1391
1406
  log.info(TAG6, "Installing dependencies in review worktree...");
1392
1407
  try {
1393
1408
  execSync3(installCommand(), {
@@ -2612,6 +2627,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
2612
2627
  reviewFindings: [],
2613
2628
  revertWarnings: []
2614
2629
  };
2630
+ commitUncommittedChanges(worktreePath, card);
2615
2631
  const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
2616
2632
  if (!hasCommits) {
2617
2633
  const { maxTurnsExhausted, failureSummary } = describeNoCommitFailure(sessionStats?.cost?.numTurns ?? 0, config.claude.maxTurns);
@@ -2786,6 +2802,37 @@ function readHeadSha(worktreePath) {
2786
2802
  return null;
2787
2803
  }
2788
2804
  }
2805
+ function commitUncommittedChanges(worktreePath, card) {
2806
+ let status = "";
2807
+ try {
2808
+ status = execFileSync9("git", ["status", "--porcelain"], {
2809
+ cwd: worktreePath,
2810
+ encoding: "utf-8"
2811
+ }).trim();
2812
+ } catch (err) {
2813
+ log.warn(TAG14, `git status failed in ${worktreePath}: ${err instanceof Error ? err.message : err}`);
2814
+ return false;
2815
+ }
2816
+ if (status.length === 0)
2817
+ return false;
2818
+ const title = card.title?.trim() || "agent changes";
2819
+ const message = `#${card.short_id} ${title}`;
2820
+ try {
2821
+ execFileSync9("git", ["add", "-A"], {
2822
+ cwd: worktreePath,
2823
+ encoding: "utf-8"
2824
+ });
2825
+ execFileSync9("git", ["commit", "-m", message], {
2826
+ cwd: worktreePath,
2827
+ encoding: "utf-8"
2828
+ });
2829
+ log.warn(TAG14, `Auto-committed uncommitted worktree changes for #${card.short_id} — agent ended without committing`);
2830
+ return true;
2831
+ } catch (err) {
2832
+ log.error(TAG14, `auto-commit failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
2833
+ return false;
2834
+ }
2835
+ }
2789
2836
  function checkHasCommits(worktreePath, baseBranch) {
2790
2837
  try {
2791
2838
  const count = execFileSync9("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
@@ -6163,6 +6210,16 @@ class Worker {
6163
6210
  } catch {}
6164
6211
  await this.recordOutcome(card.id, "failure");
6165
6212
  } else if (this.runId && this.aborted) {
6213
+ try {
6214
+ await this.client.updateCard(card.id, { assignedAgentId: null });
6215
+ } catch (err) {
6216
+ log.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
6217
+ }
6218
+ try {
6219
+ await runTransition(this.client, card, { removeLabels: ["agent"] });
6220
+ } catch (tErr) {
6221
+ log.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
6222
+ }
6166
6223
  try {
6167
6224
  await this.stateStore.endRun(this.runId, "paused", {
6168
6225
  errorMessage: "cancelled",
@@ -7747,7 +7804,7 @@ import { resolve as resolve3 } from "node:path";
7747
7804
  function isTransientGitNetworkError(message) {
7748
7805
  return TRANSIENT_GIT_NETWORK_ERROR.test(message);
7749
7806
  }
7750
- function gitErrorDetail(err) {
7807
+ function gitErrorDetail2(err) {
7751
7808
  if (err && typeof err === "object" && "stderr" in err) {
7752
7809
  const stderr = String(err.stderr ?? "").trim();
7753
7810
  if (stderr)
@@ -7846,7 +7903,7 @@ function pruneFailedRemoteBranches(opts) {
7846
7903
  ...GIT_NETWORK_EXEC
7847
7904
  });
7848
7905
  } catch (err) {
7849
- const detail = gitErrorDetail(err);
7906
+ const detail = gitErrorDetail2(err);
7850
7907
  if (isTransientGitNetworkError(detail)) {
7851
7908
  log.debug(TAG33, `Remote branch GC skipped — remote unreachable: ${detail}`);
7852
7909
  return result;
@@ -7898,7 +7955,7 @@ function pruneFailedRemoteBranches(opts) {
7898
7955
  });
7899
7956
  result.removed.push(ref);
7900
7957
  } catch (err) {
7901
- const detail = gitErrorDetail(err);
7958
+ const detail = gitErrorDetail2(err);
7902
7959
  if (isTransientGitNetworkError(detail)) {
7903
7960
  log.debug(TAG33, `Remote branch GC interrupted — remote unreachable: ${detail}`);
7904
7961
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/agent",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "Push-based agent daemon for Harmony — watches board assignments and spawns Claude CLI workers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",