feat: M12 idea-job support — version 0.6.0
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>
This commit is contained in:
parent
79eb13a210
commit
fdf3dc4471
18 changed files with 1140 additions and 146 deletions
|
|
@ -305,17 +305,58 @@ async function getFullJobContext(jobId: string) {
|
|||
},
|
||||
},
|
||||
},
|
||||
product: { select: { id: true, name: true, repo_url: true } },
|
||||
idea: {
|
||||
include: {
|
||||
pbi: { select: { id: true, code: true, title: true } },
|
||||
},
|
||||
},
|
||||
product: { select: { id: true, name: true, repo_url: true, definition_of_done: true } },
|
||||
},
|
||||
})
|
||||
if (!job) return null
|
||||
|
||||
// M12: branch on kind. Idea-jobs hebben geen task/story/pbi/sprint; ze
|
||||
// hebben in plaats daarvan idea + embedded prompt_text.
|
||||
if (job.kind === 'IDEA_GRILL' || job.kind === 'IDEA_MAKE_PLAN') {
|
||||
if (!job.idea) return null
|
||||
const { idea } = job
|
||||
const { getIdeaPromptText } = await import('../lib/idea-prompts.js')
|
||||
return {
|
||||
job_id: job.id,
|
||||
kind: job.kind,
|
||||
status: 'claimed',
|
||||
idea: {
|
||||
id: idea.id,
|
||||
code: idea.code,
|
||||
title: idea.title,
|
||||
description: idea.description,
|
||||
grill_md: idea.grill_md,
|
||||
plan_md: idea.plan_md,
|
||||
status: idea.status,
|
||||
product_id: idea.product_id,
|
||||
},
|
||||
product: {
|
||||
id: job.product.id,
|
||||
name: job.product.name,
|
||||
repo_url: job.product.repo_url,
|
||||
definition_of_done: job.product.definition_of_done,
|
||||
},
|
||||
pbi: idea.pbi,
|
||||
repo_url: job.product.repo_url,
|
||||
prompt_text: getIdeaPromptText(job.kind),
|
||||
branch_suggestion: `feat/idea-${idea.code.toLowerCase()}-${job.kind === 'IDEA_GRILL' ? 'grill' : 'plan'}`,
|
||||
}
|
||||
}
|
||||
|
||||
// TASK_IMPLEMENTATION (default) — bestaande gedrag onaangetast.
|
||||
const { task } = job
|
||||
if (!task) return null
|
||||
const { story } = task
|
||||
const { pbi, sprint } = story
|
||||
|
||||
return {
|
||||
job_id: job.id,
|
||||
kind: job.kind,
|
||||
status: 'claimed',
|
||||
task: {
|
||||
id: task.id,
|
||||
|
|
@ -378,9 +419,23 @@ export function registerWaitForJobTool(server: McpServer) {
|
|||
if (jobId) {
|
||||
const ctx = await getFullJobContext(jobId)
|
||||
if (!ctx) return toolError('Job claimed but context fetch failed')
|
||||
const wt = await attachWorktreeToJob(ctx.product.id, jobId, ctx.story.id, ctx.task.repo_url)
|
||||
if ('error' in wt) return toolError(wt.error)
|
||||
return toolJson({ ...ctx, worktree_path: wt.worktree_path, branch_name: wt.branch_name })
|
||||
// M12: idee-jobs hebben geen worktree nodig — de agent werkt in de
|
||||
// bestaande user-repo (geen branch/commit-flow). Alleen task-jobs
|
||||
// krijgen een worktree.
|
||||
if (ctx.kind === 'TASK_IMPLEMENTATION') {
|
||||
if (!ctx.story || !ctx.task) {
|
||||
return toolError('Task-job claimed but story/task context is incomplete')
|
||||
}
|
||||
const wt = await attachWorktreeToJob(
|
||||
ctx.product.id,
|
||||
jobId,
|
||||
ctx.story.id,
|
||||
ctx.task.repo_url,
|
||||
)
|
||||
if ('error' in wt) return toolError(wt.error)
|
||||
return toolJson({ ...ctx, worktree_path: wt.worktree_path, branch_name: wt.branch_name })
|
||||
}
|
||||
return toolJson(ctx)
|
||||
}
|
||||
|
||||
// 3. No job available — LISTEN and poll until timeout
|
||||
|
|
@ -416,9 +471,20 @@ export function registerWaitForJobTool(server: McpServer) {
|
|||
if (jobId) {
|
||||
const ctx = await getFullJobContext(jobId)
|
||||
if (!ctx) return toolError('Job claimed but context fetch failed')
|
||||
const wt = await attachWorktreeToJob(ctx.product.id, jobId, ctx.story.id, ctx.task.repo_url)
|
||||
if ('error' in wt) return toolError(wt.error)
|
||||
return toolJson({ ...ctx, worktree_path: wt.worktree_path, branch_name: wt.branch_name })
|
||||
if (ctx.kind === 'TASK_IMPLEMENTATION') {
|
||||
if (!ctx.story || !ctx.task) {
|
||||
return toolError('Task-job claimed but story/task context is incomplete')
|
||||
}
|
||||
const wt = await attachWorktreeToJob(
|
||||
ctx.product.id,
|
||||
jobId,
|
||||
ctx.story.id,
|
||||
ctx.task.repo_url,
|
||||
)
|
||||
if ('error' in wt) return toolError(wt.error)
|
||||
return toolJson({ ...ctx, worktree_path: wt.worktree_path, branch_name: wt.branch_name })
|
||||
}
|
||||
return toolJson(ctx)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue