feat(routing): cross-repo task routing + orphan-branch cleanup (#17)
Two related fixes for the agent-workflow defects exposed by the 2-May-2026 batch: 1. **Cross-repo task routing** (`task.repo_url` override). `resolveRepoRoot` now consults `task.repo_url` first; matches against per-repo env-var (`SCRUM4ME_REPO_ROOT_REPO_<name>`), `~/.scrum4me-agent-config.json` `repoRoots[<name>]`, and finally `~/Projects/<name>/.git`. Falls back to product-level resolution when null. Tasks tracked under one product but targeting another repo (e.g. MCP-server tasks under the main product's PBI) now work. `getFullJobContext` exposes `task.repo_url` to the agent. `attachWorktreeToJob` accepts and forwards it. 2. **Orphan-branch cleanup** in `createWorktreeForJob`. Previously a name-collision suffixed with a timestamp, leaving the agent on an unpredictable `feat/story-XXX-<ms>`-name. Worse, in the 2-May batch the agent ended up reusing an orphan branch from an earlier story (`feat/story-x35n155c`) and pushed to a remote ref that did not exist, causing 'src refspec does not match any'. Now: detect orphan, attempt to remove its (stale) worktree if any, delete the local branch, and recreate with the predictable name. Timestamp-suffix is the last resort. Vendor submodule bumped to pick up `Task.repo_url` from Scrum4Me #54. Tests: 129/129 — `suffixes branch name with timestamp` updated to `removes orphan branch and reuses the predictable name`. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1fe6ccf609
commit
2c85f4d239
5 changed files with 88 additions and 16 deletions
|
|
@ -81,9 +81,30 @@ export async function createWorktreeForJob(opts: {
|
|||
return { worktreePath, branchName }
|
||||
}
|
||||
|
||||
// Fresh branch: suffix with timestamp when name collision occurs
|
||||
// Fresh branch: if a local branch with this name already exists, it is an
|
||||
// orphan from a prior failed run (the agent didn't push or branch was
|
||||
// never tied to a worktree). Remove the orphan so the new worktree gets
|
||||
// the predictable `feat/story-<id>`-name; this prevents the kind of
|
||||
// 2-May-2026 failure where the agent inherited an unrelated suffix and
|
||||
// pushed to a non-existent remote ref.
|
||||
if (await branchExists(repoRoot, branchName)) {
|
||||
branchName = `${branchName}-${Date.now()}`
|
||||
const occupant = await findWorktreeForBranch(repoRoot, branchName)
|
||||
if (occupant) {
|
||||
// Branch is currently checked out elsewhere — likely a sibling worktree
|
||||
// that should have been cleaned up. Remove it before reusing the name.
|
||||
try {
|
||||
await exec('git', ['worktree', 'remove', '--force', occupant], { cwd: repoRoot })
|
||||
} catch {
|
||||
// ignore — fall through to deletion below
|
||||
}
|
||||
}
|
||||
try {
|
||||
await exec('git', ['branch', '-D', branchName], { cwd: repoRoot })
|
||||
console.warn(`[createWorktreeForJob] removed orphan branch ${branchName} before recreate`)
|
||||
} catch {
|
||||
// last resort: timestamp-suffix to avoid collision rather than fail
|
||||
branchName = `${branchName}-${Date.now()}`
|
||||
}
|
||||
}
|
||||
|
||||
await exec('git', ['worktree', 'add', '-b', branchName, worktreePath, baseRef], {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue