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
|
|
@ -74,11 +74,12 @@ describe('createWorktreeForJob', () => {
|
|||
expect(result.worktreePath).toBe(path.join(wtParent, 'job-001'))
|
||||
})
|
||||
|
||||
it('suffixes branch name with timestamp when branch already exists', async () => {
|
||||
it('removes orphan branch and reuses the predictable name when no worktree owns it', async () => {
|
||||
const { repoDir, originDir } = await setupRepo()
|
||||
tmpDirs.push(repoDir, originDir)
|
||||
await makeWorktreeParent()
|
||||
|
||||
// Pre-create an orphan branch (no worktree attached)
|
||||
await git(['branch', 'feat/job-002'], repoDir)
|
||||
|
||||
const result = await createWorktreeForJob({
|
||||
|
|
@ -88,10 +89,11 @@ describe('createWorktreeForJob', () => {
|
|||
baseRef: 'origin/main',
|
||||
})
|
||||
|
||||
expect(result.branchName).toMatch(/^feat\/job-002-\d+$/)
|
||||
// Orphan was deleted → predictable name reused, no timestamp suffix
|
||||
expect(result.branchName).toBe('feat/job-002')
|
||||
|
||||
const { stdout } = await git(['rev-parse', '--abbrev-ref', 'HEAD'], result.worktreePath)
|
||||
expect(stdout.trim()).toBe(result.branchName)
|
||||
expect(stdout.trim()).toBe('feat/job-002')
|
||||
})
|
||||
|
||||
it('rejects when worktree path already exists', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue