Cross-repo sprints (sprint-product = repo X, maar een taak heeft
task.repo_url naar repo Y) faalden op twee plekken omdat sprint-brede
beslissingen werden toegepast op per-repo git-state.
1. createWorktreeForJob (src/git/worktree.ts)
reuseBranch wordt sprint-breed bepaald in wait-for-job.ts. De eerste
job die repo Y target krijgt reuseBranch=true terwijl de branch daar
nooit is aangemaakt -> `git worktree add <path> <branch>` faalt met
"invalid reference" -> job vast, worker UNHEALTHY. Idem na een
container-recreate (clone is dan vers).
Fix: 3-weg fallback in het reuseBranch-pad:
- lokale branch bestaat -> hergebruik
- alleen op origin -> recreate lokaal vanaf origin/<branch>
- nergens -> fresh vanaf baseRef
Lost ook het container-recreate-verlies op.
2. maybeCreateAutoPr (src/tools/update-job-status.ts)
De sprint/story sibling-lookup voor pr_url-hergebruik filterde niet
op repo. Een repo-Y-job erfde de pr_url van een repo-X-sibling ->
job.pr_url wees naar de verkeerde repo en er werd nooit een PR voor
de repo-Y-branch aangemaakt (branch wel gepusht, maar PR-loos).
Fix: siblings groeperen per repo-bucket ((task.repo_url ?? null));
alleen een sibling uit dezelfde bucket levert een herbruikbare
pr_url. Geldt voor SPRINT- en STORY-mode. createPullRequest zelf was
al repo-correct (gh pr create draait in de worktree).
Tests: 3 nieuwe in worktree.test.ts (reuse-local / recreate-from-origin
/ fresh-fallback), 2 nieuwe in update-job-status-auto-pr.test.ts
(cross-repo story + sprint). update-job-status-mock omgezet naar
findMany. Alle 373 tests groen, build groen.
package-lock.json: version 0.7.0 -> 0.8.0 (was niet mee-gesynced in de
v0.8.0-bump commit 55fa133).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three findings from PBI-47 review:
P1 — primary_worktree_path scheiden van lock-volgorde
setupProductWorktrees acquired locks in alphabetical order (deadlock prevention)
but also returned worktrees in that order, so worktrees[0] could point at a
secondary product when its id sorted before the primary's. Lock-acquire stays
sorted; output now preserves caller's input order so worktrees[0] is always
the primary.
P1 — Idea-claim rollback bij worktree setup failure
setupProductWorktrees runs after tryClaimJob has already flipped the job to
CLAIMED. A failure in lock-acquire/git-fetch/reset/sync left the job hanging
until the 30-min stale-reset and the lock-map populated. Wrapped in try/catch
with releaseLocksOnTerminal + rollbackClaim mirror of the task-pad behaviour.
P2 — SPRINT mark-ready fallback when last task didn't push
The mark-ready path used updated.pr_url, which is null when the closing task
was verify-only or had no diff. Now falls back to a Prisma findFirst on the
SprintRun's earliest job with pr_url IS NOT NULL.
Tests: 31 files, 243 passing (incl. new input-order regression for setupProductWorktrees).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wanneer een TASK_IMPLEMENTATION-job FAILED wordt, cancelt
cancelPbiOnFailure alle queued/claimed/running siblings binnen
dezelfde PBI (over alle stories heen) en draait gepushte commits
ongedaan:
- Open PR → gh pr close --delete-branch (PR-close + remote-branch-
delete in één).
- Gemergde PR → revert-PR via git revert -m 1 <mergeSha> in een
korte worktree, gepusht naar revert/<orig>-<jobid>, gh pr create
zonder auto-merge (mens reviewed).
- Branch zonder PR → best-effort git push origin --delete.
Race-protectie: update_job_status weigert nu een statuswijziging op
een job die al CANCELLED is met een specifieke JOB_CANCELLED-error,
zodat een parallelle worker zijn lokale werk weggooit ipv een DONE
te forceren. Idempotent — een tweede cascade voor dezelfde PBI is
een no-op. Non-blocking — alle fouten worden warnings in de trace
op de oorspronkelijke failed job zijn error-veld; cascade throwt
nooit naar de caller.
Niet in scope: per-product opt-out, sprint-niveau cascade,
idea-job cascade.
11 nieuwe vitest-cases dekken DB-cascade, branch-grouping, open/
merged/no-PR paden, repo-root-mismatch en de never-throws-garantie.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Best-effort gh pr merge --auto --squash direct na succesvolle
gh pr create. PR mergt zodra alle vereiste CI-checks groen zijn,
zonder handmatige actie van de gebruiker.
Faal-tolerant: als auto-merge niet werkt (repo heeft "Allow
auto-merge" uit, of token-scope ontbreekt), wordt alleen een
warning gelogd. createPullRequest blijft de PR-URL teruggeven —
auto-merge kan handmatig aangezet worden.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Implementeert vier open stories uit PBI 'Veilige Claude-agent-workflow':
**Branch per story (cmon11tbe001zbortx35n155c)**
- `resolveBranchForJob`: zoek sibling-job in dezelfde story; reuse z'n
branch (1 PR per story i.p.v. per task).
- Branch-naam: `feat/story-<8-char>` voor nieuwe stories.
- `createWorktreeForJob` kent nu `reuseBranch=true`: detecteert stale
sibling-worktree die de branch nog vasthoudt en verwijdert die eerst.
- `attachWorktreeToJob` neemt `storyId` mee.
**PR-hergebruik (zelfde story)**
- `maybeCreateAutoPr`: als sibling-job in story al een pr_url heeft,
hergebruik die zonder nieuwe `gh pr create`-call. PR-titel komt nu
van de story (was task) zodat het als 'story-PR' leest.
**Worktree-cleanup uitgesteld bij actieve siblings**
- `cleanupWorktreeForTerminalStatus`: count active sibling-jobs in
dezelfde story; defer als > 0 (volgende sub-task gebruikt branch).
**Worktree-cleanup logging (cmon0jc14001ubortjxf2a2ck)**
- Warning bij ontbrekende repoRoot, met productId + jobId in message.
- Warning bij removeWorktreeForJob-failure met keepBranch in message.
**resolveRepoRoot fallback (cmon0jc14001ubortjxf2a2ck)**
- Convention-based fallback: `~/Projects/<repo-name>` afgeleid uit
`product.repo_url` als noch env-var noch config-bestand iets oplevert.
- `repoNameFromUrl` helper geëxporteerd voor herbruikbaarheid.
**Verify EMPTY-detection edge-case (cmon0kdq6001xbort2kgbcqmr)**
- `classifyDiffAgainstPlan`: na file-paths-check ook content-lines
checken; als alle +/- regels alleen headers of whitespace zijn,
return EMPTY met duidelijke reasoning.
Tests: 120/120 groen (3 nieuwe), tsc clean, build clean.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New src/git/pr.ts helper wraps 'gh pr create'; returns { url } or { error }.
maybeCreateAutoPr() in update-job-status checks product.auto_pr, builds title
from story.code + task.title, writes pr_url to DB. Non-fatal: gh failure logs
a warning and leaves DONE status intact. Also syncs schema: auto_pr on Product,
pr_url on ClaudeJob.
Runs git push -u origin <branch> in the worktree. Detects no-changes
(HEAD = origin/main) before pushing. Classifies push failures into
no-credentials, conflict, or unknown via stderr pattern matching.
5 unit tests covering all paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes worktree dir via `git worktree remove --force` and deletes
the local branch by default; keepBranch=true preserves the branch.
Returns { removed: false } when the worktree path doesn't exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>