fix(update_job_status): gebruik DB-branch ipv legacy feat/job-<8> fallback

Symptoom: TASK_IMPLEMENTATION job T-806 in een SPRINT-strategy sprint
faalde met:

  push failed (unknown): error: src refspec feat/job-us3aqoup does not
  match any
  error: failed to push some refs to 'https://github.com/.../Scrum4Me.git'

Maar de PR was wel succesvol aangemaakt door Claude (PR #174 op
feat/sprint-fvy30lvv) — Claude commit'te in de juiste worktree-branch,
maar update_job_status's prepareDoneUpdate probeerde te pushen op een
niet-bestaande branch.

Root cause: prepareDoneUpdate(jobId, branch) accepteert een branch-arg
(meestal undefined want Claude geeft 'm niet mee) en valt terug op
`feat/job-${jobId.slice(-8)}`. Dat is het legacy pre-PBI-50 pad — voor
sprint-jobs is de werkelijke branch `feat/sprint-<id>` (PR_strategy=SPRINT)
of `feat/story-<id>` (STORY), opgeslagen in ClaudeJob.branch door
attachWorktreeToJob.

Fix:
- prepareDoneUpdate leest nu eerst ClaudeJob.branch uit de DB als de
  expliciete branch-arg ontbreekt.
- Pas daarna fallback op `feat/job-<8>` (zou niet moeten voorkomen na PBI-50).

Tests: vi.mock('../src/prisma.js') toegevoegd voor de findUnique-stub.
Bestaande test "derives branchName from jobId when branch is undefined"
hernoemd naar "reads branchName from DB" met DB-mock returnt
'feat/sprint-fvy30lvv'. Plus extra test voor de legacy fallback wanneer
DB.branch ook null is.

341 tests in 38 files passed (was 340, +1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Madhura68 2026-05-09 13:53:43 +02:00
parent 32929eee93
commit 0a18f565d2
2 changed files with 48 additions and 2 deletions

View file

@ -5,13 +5,26 @@ vi.mock('../src/git/push.js', () => ({
pushBranchForJob: vi.fn(),
}))
vi.mock('../src/prisma.js', () => ({
prisma: {
claudeJob: {
findUnique: vi.fn(),
},
},
}))
import { pushBranchForJob } from '../src/git/push.js'
import { prisma } from '../src/prisma.js'
import { prepareDoneUpdate } from '../src/tools/update-job-status.js'
const mockPush = pushBranchForJob as ReturnType<typeof vi.fn>
const mockFindUnique = (prisma as unknown as {
claudeJob: { findUnique: ReturnType<typeof vi.fn> }
}).claudeJob.findUnique
beforeEach(() => {
vi.clearAllMocks()
mockFindUnique.mockResolvedValue(null)
})
describe('prepareDoneUpdate', () => {
@ -39,8 +52,25 @@ describe('prepareDoneUpdate', () => {
})
})
it('derives branchName from jobId when branch is undefined', async () => {
it('reads branchName from DB (claudeJob.branch) when branch arg is undefined', async () => {
process.env.SCRUM4ME_AGENT_WORKTREE_DIR = '/wt'
mockFindUnique.mockResolvedValue({ branch: 'feat/sprint-fvy30lvv' })
mockPush.mockResolvedValue({ pushed: true, remoteRef: 'refs/heads/feat/sprint-fvy30lvv' })
await prepareDoneUpdate('job-abc12345', undefined)
expect(mockFindUnique).toHaveBeenCalledWith({
where: { id: 'job-abc12345' },
select: { branch: true },
})
expect(mockPush).toHaveBeenCalledWith(
expect.objectContaining({ branchName: 'feat/sprint-fvy30lvv' }),
)
})
it('falls back to feat/job-<8> when neither branch arg nor DB.branch is set', async () => {
process.env.SCRUM4ME_AGENT_WORKTREE_DIR = '/wt'
mockFindUnique.mockResolvedValue({ branch: null })
mockPush.mockResolvedValue({ pushed: true, remoteRef: 'refs/heads/feat/job-abc12345' })
await prepareDoneUpdate('job-abc12345', undefined)