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>
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>
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>
server.connect(transport) on the stdio transport awaits the first MCP
handshake from the client. If that handshake stalls (or the await keeps
the process pinned to the stdio event loop), the lines that follow
never run — registerWorker / startHeartbeat / shutdown-handlers are
silently skipped.
Symptom: NavBar shows 'Geen agent' while jobs are claiming and running
(observed in production after the M13 worker-presence release).
ClaudeWorker count stays at 0 even though tools are responding.
Fix: do the presence bootstrap before opening the transport. Tools are
already registered at this point — connecting the transport just makes
them reachable. Delaying the connect by ~10ms (one DB upsert + one
pg_notify) is harmless to the client handshake.
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.
resetStaleClaimedJobs now uses $queryRaw with RETURNING so it can send pg_notify
claude_job_status events per transitioned job. Jobs under the retry limit are
re-queued with retry_count incremented; jobs at ≥2 retries are marked FAILED.
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>
On DONE/FAILED, resolves repoRoot and calls removeWorktreeForJob (best-effort).
keepBranch=true when status=done and agent reported a branch (push assumed);
false otherwise. Cleanup failures are logged as warnings — DB status is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After claiming a job, resolves repoRoot (env SCRUM4ME_REPO_ROOT_<productId>
or ~/.scrum4me-agent-config.json), creates a git worktree, and returns
worktree_path + branch_name in the response. Rolls back claim to QUEUED
on failure. Tool description updated to instruct agent to work in worktree.
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>
Read-only tool that compares frozen plan_snapshot against current
task.implementation_plan + story logs + commits. Returns markdown report
with per-AC ✓/✗/? keyword heuristic, drift-score, and plan diff.
Demo users allowed (readOnlyHint: true).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resetStaleClaimedJobs: also sets plan_snapshot = NULL on reset
- tryClaimJob: JOINs tasks table to read implementation_plan in the
same atomic transaction, writes it to claude_jobs.plan_snapshot
- Empty-plan edge case: NULL becomes '' (non-null) in snapshot
- Exports both functions for unit testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
update_task_status now delegates to updateTaskStatusWithStoryPromotion
and surfaces story_status_change ('promoted' | 'demoted' | null) in the
response so Claude Code can act on story completion without a separate
read call.
get_claude_context adds an OR-filter on tasks so stories where every
task is DONE are skipped — only surfaces stories that still have work to
do (no tasks, or at least one non-DONE task).
Ported transactional story-promotion logic from Scrum4Me app. Promotes
parent story to DONE when all sibling tasks transition to DONE; demotes
back to IN_SPRINT when a task is re-opened on a DONE story. Accepts an
optional tx client to support existing transaction contexts.
- wait_for_job: blocks ≤600s, claims QUEUED job atomically via FOR UPDATE
SKIP LOCKED, resets stale CLAIMED jobs (>30min), registers ClaudeWorker
presence with heartbeat, emits worker_connected/disconnected via NOTIFY
- update_job_status: agent reports running|done|failed, validates token
ownership (claimed_by_token_id), emits claude_job_status via NOTIFY
- auth.ts extended with tokenId so tools can set claimed_by_token_id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tot nu toe konden MCP-tools alleen bestaande stories/tasks bewerken
(update_task_status, log_*). Met deze drie tools kan Claude Code een
volledige backlog vanaf nul opbouwen — handig voor nieuwe projecten waar
het Scrum4Me-product nog leeg is.
- create_pbi: { product_id, title, description?, priority, sort_order? }
Auto sort_order = last+1 binnen prio-groep. Code-veld blijft null
(Scrum4Me-app genereert auto-codes via UI/seed).
- create_story: { pbi_id, title, description?, acceptance_criteria?, priority,
sort_order? } — product_id afgeleid uit PBI (denormalized FK conform CLAUDE.md
convention; nooit op client-input vertrouwen). Status='OPEN' default →
landt in product-backlog, niet auto in een sprint.
- create_task: { story_id, title, description?, implementation_plan?, priority,
sort_order? } — sprint_id geërfd van story. Status='TO_DO' default.
Alle drie achter `requireWriteAccess` (PERMISSION_DENIED voor demo) +
`userCanAccessProduct` op de relevante parent-product. Mirror van het
create-todo-patroon.
scripts/smoke-test.ts: tool-count check 13 → 16. README.md: tool-tabel
uitgebreid.
Quality gates: typecheck clean, build success, smoke-test toont 16 tools.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vier nieuwe tools voor het Claude vraag-antwoord-kanaal:
- ask_user_question (write): post een gestructureerde vraag aan de actieve
Scrum4Me-gebruiker over een story; default async (returnt direct met
question_id + status='open'); optionele wait_seconds (max 600) polt elke 2s
tot het antwoord er is of timeout — daarna status='pending' zodat Claude met
get_question_answer later kan ophalen
- get_question_answer (read): huidige status + antwoord van een eerder
gestelde vraag
- list_open_questions (read): eigen vragen met status open/answered, max 50,
meest recente eerst
- cancel_question (write, asker-only): atomic UPDATE WHERE asked_by + status=
'open' zodat alleen eigen open vragen geannuleerd worden
Allemaal achter access-check via userCanAccessStory/Product en demo-blok via
requireWriteAccess (volgt patroon van create-todo en bestaande log-tools).
Submodule vendor/scrum4me bumpt naar Scrum4Me commit 79367dd (M11 ST-1101) —
bevat het ClaudeQuestion-model en notify_question_change-trigger waar deze
tools tegen werken.
scripts/smoke-test.ts: 13 tools verwacht (was 9); list_open_questions
toegevoegd als read-tool-coverage. Build + tools/list groen — verdere e2e via
MCP Inspector na PR-merge omdat de seed een nieuwe API-token heeft
gegenereerd en .env een nieuwe waarde nodig heeft.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #2 merged, so the StoryLog.metadata JSONB column is live. Sync
the vendored schema and wire `metadata` through to prisma.create in
log_implementation, log_test_result and log_commit. Cast via
Prisma.InputJsonValue because Zod parses the input as a generic
record while Prisma's JSON input type is invariant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The MCP SDK rejects tools/call results where structuredContent is not a
record — array returns from list_products triggered an MCP error code
-32602. toolJson now wraps arrays/primitives as { result: <value> }.
scripts/smoke-test.ts spawns the built server over stdio, calls each
read-side tool against the live DB and asserts shape — surfaces this
bug class before regressions ship.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-to-end workflow prompt for Claude Code: fetch context, log a plan,
walk the tasks (in_progress → done), run tests, log result, commit.
Takes product_id as the only argument.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a todo for the authenticated user with optional description (max
2000) and optional product scope. Verifies product access if a
product_id is given. Demo accounts get PERMISSION_DENIED.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three write tools that append StoryLog entries:
- log_implementation: type=IMPLEMENTATION_PLAN
- log_test_result: type=TEST_RESULT, status PASSED|FAILED
- log_commit: type=COMMIT with hash and message
All accept optional metadata in input but skip writing it for now —
the StoryLog.metadata JSONB column lands with Scrum4Me PR #2.
After that PR merges, run sync-schema and add `metadata` to each
prisma.create's data field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/access.ts: shared product/story/task access checks via product
ownership or membership
- update_task_status accepts lowercase API values, converts to DB
enum, rejects unknown values
- update_task_plan replaces implementation_plan on a task
- Both call requireWriteAccess() so demo accounts get
PERMISSION_DENIED before any DB write
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- health: pings DB via SELECT 1 and returns status/version/time
- list_products: active products owned or shared with the auth user
- get_claude_context: bundled product + active sprint + next story
(with tasks, status mapped to lowercase) + 50 open todos
prisma.ts switches to a lazy proxy so the server bootstrap doesn't
crash before tools fire when DATABASE_URL is unset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/status.ts: bidirectional Task/Story status mappers — DB stays
UPPER_SNAKE, MCP tools expose lowercase (matches REST API contract)
- src/errors.ts: formatZodError, toolError, toolJson and the
withToolErrors() wrapper so each tool turns thrown exceptions
(PermissionDenied, ZodError, generic) into structured MCP errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/prisma.ts: PrismaClient via PrismaPg adapter and pg.Pool,
same pattern as Scrum4Me's lib/prisma.ts
- src/auth.ts: getAuth() resolves SCRUM4ME_TOKEN once, caches
{ userId, username, isDemo }. requireWriteAccess() throws
PermissionDeniedError for demo tokens — write tools call this
before any DB mutation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Initial repo with TypeScript strict, MCP SDK 1.29, Prisma 7,
zod and tsx. Stdio-transport bootstrap in src/index.ts boots
without crashing. Tools and prompts to be added in ST-705..ST-709.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>