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
|
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[]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
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