@bivy/bivy 0.10.1-staging.241 → 0.10.1-staging.243

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.
@@ -215,6 +215,43 @@ export async function resolveAdoptBaseRef(repoDir, branch) {
215
215
  return resolveDefaultBaseRef(repoDir);
216
216
  }
217
217
  }
218
+ /**
219
+ * Resolve the best base for a same-node fork without assuming the source branch
220
+ * ref still exists. Prefer the live source checkout because it preserves its
221
+ * exact committed tip, including commits that were never pushed. Then try the
222
+ * local and remote branch refs before degrading to the repository default.
223
+ */
224
+ export async function resolveForkBaseRef(repoDir, branch, sourceWorktree) {
225
+ if (sourceWorktree) {
226
+ try {
227
+ const { stdout } = await exec("git", ["-C", sourceWorktree, "rev-parse", "--verify", "HEAD"], { cwd: sourceWorktree });
228
+ const head = stdout.trim();
229
+ if (head)
230
+ return head;
231
+ }
232
+ catch {
233
+ // The source checkout may have been pruned; continue through the refs.
234
+ }
235
+ }
236
+ if (branch) {
237
+ try {
238
+ await exec("git", ["-C", repoDir, "rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], { cwd: repoDir });
239
+ return branch;
240
+ }
241
+ catch {
242
+ // The local branch may have been pruned or lost in a re-clone.
243
+ }
244
+ await fetchOrigin(repoDir);
245
+ try {
246
+ await exec("git", ["-C", repoDir, "rev-parse", "--verify", "--quiet", `refs/remotes/origin/${branch}`], { cwd: repoDir });
247
+ return `origin/${branch}`;
248
+ }
249
+ catch {
250
+ // A never-pushed branch is expected to be absent remotely.
251
+ }
252
+ }
253
+ return resolveDefaultBaseRef(repoDir);
254
+ }
218
255
  /**
219
256
  * Whether an existing Bivy-owned checkout at `dest` can be reused as-is, i.e. it
220
257
  * has a `.git` entry AND `git rev-parse` accepts it as a real repository. A
package/dist/server.js CHANGED
@@ -76,7 +76,7 @@ import { checkDiskAdmission } from "./harness/disk-admission.js";
76
76
  import { sandboxTier, setConfiguredSandboxTier, normalizeSandboxTier } from "./harness/sandbox.js";
77
77
  import { setConfiguredAutoAttachToolImages } from "./harness/tool-image-attachments.js";
78
78
  import { injectMcpProxyForSession, injectBivyToolsForSession } from "./harness/mcp-inject.js";
79
- import { parseRepo, isGitHubSlugPart, inferGitHubRepoFromWorkspace, isSharedCloneRoot, resolveGitHubToken, ghCliInstalled, cloneOrUpdateRepo, resolveDefaultBaseRef, resolveBranchBaseRef, resolveAdoptBaseRef, fetchOrigin } from "./repo-workspace.js";
79
+ import { parseRepo, isGitHubSlugPart, inferGitHubRepoFromWorkspace, isSharedCloneRoot, resolveGitHubToken, ghCliInstalled, cloneOrUpdateRepo, resolveDefaultBaseRef, resolveBranchBaseRef, resolveAdoptBaseRef, resolveForkBaseRef, fetchOrigin } from "./repo-workspace.js";
80
80
  import { configureGitAuth, writeGitCredentialEndpoint } from "./git-auth.js";
81
81
  import { GitHubTaskPoller, resolveGitHubTaskConfig, buildTaskPrompt, buildResumePrompt, buildInteractiveResumePrompt, DEFAULT_ISSUE_INSTRUCTIONS, parseBivyDirectives, commitAll, pushBranch, mergeBaseIntoBranch, completeMerge, abortMerge, findOpenPullRequestForBranch, findPullRequestsForBranch, findMergedPullRequestForBranch, issueBranchName, getPullRequest, commentIssueOnce, listOpenLabelledIssues, selectActionableIssues, getIssue, getIssueCommentBody, addLabel, removeLabel, announcePickup, } from "./github-tasks.js";
82
82
  import { buildLinearTaskPrompt, getLinearIssue, linearBranchName } from "./linear-tasks.js";
@@ -8045,6 +8045,7 @@ const forkStandUp = createForkStandUp({
8045
8045
  createWorktree,
8046
8046
  resolveDefaultBaseRef,
8047
8047
  resolveAdoptBaseRef,
8048
+ resolveForkBaseRef,
8048
8049
  applyDirtyPatch,
8049
8050
  gitRepoRoot,
8050
8051
  materializeFork,
@@ -77,10 +77,11 @@ export function createForkStandUp(deps) {
77
77
  // pickups don't race on `git worktree add` or clobber each other's trees.
78
78
  const wt = await deps.withRepoLock(repoDir, async () => {
79
79
  if (opts.worktree === "fresh") {
80
- // Same-node fork: cut a NEW branch from the source's LOCAL branch (which
81
- // holds its latest, possibly-unpushed commits) or the repo default.
80
+ // Same-node fork: preserve the live source tip when possible, but do
81
+ // not fail when its checkout or branch ref has already been pruned.
82
82
  const forkBranch = `${srcBranch ?? "fork"}-fork-${randomBytes(4).toString("hex")}`;
83
- return deps.createWorktree({ repoDir, id: forkBranch, branch: forkBranch, base: srcBranch ?? await deps.resolveDefaultBaseRef(repoDir) });
83
+ const base = await deps.resolveForkBaseRef(repoDir, srcBranch, bundle.record.worktree);
84
+ return deps.createWorktree({ repoDir, id: forkBranch, branch: forkBranch, base });
84
85
  }
85
86
  // Cross-node adopt: the source branch has no LOCAL ref here. Base the
86
87
  // adopted branch on the pushed `origin/<branch>` so committed work travels
package/dist/worktree.js CHANGED
@@ -77,16 +77,26 @@ export async function createWorktree(opts) {
77
77
  // first, then check the existing branch out into a fresh worktree.
78
78
  const localExists = await refExists(repoRoot, `refs/heads/${branch}`);
79
79
  const remoteExists = !localExists && (await refExists(repoRoot, `refs/remotes/origin/${branch}`));
80
- if (!localExists && !remoteExists)
81
- throw error;
82
- await removeWorktree(repoRoot, wtPath);
83
- fs.rmSync(wtPath, { recursive: true, force: true });
84
- if (localExists) {
85
- await exec("git", ["-C", repoRoot, "worktree", "add", wtPath, branch]);
80
+ if (localExists || remoteExists) {
81
+ await removeWorktree(repoRoot, wtPath);
82
+ fs.rmSync(wtPath, { recursive: true, force: true });
83
+ if (localExists) {
84
+ await exec("git", ["-C", repoRoot, "worktree", "add", wtPath, branch]);
85
+ }
86
+ else {
87
+ // Recreate the local branch from origin, then check it out in the worktree.
88
+ await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, wtPath, `origin/${branch}`]);
89
+ }
90
+ }
91
+ else if (base !== "HEAD" && !(await refExists(repoRoot, base))) {
92
+ // Defense in depth for stale internal metadata: callers should resolve a
93
+ // fork base first, but a missing ref must not prevent session stand-up.
94
+ await removeWorktree(repoRoot, wtPath);
95
+ fs.rmSync(wtPath, { recursive: true, force: true });
96
+ await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, wtPath, "HEAD"]);
86
97
  }
87
98
  else {
88
- // Recreate the local branch from origin, then check it out in the worktree.
89
- await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, wtPath, `origin/${branch}`]);
99
+ throw error;
90
100
  }
91
101
  }
92
102
  // Opportunistically reuse a sibling worktree's installed deps (node_modules,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.10.1-staging.241",
3
+ "version": "0.10.1-staging.243",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",