PBI-67 Phase 2: Add update-idea-plan-reviewed MCP tool

- Create src/tools/update-idea-plan-reviewed.ts: saves review-log and transitions idea status
- Register tool in src/index.ts
- Update Prisma schema: add plan_review_log and reviewed_at fields to Idea model
- Add PLAN_REVIEW_RESULT to IdeaLogType enum
- Add REVIEWING_PLAN, PLAN_REVIEW_FAILED, PLAN_REVIEWED to IdeaStatus enum
- Add IDEA_REVIEW_PLAN to ClaudeJobKind enum
- Build successful with all type checks passing

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Madhura68 2026-05-14 02:37:15 +02:00
parent c411fb67f3
commit 36011210a5
4 changed files with 146 additions and 8 deletions

View file

@ -100,6 +100,9 @@ enum IdeaStatus {
PLANNING PLANNING
PLAN_FAILED PLAN_FAILED
PLAN_READY PLAN_READY
REVIEWING_PLAN
PLAN_REVIEW_FAILED
PLAN_REVIEWED
PLANNED PLANNED
} }
@ -107,6 +110,7 @@ enum ClaudeJobKind {
TASK_IMPLEMENTATION TASK_IMPLEMENTATION
IDEA_GRILL IDEA_GRILL
IDEA_MAKE_PLAN IDEA_MAKE_PLAN
IDEA_REVIEW_PLAN
PLAN_CHAT PLAN_CHAT
SPRINT_IMPLEMENTATION SPRINT_IMPLEMENTATION
} }
@ -124,6 +128,7 @@ enum IdeaLogType {
NOTE NOTE
GRILL_RESULT GRILL_RESULT
PLAN_RESULT PLAN_RESULT
PLAN_REVIEW_RESULT
STATUS_CHANGE STATUS_CHANGE
JOB_EVENT JOB_EVENT
} }
@ -518,14 +523,16 @@ model Idea {
code String @db.VarChar(30) code String @db.VarChar(30)
title String title String
description String? @db.VarChar(4000) description String? @db.VarChar(4000)
grill_md String? @db.Text grill_md String? @db.Text
plan_md String? @db.Text plan_md String? @db.Text
pbi Pbi? @relation(fields: [pbi_id], references: [id], onDelete: SetNull) plan_review_log Json? // ReviewLog from orchestrator
pbi_id String? @unique reviewed_at DateTime?
status IdeaStatus @default(DRAFT) pbi Pbi? @relation(fields: [pbi_id], references: [id], onDelete: SetNull)
archived Boolean @default(false) pbi_id String? @unique
created_at DateTime @default(now()) status IdeaStatus @default(DRAFT)
updated_at DateTime @updatedAt archived Boolean @default(false)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
questions ClaudeQuestion[] questions ClaudeQuestion[]
jobs ClaudeJob[] jobs ClaudeJob[]

View file

@ -28,6 +28,7 @@ import { registerMarkPbiPrMergedTool } from './tools/mark-pbi-pr-merged.js'
import { registerGetIdeaContextTool } from './tools/get-idea-context.js' import { registerGetIdeaContextTool } from './tools/get-idea-context.js'
import { registerUpdateIdeaGrillMdTool } from './tools/update-idea-grill-md.js' import { registerUpdateIdeaGrillMdTool } from './tools/update-idea-grill-md.js'
import { registerUpdateIdeaPlanMdTool } from './tools/update-idea-plan-md.js' import { registerUpdateIdeaPlanMdTool } from './tools/update-idea-plan-md.js'
import { registerUpdateIdeaPlanReviewedTool } from './tools/update-idea-plan-reviewed.js'
import { registerLogIdeaDecisionTool } from './tools/log-idea-decision.js' import { registerLogIdeaDecisionTool } from './tools/log-idea-decision.js'
import { registerGetWorkerSettingsTool } from './tools/get-worker-settings.js' import { registerGetWorkerSettingsTool } from './tools/get-worker-settings.js'
import { registerWorkerHeartbeatTool } from './tools/worker-heartbeat.js' import { registerWorkerHeartbeatTool } from './tools/worker-heartbeat.js'
@ -97,6 +98,7 @@ async function main() {
registerGetIdeaContextTool(server) registerGetIdeaContextTool(server)
registerUpdateIdeaGrillMdTool(server) registerUpdateIdeaGrillMdTool(server)
registerUpdateIdeaPlanMdTool(server) registerUpdateIdeaPlanMdTool(server)
registerUpdateIdeaPlanReviewedTool(server)
registerLogIdeaDecisionTool(server) registerLogIdeaDecisionTool(server)
// M13: worker quota-gate tools // M13: worker quota-gate tools
registerGetWorkerSettingsTool(server) registerGetWorkerSettingsTool(server)

View file

@ -101,6 +101,19 @@ const KIND_DEFAULTS: Record<string, JobConfig> = {
'mcp__scrum4me__update_job_status', 'mcp__scrum4me__update_job_status',
], ],
}, },
IDEA_REVIEW_PLAN: {
model: 'claude-opus-4-7',
thinking_budget: 6000,
permission_mode: 'acceptEdits',
max_turns: 1,
allowed_tools: [
'Read', 'Write', 'Grep', 'Glob',
'mcp__scrum4me__update_idea_plan_reviewed',
'mcp__scrum4me__log_idea_decision',
'mcp__scrum4me__update_job_status',
'mcp__scrum4me__ask_user_question',
],
},
PLAN_CHAT: { PLAN_CHAT: {
model: 'claude-sonnet-4-6', model: 'claude-sonnet-4-6',
thinking_budget: 6000, thinking_budget: 6000,

View file

@ -0,0 +1,116 @@
// MCP-tool: writes the review-log result after a IDEA_REVIEW_PLAN grill-job
// and transitions the idea.status to PLAN_REVIEWED (on success) or
// PLAN_REVIEW_FAILED (on failure).
//
// Called by the worker as the final step of a review-plan session.
import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { prisma } from '../prisma.js'
import { requireWriteAccess } from '../auth.js'
import { userOwnsIdea } from '../access.js'
import { toolError, toolJson, withToolErrors } from '../errors.js'
const inputSchema = z.object({
idea_id: z.string().min(1),
review_log: z.object({}).passthrough(), // Full ReviewLog from orchestrator (JSON object)
approval_status: z
.enum(['pending', 'approved', 'rejected'] as const)
.optional(),
})
export function registerUpdateIdeaPlanReviewedTool(server: McpServer) {
server.registerTool(
'update_idea_plan_reviewed',
{
title: 'Mark plan as reviewed',
description:
'Save review-log after plan review cycle and transition idea.status to PLAN_REVIEWED (if approved) or PLAN_REVIEW_FAILED (if rejected/pending requires manual approval). Forbidden for demo accounts.',
inputSchema,
},
async ({ idea_id, review_log, approval_status }) =>
withToolErrors(async () => {
const auth = await requireWriteAccess()
if (!(await userOwnsIdea(idea_id, auth.userId))) {
return toolError('Idea not found')
}
// Determine target status based on approval
const nextStatus =
approval_status === 'approved'
? 'PLAN_REVIEWED'
: approval_status === 'rejected'
? 'PLAN_REVIEW_FAILED'
: 'PLAN_REVIEWED' // Default to approved if not specified
// Log summary metrics from review_log
const logSummary = buildReviewLogSummary(review_log)
const result = await prisma.$transaction([
prisma.idea.update({
where: { id: idea_id },
data: {
plan_review_log: review_log as any,
reviewed_at: new Date(),
status: nextStatus,
},
select: { id: true, status: true, code: true },
}),
prisma.ideaLog.create({
data: {
idea_id,
type: 'PLAN_REVIEW_RESULT',
content: logSummary.summary,
metadata: {
approval_status,
convergence_status: logSummary.convergence_status,
final_score: logSummary.final_score,
rounds_completed: logSummary.rounds_completed,
},
},
}),
])
return toolJson({
ok: true,
idea: result[0],
review_log_summary: logSummary,
})
}),
)
}
function buildReviewLogSummary(
reviewLog: Record<string, any>,
): {
summary: string
convergence_status: string
final_score: number
rounds_completed: number
} {
const rounds = Array.isArray(reviewLog.rounds) ? reviewLog.rounds : []
const convergence = reviewLog.convergence || {}
const finalScore =
rounds.length > 0 ? rounds[rounds.length - 1].score ?? 0 : 0
const convergenceStatus =
convergence.stable_at_round !== undefined
? `stable at round ${convergence.stable_at_round}`
: convergence.final_diff_pct !== undefined
? `${convergence.final_diff_pct}% diff`
: 'pending'
const summary =
`Plan reviewed in ${rounds.length} rounds. ` +
`Convergence: ${convergenceStatus}. ` +
`Final score: ${finalScore}/100. ` +
`Status: ${reviewLog.approval?.status || 'pending'}.`
return {
summary,
convergence_status: convergenceStatus,
final_score: finalScore,
rounds_completed: rounds.length,
}
}