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:
parent
c411fb67f3
commit
36011210a5
4 changed files with 146 additions and 8 deletions
|
|
@ -100,6 +100,9 @@ enum IdeaStatus {
|
|||
PLANNING
|
||||
PLAN_FAILED
|
||||
PLAN_READY
|
||||
REVIEWING_PLAN
|
||||
PLAN_REVIEW_FAILED
|
||||
PLAN_REVIEWED
|
||||
PLANNED
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +110,7 @@ enum ClaudeJobKind {
|
|||
TASK_IMPLEMENTATION
|
||||
IDEA_GRILL
|
||||
IDEA_MAKE_PLAN
|
||||
IDEA_REVIEW_PLAN
|
||||
PLAN_CHAT
|
||||
SPRINT_IMPLEMENTATION
|
||||
}
|
||||
|
|
@ -124,6 +128,7 @@ enum IdeaLogType {
|
|||
NOTE
|
||||
GRILL_RESULT
|
||||
PLAN_RESULT
|
||||
PLAN_REVIEW_RESULT
|
||||
STATUS_CHANGE
|
||||
JOB_EVENT
|
||||
}
|
||||
|
|
@ -520,6 +525,8 @@ model Idea {
|
|||
description String? @db.VarChar(4000)
|
||||
grill_md String? @db.Text
|
||||
plan_md String? @db.Text
|
||||
plan_review_log Json? // ReviewLog from orchestrator
|
||||
reviewed_at DateTime?
|
||||
pbi Pbi? @relation(fields: [pbi_id], references: [id], onDelete: SetNull)
|
||||
pbi_id String? @unique
|
||||
status IdeaStatus @default(DRAFT)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { registerMarkPbiPrMergedTool } from './tools/mark-pbi-pr-merged.js'
|
|||
import { registerGetIdeaContextTool } from './tools/get-idea-context.js'
|
||||
import { registerUpdateIdeaGrillMdTool } from './tools/update-idea-grill-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 { registerGetWorkerSettingsTool } from './tools/get-worker-settings.js'
|
||||
import { registerWorkerHeartbeatTool } from './tools/worker-heartbeat.js'
|
||||
|
|
@ -97,6 +98,7 @@ async function main() {
|
|||
registerGetIdeaContextTool(server)
|
||||
registerUpdateIdeaGrillMdTool(server)
|
||||
registerUpdateIdeaPlanMdTool(server)
|
||||
registerUpdateIdeaPlanReviewedTool(server)
|
||||
registerLogIdeaDecisionTool(server)
|
||||
// M13: worker quota-gate tools
|
||||
registerGetWorkerSettingsTool(server)
|
||||
|
|
|
|||
|
|
@ -101,6 +101,19 @@ const KIND_DEFAULTS: Record<string, JobConfig> = {
|
|||
'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: {
|
||||
model: 'claude-sonnet-4-6',
|
||||
thinking_budget: 6000,
|
||||
|
|
|
|||
116
src/tools/update-idea-plan-reviewed.ts
Normal file
116
src/tools/update-idea-plan-reviewed.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue