When verify_task_against_plan returns EMPTY because the requested changes
already live in origin/main (parallel work, earlier PR, race between
siblings), the worker had no clean exit: update_job_status only accepted
running|done|failed. 'failed' triggered the PBI fail-cascade which then
overwrote the error column with 'cancelled_by_self' and cancelled all
sibling tasks of the PBI — see Scrum4Me job cmovkur8 / T-695 for the
reference incident.
This change introduces a fourth status and tightens the cascade:
ST-1273 — 'skipped' exit in update_job_status (T-706 + T-707)
- src/tools/update-job-status.ts: status enum + DB_STATUS_MAP +
resolveNextAction now include 'skipped'. cleanupWorktreeForTerminalStatus
signature widened to ('done'|'failed'|'skipped'); SKIPPED uses keepBranch
semantics identical to FAILED (no push, no branch keep). New input guard:
'skipped' is only valid for TASK_IMPLEMENTATION jobs and requires a
non-empty error (≥10 chars) explaining the reason — it bypasses the
verify-gate, the auto-PR, the SprintRun finalize/fail paths and the
PBI fail-cascade. Locks are still released on terminal exit.
- Tool description spells out when to pick 'skipped' so MCP clients see it.
- New __tests__/update-job-status-skipped.test.ts: resolveNextAction with
'skipped' (wait_for_job_again / queue_empty), and cleanupWorktreeForTerminalStatus
with status='skipped' (keepBranch=false even with a branch reported,
defers cleanup with active siblings).
ST-1274 — cascade ignores SKIPPED + appends trace (T-708 + T-709)
- src/cancel/pbi-cascade.ts: runCascade reads job.status, returns EMPTY
when status === 'SKIPPED' (no sibling cancel). Trace persistence now
reads the current error first and writes `${original}\n---\n${trace}`
(truncated at 1900 chars), so the original failure cause is preserved
for forensics instead of being overwritten.
- New cases in __tests__/cancel-pbi-cascade.test.ts: SKIPPED entry-guard
(no findMany / updateMany / update), original error preserved with
trace appended after '---', trace-only fallback when no original
error, 1900-char truncation keeps the head of the original.
All 282 scrum4me-mcp tests pass; tsc build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- checkSprintVerifyGate: aggregate verify-gate via SprintTaskExecution.
Per row: DONE → checkVerifyGate met snapshot-velden, SKIPPED →
alleen toegestaan bij verify_required=ANY, FAILED/PENDING/RUNNING →
blocker. Toolerror met opsomming bij faal.
- finalizeSprintRunOnDone: idempotent SprintRun → DONE wanneer alle
stories DONE/FAILED zijn.
- maybeCreateSprintBatchPr: één draft-PR per sprint met sprint_goal
als title. Hergebruikt bestaande PR via SprintRunChain bij resume.
- DONE-pad: na update markPullRequestReady wanneer SprintRun DONE.
- FAILED-pad: detect QUOTA_PAUSE: prefix → SprintRun PAUSED met
pause_context (resume-instructions + last-completed-task); anders
→ FAILED met failure_reason + failed_task_id (uit error-string).
- cancelPbiOnFailure overslaan voor SPRINT-jobs (geen task_id).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vier nieuwe tools + propagateStatusUpwards uitbreiding:
T1 — verify_sprint_task (src/tools/verify-sprint-task.ts):
Execution-aware verify met frozen plan_snapshot. Input: execution_id +
worktree_path + optionele summary (voor PARTIAL/DIVERGENT-rationale).
Vult base_sha dynamisch voor task[1..N] op basis van vorige DONE-execution's
head_sha. Schrijft verify_result + verify_summary op execution-row.
Returns { result, reasoning, base_sha, allowed_for_done, reason? } —
allowed_for_done via standaard checkVerifyGate met snapshot-velden.
T2 — update_task_execution (src/tools/update-task-execution.ts):
Lifecycle-tool voor SprintTaskExecution: PENDING/RUNNING/DONE/FAILED/SKIPPED
+ base_sha/head_sha/skip_reason. Idempotent. Token-check via
execution.sprint_job.claimed_by_token_id. started_at/finished_at automatisch.
T3 — job_heartbeat (src/tools/job-heartbeat.ts):
Verlengt ClaudeJob.lease_until met 5 min via atomic conditional UPDATE
(token-check + status-check in WHERE). Voor SPRINT-jobs: response bevat
sprint_run_status + sprint_run_pause_reason zodat worker op UI-side cancel
of MERGE_CONFLICT-pause kan breken zonder extra query.
T4 — update_task_status sprint_run_id-arg + token-coupling
(src/tools/update-task-status.ts):
Optionele sprint_run_id-arg voor expliciete cascade. Validaties: SprintRun
bestaat + actief, task in deze sprint, current token heeft een actieve
ClaudeJob in deze run geclaimd (403 anders). Response uitgebreid met
sprint_run_status_change.
T5 — propagateStatusUpwards sprintRunId-param
(src/lib/tasks-status-update.ts):
Optionele sprintRunId-parameter. Resolve-volgorde: expliciete arg →
ClaudeJob.task_id-lookup → Story → Sprint → SprintRun.findFirst({active}).
De derde fallback dekt SPRINT_IMPLEMENTATION (geen task_id-koppeling) én
handmatige task-statuswijzigingen via UI. cancelExceptJobId voor
sibling-cancel; null voor SPRINT-job betekent geen siblings te cancellen.
src/index.ts: drie nieuwe tools geregistreerd.
Tests: 31 files, 243 passing (geen tests voor nieuwe tools nog — F5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F2-T2 — getFullJobContext branche voor `kind === 'SPRINT_IMPLEMENTATION'`:
- Fetch sprint_run met deep include (sprint → product + stories → pbi + tasks).
- resolveRepoRoot via product; rollbackClaim bij faal.
- Branch-resolutie: previous_run_id + branch → reuse (resume-pad), anders
verse `feat/sprint-<run_id-suffix>`. createWorktreeForJob met juiste
reuseBranch-flag.
- Capture base_sha via `git rev-parse HEAD` na worktree-add.
- Frozen scope-snapshot: SprintTaskExecution.createMany met plan_snapshot,
verify_required_snapshot, verify_only_snapshot per task in scope. Order
is PBI→Story→Task. base_sha alleen op task[0] (rest fillt verify-tool).
- Update job.branch + job.base_sha + sprint_run.branch in één transactie.
- Lookup execution_ids voor response shape.
F2-T3 — resetStaleClaimedJobs lease-driven:
- WHERE-clause uitgebreid naar `status IN ('CLAIMED','RUNNING')` met OR-clause
`lease_until < NOW() OR (lease_until IS NULL AND claimed_at < NOW() - 30min)`.
Legacy jobs zonder lease blijven via claimed_at-pad werken; nieuwe jobs
via lease_until.
- RETURNING uitgebreid met kind, sprint_run_id, branch.
- Bij stale FAILED SPRINT_IMPLEMENTATION: push branch (geen mark-ready,
geen PR-promotie) zodat werk niet verloren gaat. Vul SprintRun.failure_reason
met laatst-RUNNING execution voor diagnose.
Imports: getWorktreeRoot uit worktree-paths.js, pushBranchForJob uit push.js.
Tests: 31 files, 243 passing.
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>
Schema sync vanaf upstream Scrum4Me (v77617e8): FAILED toegevoegd aan
Task/Story/Pbi/SprintStatus, nieuw SprintRunStatus + PrStrategy enums,
SprintRun model, ClaudeJob.sprint_run_id, Product.pr_strategy.
T-18 — propagateStatusUpwards in src/lib/tasks-status-update.ts.
Real-time cascade Task → Story → PBI → Sprint → SprintRun bij elke
task-statuswijziging. Bij FAILED cancelt sibling-jobs in dezelfde
SprintRun. PBI-status BLOCKED blijft handmatig. Houd deze helper bit-
voor-bit synchroon met Scrum4Me/lib/tasks-status-update.ts.
updateTaskStatusWithStoryPromotion blijft als BC-wrapper.
T-19 — wait-for-job.ts claim-filter. Task-jobs worden alleen geclaimd
als hun SprintRun status QUEUED of RUNNING heeft. Idea-jobs blijven
ongefilterd. Bij eerste claim van een QUEUED SprintRun → RUNNING
binnen dezelfde tx (race-safe).
T-20 — update-job-status.ts roept propagateStatusUpwards aan na elke
task DONE/FAILED. Bestaande cancelPbiOnFailure-aanroep blijft voor
PR-cleanup; sibling-cancellation overlap is harmless (idempotent).
T-21 — classify.ts (verifier) leest nu ook "--- a/<path>" zodat
delete-only commits niet meer als EMPTY worden geclassificeerd.
Bug had eerder geleid tot ten onrechte FAILED-status op cmotto5h en
cmotto5i (06-05-2026); zou met cascade-flow een hele sprint laten
falen.
Cleanup: create-todo.ts en open_todos in get-claude-context.ts
verwijderd (Todo-model is op main gedropt). Endpoint geeft nu
open_ideas terug — ideeën die niet PLANNED zijn.
Status-mappers (src/status.ts) uitgebreid met failed.
Tests: 184/184 groen (180 → 184; vier nieuwe delete-only classify-tests
en herwerkte propagate-status tests).
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>
Bron-aanpassing in scrum4me/lib/idea-prompts/make-plan.md (PR
madhura68/Scrum4Me#130) wordt hierin gesynced naar de embedded kopie
die de worker daadwerkelijk leest via getIdeaPromptText.
Inhoudelijke wijziging: nieuwe verplichte stap-3 in de werkwijze ("Bij
removal/refactor: doe een dependency-cascade-grep") + complete sectie
met grep-protocol per type wijziging (Prisma-model, component, type,
hernoemen, veld) + eind-taak `npm run typecheck`.
Achtergrond: tijdens ST-1236 (Todo-applicatielaag verwijderen) miste het
plan de cascade naar 4 consumer-bestanden + de v3-landing. Lint en tests
slaagden, next build brak. De upstream prompt-update voorkomt dit voor
toekomstige IDEA_MAKE_PLAN-jobs — deze sync zorgt dat workers het ook
echt zien.
Verified: tsc + vitest (153/153) groen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verlaagt het schrijfvolume naar claude_workers met factor 2. CLAUDE.md noot
toegevoegd dat de Scrum4Me NavBar-drempel (last_seen_at < now() - 15s)
bij 10s interval krap is — daar kan 25-30s een veiliger marge zijn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
update_job_status accepts optionele model_id + 4 token-velden conform het
runbook-contract (mcp-integration.md:42). De waarden komen niet van de agent
zelf maar van scripts/persist-job-usage.ts, een PostToolUse-hook die het
lokale Claude Code transcript (~/.claude/projects/.../*.jsonl) leest en de
usage tussen de laatste wait_for_job en update_job_status optelt.
Geen Anthropic API-key nodig — alle data staat al lokaal op disk omdat
Claude Code per assistant-message het API usage-blok logt
(input_tokens, output_tokens, cache_creation_input_tokens,
cache_read_input_tokens + message.model).
Robustness:
- Subagent (isSidechain: true) lines worden geskipt om double-counting
te voorkomen tegen subagents/-subdirectory transcripts.
- Lines worden gededupliceerd op uuid (branching/resumption).
- model_id wordt genormaliseerd: claude-opus-4-7[1m] -> claude-opus-4-7-1m
zodat de [1m]-variant op een aparte model_prices-rij kan matchen.
- Hook is non-blocking: elke fout logt een warning en exit 0.
Hook-config in .claude/settings.json met SCRUM4ME_MCP_DIR-fallback zodat
de agent vanuit een product-worktree (andere cwd) ook werkt mits de user
de hook in ~/.claude/settings.json kopieert.
16 nieuwe vitest-cases voor parseTranscript, computeUsageFromTranscript,
normalizeModelId en persistJobUsage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-519 — pre-flight quota-gate voor de worker-loop.
Twee nieuwe MCP-tools:
- get_worker_settings (read): retourneert User.min_quota_pct. Worker
roept dit elke iteratie aan vóór de quota-probe.
- worker_heartbeat (write): worker rapporteert last_quota_pct +
last_quota_check_at na een probe. Update ClaudeWorker en emit
pg_notify 'worker_heartbeat' op scrum4me_changes-channel zodat
NavBar stand-by-badge real-time updatet. requireWriteAccess
(demo-blok).
Schema-resync: vendor/scrum4me bijgewerkt naar 555ed8f waarmee de
M13-velden (User.min_quota_pct, ClaudeWorker.last_quota_pct +
last_quota_check_at) beschikbaar zijn voor Prisma client.
Bestaande achtergrond-heartbeat (presence/heartbeat.ts, 5s tick op
last_seen_at) blijft ongewijzigd. Worker_heartbeat is een aparte
expliciete call met quota-info.
Versie naar 0.7.0 (minor — twee nieuwe tools).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schema heeft Task.repo_url al (override van product.repo_url voor
worktree/branch/push), maar de create_task MCP-tool exposeerde 'm
niet — gevolg: cross-repo tasks (bv. T-519 in scrum4me-mcp onder een
Scrum4Me-PBI) eindigden met repo_url=null en worker draaide ze in
het verkeerde repo.
PBI-34 introduceerde IdeaProduct (idea aan meerdere producten) als
multi-product-pattern. Voor PBI/Story is geen extensie nodig; per-task
override is genoeg om cross-repo werk correct te routeren.
Validatie: zod.string().url() — full https://github.com/owner/repo URL.
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>
T-505 in v0.6.0 wired the idea-failure side-effects but missed the
'skip verify-gate for IDEA_*-kinds on done' branch from the M12 plan.
Reproduced live on IDEA-002: agent answered 5 questions, called
update_idea_grill_md (status → GRILLED, grill_md persisted), but
update_job_status('done') was rejected by the verify-gate because
idea-jobs have no task → no plan_snapshot → verify_task_against_plan
cannot run. Job got marked FAILED + idea reverted to GRILL_FAILED
even though the grill itself succeeded.
Fix: in update_job_status, when status='done' AND kind in
[IDEA_GRILL, IDEA_MAKE_PLAN]: skip checkVerifyGate AND
prepareDoneUpdate (no git push, no branch). The idea-status was
already moved to GRILLED/PLAN_READY by update_idea_*_md; the job
just needs to flip to DONE.
Tests: 153/153 still green.
Bump 0.6.1 → 0.6.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-505 added the kind-discriminator to wait_for_job's response payload but
missed the claim-SQL: tryClaimJob does INNER JOIN tasks ON cj.task_id,
which matches NO rows for IDEA_*-jobs (task_id IS NULL by design — M12
schema). Result: idea-jobs sit forever in QUEUED, never picked up.
Reproduced live: IDEA-002 (cmoshh2ne...) had a IDEA_GRILL job queued at
10:26 that 2 active workers ignored for 14+ minutes.
Fix: LEFT JOIN tasks. plan_snapshot stays empty for idea-jobs (no
verify-flow needed for grill/make-plan).
Bump to 0.6.1 since 0.6.0 production deploy has the broken claim-SQL.
Tests: 153/153 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scrum4Me PR #91 (feat/m12-ideas) merged at 09:58 UTC. Vendor pointer now
tracks origin/main (commit 2893573, includes the canonical M12 schema and
all M12 server/UI/REST/realtime work).
Re-synced prisma/schema.prisma from vendor as the authoritative source
(was previously synced from a local Scrum4Me feature-branch worktree).
Diff vs vendor: only the erd-generator block (vendored has it, mcp does
not — same as before M12).
Tests: 153/153 green; tsc + build clean. No tool-code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the 4 new MCP-tools for the Scrum4Me M12 Idea-entity flow + extends
3 existing tools to handle the new ClaudeJobKind discriminator.
New tools:
- get_idea_context: full idea + product + open questions + recent logs
- update_idea_grill_md: save grill-result + status → GRILLED + IdeaLog
- update_idea_plan_md: server-side yaml parser validates frontmatter;
ok → PLAN_READY, fail → PLAN_FAILED + line-info errors
- log_idea_decision: DECISION/NOTE entries on the timeline
Extended tools:
- ask_user_question: xor schema (story_id | idea_id); idea-questions are
user-private with productId derived from idea.product_id
- wait_for_job: returns \`kind\` discriminator; IDEA_* payloads include
idea + prompt_text (from src/prompts/idea/) and skip worktree creation
- update_job_status: failed on IDEA_* auto-transitions idea-status to
GRILL_FAILED / PLAN_FAILED + IdeaLog{JOB_EVENT}; auto-PR + worktree-
cleanup skipped for idea-jobs
Other changes:
- Health version now read dynamically from package.json (was hardcoded
'0.1.0' which caused deploy-sync confusion)
- Schema synced to Scrum4Me M12 (Idea + IdeaLog + enums + ClaudeJob/
Question nullable-FKs + check-constraints + pg_notify-trigger update)
- New @scrum4me-mcp/lib/idea-plan-parser duplicates Scrum4Me's parser
(drift detected by vendor schema-watchdog)
- Embedded grill+make-plan prompts copied to src/prompts/idea/
- New userOwnsIdea access helper
Tests: 153/153 green; tsc + build clean.
Migration: requires Scrum4Me M12 migration (20260504172747_add_ideas_and_grill_jobs)
applied on the target DB. See vendor/scrum4me/docs/runbooks/mcp-integration.md
for the updated batch-loop with kind-switch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code field became required in schema (feat/entity-codes-required).
All three create tools now generate PBI-N / ST-001 / T-N via the same
SELECT-MAX + retry pattern used in the Scrum4Me app. Also bumps vendor
submodule to v1.0.0 and regenerates prisma/schema.prisma.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Synchronous, non-blocking count of active ClaudeJobs per product or across
all accessible products. Registers check_queue_empty MCP tool with optional
product_id scope, productAccessFilter AuthZ, tests, and README docs.
* feat(ST-mhj9f2la): add set_pbi_pr MCP tool
- Add pr_url and pr_merged_at fields to Pbi model in schema
- Implement set_pbi_pr tool: writes pr_url, clears pr_merged_at (idempotent)
- AuthZ via requireWriteAccess + userCanAccessProduct through pbi.product_id
- 10 tests: happy path, not-found, no-access, demo-denied, schema validation
- Update README tools table and bump version to 0.2.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-mhj9f2la): add mark_pbi_pr_merged MCP tool
- Implement mark_pbi_pr_merged: sets pr_merged_at = now() on a PBI
- Requires pr_url to be set; returns error if not (geen gekoppelde PR)
- Idempotent: re-calling overwrites the timestamp
- AuthZ via requireWriteAccess + userCanAccessProduct through pbi.product_id
- 6 tests: happy path, no-pr_url, idempotent, no-access, not-found, demo-denied
- Update README tools table with mark_pbi_pr_merged entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(ST-mhj9f2la): expand README with set_pbi_pr + mark_pbi_pr_merged docs
Add full signature/input/output/error documentation sections for both
new tools, following the verify_task_against_plan pattern.
Version already bumped to 0.2.0 in earlier commit.
Tag + MCP_GIT_REF pin in scrum4me-docker to be done by maintainer after merge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <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>
Sluit story 'Verify-gate uitbreiden' in PBI 'Agent verify-flow hardening' af.
The previous gate weighed only EMPTY: any PARTIAL or DIVERGENT verify
slipped through. The Insights batch (2 May 2026) showed why that's
weak — agent-jobs claiming DONE while only delivering helpers, not
the requested UI components, with verify=DIVERGENT/PARTIAL accepted.
New decision matrix:
null → reject (run verify_task_against_plan)
EMPTY + !verify_only → reject
EMPTY + verify_only → allowed
ALIGNED → always allowed
PARTIAL/DIVERGENT
required=ALIGNED → reject (strict task)
required=ALIGNED_OR_PARTIAL (default) → allowed only if summary
≥20 chars (acknowledge drift)
required=ANY → allowed (refactor escape hatch)
`update_job_status('done')` now reads `task.verify_required` from the DB
(field added in Scrum4Me PR #53) and passes it + `summary` to the gate.
Tool description updated with the new rules.
Vendor submodule synced to pick up the schema enum.
Tests: 129/129 (was 120 + 9 new combinatorial gate tests).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Worktree-flow section now describes resolveBranchForJob (sibling
reuse), feat/story-<id> naming, deferred cleanup while siblings are
active, and the 1-PR-per-story result.
- File table corrects the heartbeat description (PR #14 made it
self-healing instead of self-terminating).
Closes the docs task in story 'Voorkom doublure-PRs' under PBI 'Veilige
Claude-agent-workflow'.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, if the ClaudeWorker record vanished (deleted by
prisma_workers_cleanup, manual cleanup, or a race during shutdown of a
parallel worker), the heartbeat would log a warning and stop itself
permanently. From that moment the NavBar showed 'Geen agent' for the
rest of the MCP-server process lifetime — even though the agent was
still alive and serving tools.
Fix: on result.count === 0, call registerWorker again so the record is
re-created. Heartbeat keeps ticking. Self-healing instead of self-
terminating.
startHeartbeat now also accepts userId (needed for re-registration);
caller in index.ts updated.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>