opencara 0.24.2 → 0.25.0

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 (2) hide show
  1. package/dist/index.js +92 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1790,7 +1790,7 @@ function fetchPRRef(bareRepoPath, prNumber, ghAvailable) {
1790
1790
  bareRepoPath
1791
1791
  );
1792
1792
  }
1793
- function addWorktree(bareRepoPath, worktreeKey) {
1793
+ function addWorktree(bareRepoPath, worktreeKey, targetRef) {
1794
1794
  validatePathSegment(worktreeKey, "worktreeKey");
1795
1795
  const repoName = path4.basename(bareRepoPath, ".git");
1796
1796
  const worktreeBase = path4.join(path4.dirname(bareRepoPath), `${repoName}-worktrees`);
@@ -1799,7 +1799,7 @@ function addWorktree(bareRepoPath, worktreeKey) {
1799
1799
  return worktreePath;
1800
1800
  }
1801
1801
  fs4.mkdirSync(worktreeBase, { recursive: true });
1802
- gitExec("git", ["worktree", "add", "--detach", worktreePath, "FETCH_HEAD"], bareRepoPath);
1802
+ gitExec("git", ["worktree", "add", "--detach", worktreePath, targetRef], bareRepoPath);
1803
1803
  return worktreePath;
1804
1804
  }
1805
1805
  function removeWorktree(bareRepoPath, worktreePath) {
@@ -1819,6 +1819,9 @@ function repoKeyFromBarePath(bareRepoPath) {
1819
1819
  const owner = path4.basename(path4.dirname(bareRepoPath));
1820
1820
  return `${owner}/${repoName}`;
1821
1821
  }
1822
+ function resolveFetchedPrCommit(bareRepoPath) {
1823
+ return gitExec("git", ["rev-parse", "--verify", "FETCH_HEAD"], bareRepoPath).trim();
1824
+ }
1822
1825
  async function checkoutWorktree(owner, repo, prNumber, baseDir, _taskId, sparseOptions) {
1823
1826
  validatePathSegment(owner, "owner");
1824
1827
  validatePathSegment(repo, "repo");
@@ -1829,8 +1832,9 @@ async function checkoutWorktree(owner, repo, prNumber, baseDir, _taskId, sparseO
1829
1832
  return withRepoLock(repoKey, () => {
1830
1833
  const { bareRepoPath, cloned } = ensureBareClone(owner, repo, baseDir, ghAvailable);
1831
1834
  fetchPRRef(bareRepoPath, prNumber, ghAvailable);
1832
- const worktreePath = addWorktree(bareRepoPath, wtKey);
1833
- gitExec("git", ["checkout", "--detach", "--force", "FETCH_HEAD"], worktreePath);
1835
+ const prHeadCommit = resolveFetchedPrCommit(bareRepoPath);
1836
+ const worktreePath = addWorktree(bareRepoPath, wtKey, prHeadCommit);
1837
+ gitExec("git", ["checkout", "--detach", "--force", prHeadCommit], worktreePath);
1834
1838
  if (useSparse) {
1835
1839
  configureSparseCheckout(worktreePath, sparseOptions.diffPaths);
1836
1840
  }
@@ -1876,18 +1880,71 @@ function gitExec(command, args, cwd, opts) {
1876
1880
  throw new Error(sanitizeTokens(message));
1877
1881
  }
1878
1882
  }
1879
- function diffFromWorktree(bareRepoPath, worktreePath, baseRef, ghAvailable, maxDiffBytes = 128 * 1024 * 1024) {
1880
- if (!/^[A-Za-z0-9_./-]+$/.test(baseRef) || baseRef.startsWith("-")) {
1881
- throw new Error(`Invalid base ref: ${baseRef}`);
1882
- }
1883
+ var DEFAULT_BRANCH_FALLBACKS = ["main", "master"];
1884
+ function isValidBranchName(name) {
1885
+ if (!name) return false;
1886
+ if (name.startsWith("-")) return false;
1887
+ if (name.includes("..")) return false;
1888
+ return /^[A-Za-z0-9_./-]+$/.test(name);
1889
+ }
1890
+ function fetchBranch(bareRepoPath, branch, ghAvailable) {
1883
1891
  const credArgs = ghAvailable ? ["-c", `credential.helper=${GH_CREDENTIAL_HELPER}`] : [];
1884
1892
  gitExec(
1885
1893
  "git",
1886
- [...credArgs, "fetch", "--force", "origin", `${baseRef}:refs/remotes/origin/${baseRef}`],
1894
+ [...credArgs, "fetch", "--force", "origin", `${branch}:refs/remotes/origin/${branch}`],
1887
1895
  bareRepoPath
1888
1896
  );
1897
+ }
1898
+ function isRemoteRefMissingError(err) {
1899
+ const msg = err instanceof Error ? err.message : String(err);
1900
+ return /couldn't find remote ref/i.test(msg) || /couldnt find remote ref/i.test(msg) || /no such ref/i.test(msg) || /remote ref.*not found/i.test(msg) || /unknown revision or path not in the working tree/i.test(msg);
1901
+ }
1902
+ function deriveDefaultBranch(bareRepoPath, ghAvailable) {
1903
+ try {
1904
+ const out = gitExec("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], bareRepoPath).trim();
1905
+ const prefix = "refs/remotes/origin/";
1906
+ if (out.startsWith(prefix)) {
1907
+ const branch = out.slice(prefix.length);
1908
+ if (isValidBranchName(branch)) {
1909
+ try {
1910
+ fetchBranch(bareRepoPath, branch, ghAvailable);
1911
+ return branch;
1912
+ } catch {
1913
+ }
1914
+ }
1915
+ }
1916
+ } catch {
1917
+ }
1918
+ for (const candidate of DEFAULT_BRANCH_FALLBACKS) {
1919
+ try {
1920
+ fetchBranch(bareRepoPath, candidate, ghAvailable);
1921
+ return candidate;
1922
+ } catch {
1923
+ }
1924
+ }
1925
+ throw new Error("Cannot derive default branch: origin/HEAD, main, and master all failed");
1926
+ }
1927
+ function diffFromWorktree(bareRepoPath, worktreePath, baseRef, ghAvailable, maxDiffBytes = 128 * 1024 * 1024) {
1928
+ let resolvedBaseRef;
1929
+ if (baseRef) {
1930
+ if (!isValidBranchName(baseRef)) {
1931
+ throw new Error(`Invalid base ref: ${baseRef}`);
1932
+ }
1933
+ try {
1934
+ fetchBranch(bareRepoPath, baseRef, ghAvailable);
1935
+ resolvedBaseRef = baseRef;
1936
+ } catch (err) {
1937
+ if (!isRemoteRefMissingError(err)) {
1938
+ throw err;
1939
+ }
1940
+ resolvedBaseRef = void 0;
1941
+ }
1942
+ }
1943
+ if (!resolvedBaseRef) {
1944
+ resolvedBaseRef = deriveDefaultBranch(bareRepoPath, ghAvailable);
1945
+ }
1889
1946
  try {
1890
- return gitExec("git", ["diff", `origin/${baseRef}...HEAD`], worktreePath, {
1947
+ return gitExec("git", ["diff", `origin/${resolvedBaseRef}...HEAD`], worktreePath, {
1891
1948
  maxBuffer: maxDiffBytes
1892
1949
  });
1893
1950
  } catch (err) {
@@ -4683,6 +4740,12 @@ function registerShutdownHandlers(controller, log, graceMs = SHUTDOWN_GRACE_MS)
4683
4740
  };
4684
4741
  }
4685
4742
  var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
4743
+ function build404Hint(isGhAuthenticated) {
4744
+ if (isGhAuthenticated) {
4745
+ return ". Diff fetch returned 404. Possible causes: (a) PR not found, (b) the installation can't access this repo, or (c) a transient GitHub outage. See any `[fetchDiffViaGh]` warning above for the underlying `gh api` error.";
4746
+ }
4747
+ return ". If this is a private repo, ensure gh CLI is installed and authenticated: gh auth login";
4748
+ }
4686
4749
  function toApiDiffUrl(webUrl) {
4687
4750
  const match = webUrl.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\.diff)?$/);
4688
4751
  if (!match) return null;
@@ -4744,7 +4807,7 @@ function computeRoles(agent) {
4744
4807
  return ["review", "summary", "implement", "fix"];
4745
4808
  }
4746
4809
  var DIFF_FETCH_TIMEOUT_MS = 6e4;
4747
- async function fetchDiffHttp(url, headers, signal, maxDiffSizeKb) {
4810
+ async function fetchDiffHttp(url, headers, signal, maxDiffSizeKb, isGhAuthenticatedFn = isGhAvailable) {
4748
4811
  const maxBytes = maxDiffSizeKb ? maxDiffSizeKb * 1024 : Infinity;
4749
4812
  const controller = new AbortController();
4750
4813
  const timer = setTimeout(() => controller.abort(), DIFF_FETCH_TIMEOUT_MS);
@@ -4765,7 +4828,7 @@ async function fetchDiffHttp(url, headers, signal, maxDiffSizeKb) {
4765
4828
  clearTimeout(timer);
4766
4829
  signal?.removeEventListener("abort", onParentAbort);
4767
4830
  if (!response.ok) {
4768
- const hint = response.status === 404 ? ". If this is a private repo, ensure gh CLI is installed and authenticated: gh auth login" : "";
4831
+ const hint = response.status === 404 ? build404Hint(isGhAuthenticatedFn()) : "";
4769
4832
  const msg = `Failed to fetch diff: ${response.status} ${response.statusText}${hint}`;
4770
4833
  if (NON_RETRYABLE_STATUSES.has(response.status)) {
4771
4834
  throw new NonRetryableError(msg);
@@ -5050,7 +5113,12 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
5050
5113
  );
5051
5114
  taskReviewDeps = { ...reviewDeps, codebaseDir: null };
5052
5115
  }
5053
- if (taskCheckoutPath && taskBareRepoPath && base_ref) {
5116
+ if (taskCheckoutPath && taskBareRepoPath) {
5117
+ if (!base_ref) {
5118
+ logWarn(
5119
+ ` Warning: task ${task_id} has no base_ref \u2014 deriving default branch from worktree`
5120
+ );
5121
+ }
5054
5122
  try {
5055
5123
  const ghAvailable = isGhAvailable();
5056
5124
  const maxDiffBytes = reviewDeps.maxDiffSizeKb ? reviewDeps.maxDiffSizeKb * 1024 : void 0;
@@ -5073,23 +5141,16 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
5073
5141
  diffContent = gitDiff;
5074
5142
  log(` Diff generated via git (${Math.round(diffContent.length / 1024)}KB)`);
5075
5143
  } catch (err) {
5144
+ const message = err.message;
5076
5145
  if (err instanceof DiffTooLargeError) {
5077
- logError(` ${err.message}`);
5078
- await safeReject(
5079
- client,
5080
- task_id,
5081
- agentId,
5082
- `Cannot access diff: ${err.message}`,
5083
- logger
5084
- );
5085
- return { diffFetchFailed: true };
5146
+ logError(` ${message}`);
5147
+ } else {
5148
+ logError(` git diff failed for task ${task_id}: ${message}`);
5086
5149
  }
5087
- logWarn(
5088
- ` Warning: git diff failed (${err.message}) \u2014 falling back to API fetch`
5089
- );
5150
+ await safeReject(client, task_id, agentId, `Cannot access diff: ${message}`, logger);
5151
+ return { diffFetchFailed: true };
5090
5152
  }
5091
- }
5092
- if (!diffContent) {
5153
+ } else {
5093
5154
  try {
5094
5155
  const result = await fetchDiff(diff_url, owner, repo, pr_number, {
5095
5156
  githubToken: client.currentToken,
@@ -5732,7 +5793,7 @@ function sleep2(ms, signal) {
5732
5793
  async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
5733
5794
  const client = new ApiClient(platformUrl, {
5734
5795
  authToken: options?.authToken,
5735
- cliVersion: "0.24.2",
5796
+ cliVersion: "0.25.0",
5736
5797
  versionOverride: options?.versionOverride,
5737
5798
  onTokenRefresh: options?.onTokenRefresh
5738
5799
  });
@@ -6097,7 +6158,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6097
6158
  const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
6098
6159
  const client = new ApiClient(config.platformUrl, {
6099
6160
  authToken: oauthToken,
6100
- cliVersion: "0.24.2",
6161
+ cliVersion: "0.25.0",
6101
6162
  versionOverride,
6102
6163
  onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
6103
6164
  });
@@ -6446,7 +6507,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
6446
6507
  }
6447
6508
  config = loadConfig();
6448
6509
  }
6449
- console.log(formatVersionBanner("0.24.2", "86722e7"));
6510
+ console.log(formatVersionBanner("0.25.0", "a984ae2"));
6450
6511
  if (config.agents && config.agents.length > 0) {
6451
6512
  const toolEntries = config.agents.map((a) => ({
6452
6513
  tool: a.tool,
@@ -7268,7 +7329,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
7268
7329
  });
7269
7330
 
7270
7331
  // src/index.ts
7271
- var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.24.2"} (${"86722e7"})`);
7332
+ var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.25.0"} (${"a984ae2"})`);
7272
7333
  program.addCommand(agentCommand);
7273
7334
  program.addCommand(authCommand());
7274
7335
  program.addCommand(dedupCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.24.2",
3
+ "version": "0.25.0",
4
4
  "description": "Distributed AI code review agent — poll, review, and submit PR reviews using your own AI tools",
5
5
  "type": "module",
6
6
  "license": "MIT",