feat(solo): A/B/C plan gate + plan-critic specialist (#34)

* feat(solo): add council-style plan gate

Introduce a prompt-level A/B/C planning gate so Solo mode requires Planner, Reviewer, and Critic perspectives before implementation, while preserving the existing staged workflow and human approval gate.

Tested: bun test src/coordinator/soloPipelinePrompt.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts -t "Solo Pipeline system prompt"
Tested: bun test src/server/__tests__/conversations.test.ts -t "routes coordinator and Solo"
Tested: cd desktop && bun run lint
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>

* feat(agents): add plan critic specialist

Add a read-only plan-critic specialist with parseable PLAN_REVIEW verdicts, register it in built-in and coordinator agent registries, and teach Solo's plan gate to use Plan plus plan-critic when available before synthesis.

Tested: bun test src/tools/AgentTool/builtInAgents.test.ts src/coordinator/workerAgent.test.ts src/coordinator/soloPipelinePrompt.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts -t "Solo Pipeline system prompt"
Tested: bun test src/server/__tests__/conversations.test.ts -t "routes coordinator and Solo"
Not-tested: Full bun test remains blocked by unrelated existing failures in attribution header, shell PATH/env, release workflow, and desktop Vitest compatibility tests.
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>

* chore(ci): retrigger PR checks after applying policy labels

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>

---------

Co-authored-by: 你的姓名 <you@example.com>
Co-authored-by: Claude GPT-5.5 <noreply@anthropic.com>
This commit is contained in:
小橙子 2026-06-12 21:55:22 +08:00 committed by GitHub
parent b5e62e3251
commit 35fac60158
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 191 additions and 31 deletions

View File

@ -1276,7 +1276,7 @@ export const en = {
'session.coordinatorChip': 'Orchestration',
'session.coordinatorChipTooltip': 'Orchestration mode is on for this session. Tasks fan out to specialist worker agents and the main thread coordinates them. Toggle it from the + menu in the composer.',
'session.soloPipelineChip': 'Solo pipeline',
'session.soloPipelineChipTooltip': 'Solo Pipeline mode is on for this session. The AI runs a 5-stage workflow (planner → builder → tester → reviewer → integrator) on its own. Toggle it from the + menu in the composer.',
'session.soloPipelineChipTooltip': 'Solo Pipeline mode is on for this session. Before implementation, the AI runs an A/B/C plan gate (Planner → Reviewer → Critic), then continues through implement → test → review → land. Toggle it from the + menu in the composer.',
'empty.recentActivity.handoff.branchLine': 'Last session on {branch}: "{title}".',
'empty.recentActivity.handoff.titleLine': 'Last session: "{title}".',
'empty.recentActivity.handoff.filesLine': 'Files touched: {files}{more}.',
@ -1297,7 +1297,7 @@ export const en = {
'solo.suggest.finishWip.title': 'Finish {count} pending change(s)',
'solo.suggest.finishWip.detail': 'Uncommitted: {sample}',
'solo.suggest.finishWip.detailForeign': 'Uncommitted (some may be from another agent): {sample}',
'solo.suggest.finishWip.taskPrompt': 'Wrap up the {count} uncommitted change(s) in this worktree ({files}). Walk them through plan → implement → test → review → land.',
'solo.suggest.finishWip.taskPrompt': 'Wrap up the {count} uncommitted change(s) in this worktree ({files}). Run the A/B/C plan gate first, then implement → test → review → land.',
'solo.suggest.shipAhead.title': 'Ship {branch} ({count} commit(s) ahead)',
'solo.suggest.shipAhead.detail': 'Local is {count} commit(s) ahead of upstream. The work is done — review and land.',

View File

@ -1269,7 +1269,7 @@ export const jp: Record<TranslationKey, string> = {
'session.coordinatorChip': 'オーケストレーション',
'session.coordinatorChipTooltip': 'このセッションはオーケストレーションモードです。タスクは専門 worker エージェントへ分散し、メインスレッドが調整します。コンポーザーの + メニューから切り替えできます。',
'session.soloPipelineChip': 'ソロパイプライン',
'session.soloPipelineChipTooltip': 'このセッションはソロパイプラインモードです。AI は 5 段階 (プランナー → 実装 → テスト → レビュー → 統合) を単独で実行します。コンポーザーの + メニューから切り替えできます。',
'session.soloPipelineChipTooltip': 'このセッションはソロパイプラインモードです。実装前に AI は A/B/C 計画ゲートPlanner → Reviewer → Criticを実行し、その後 実装 → テスト → レビュー → リリース に進みます。コンポーザーの + メニューから切り替えできます。',
'empty.recentActivity.handoff.branchLine': '前回のセッション ({branch}): 「{title}」。',
'empty.recentActivity.handoff.titleLine': '前回のセッション:「{title}」。',
'empty.recentActivity.handoff.filesLine': '触ったファイル: {files}{more}。',
@ -1283,7 +1283,7 @@ export const jp: Record<TranslationKey, string> = {
'solo.suggest.finishWip.title': '{count} 件の未コミット変更を仕上げる',
'solo.suggest.finishWip.detail': '未コミット: {sample}',
'solo.suggest.finishWip.detailForeign': '未コミット(他のエージェントの作業を含む可能性): {sample}',
'solo.suggest.finishWip.taskPrompt': '現在の worktree にある {count} 件の未コミット変更({files})を仕上げてください。plan → implement → test → review → land を一通り通します。',
'solo.suggest.finishWip.taskPrompt': '現在の worktree にある {count} 件の未コミット変更({files})を仕上げてください。まず A/B/C 計画ゲートを通し、その後 実装 → テスト → レビュー → リリース に進みます。',
'solo.suggest.shipAhead.title': '{branch} をリリース({count} 件先行)',
'solo.suggest.shipAhead.detail': 'ローカルが上流より {count} 件先行しており、作業はすでに完了しています — レビュー + リリース。',

View File

@ -1269,7 +1269,7 @@ export const kr: Record<TranslationKey, string> = {
'session.coordinatorChip': '오케스트레이션',
'session.coordinatorChipTooltip': '이 세션은 오케스트레이션 모드입니다. 작업은 전문 worker 에이전트로 분산되고 메인 스레드가 조정합니다. 컴포저의 + 메뉴에서 전환할 수 있습니다.',
'session.soloPipelineChip': '솔로 파이프라인',
'session.soloPipelineChipTooltip': '이 세션은 솔로 파이프라인 모드입니다. AI가 5 단계 (계획 → 구현 → 테스트 → 리뷰 → 통합)를 단독으로 실행합니다. 컴포저의 + 메뉴에서 전환할 수 있습니다.',
'session.soloPipelineChipTooltip': '이 세션은 솔로 파이프라인 모드입니다. 구현 전에 AI가 A/B/C 계획 게이트(Planner → Reviewer → Critic)를 먼저 실행한 뒤 구현 → 테스트 → 리뷰 → 통합으로 진행합니다. 컴포저의 + 메뉴에서 전환할 수 있습니다.',
'empty.recentActivity.handoff.branchLine': '지난 세션({branch}): "{title}".',
'empty.recentActivity.handoff.titleLine': '지난 세션: "{title}".',
'empty.recentActivity.handoff.filesLine': '수정한 파일: {files}{more}.',
@ -1283,7 +1283,7 @@ export const kr: Record<TranslationKey, string> = {
'solo.suggest.finishWip.title': '{count}개의 미커밋 변경 마무리',
'solo.suggest.finishWip.detail': '미커밋: {sample}',
'solo.suggest.finishWip.detailForeign': '미커밋 (다른 에이전트의 작업 포함 가능): {sample}',
'solo.suggest.finishWip.taskPrompt': '현재 worktree의 {count}개 미커밋 변경({files})을 마무리해 주세요. plan → implement → test → review → land 한 사이클을 진행합니다.',
'solo.suggest.finishWip.taskPrompt': '현재 worktree의 {count}개 미커밋 변경({files})을 마무리해 주세요. 먼저 A/B/C 계획 게이트를 거친 뒤 구현 → 테스트 → 리뷰 → 통합을 진행합니다.',
'solo.suggest.shipAhead.title': '{branch} 출시 ({count}개 커밋 앞섬)',
'solo.suggest.shipAhead.detail': '로컬이 업스트림보다 {count}개 앞서 있고 작업은 이미 완료됨 — 리뷰 + 출시.',

View File

@ -1269,7 +1269,7 @@ export const zh: Record<TranslationKey, string> = {
'session.coordinatorChip': '編排模式',
'session.coordinatorChipTooltip': '該會話已開啟編排模式。任務會扇出到專家 worker 代理執行,主執行緒負責協調。在輸入框 + 選單裡切換。',
'session.soloPipelineChip': '獨立流水線',
'session.soloPipelineChipTooltip': '該會話已開啟獨立流水線模式。AI 會按 5 個階段(規劃 → 實作 → 測試 → 審查 → 落地)獨立執行。在輸入框 + 選單裡切換。',
'session.soloPipelineChipTooltip': '該會話已開啟獨立流水線模式。實作前 AI 會先走 A/B/C 計畫閘門Planner → Reviewer → Critic再繼續實作 → 測試 → 審查 → 落地。在輸入框 + 選單裡切換。',
'empty.recentActivity.handoff.branchLine': '上次在 {branch} 上做了:「{title}」。',
'empty.recentActivity.handoff.titleLine': '上次會話:「{title}」。',
'empty.recentActivity.handoff.filesLine': '動了這些檔案:{files}{more}。',
@ -1283,7 +1283,7 @@ export const zh: Record<TranslationKey, string> = {
'solo.suggest.finishWip.title': '收尾 {count} 個未提交改動',
'solo.suggest.finishWip.detail': '未提交:{sample}',
'solo.suggest.finishWip.detailForeign': '未提交(可能含其他 agent 的改動):{sample}',
'solo.suggest.finishWip.taskPrompt': '把當前 worktree 裡 {count} 個未提交改動({files})收尾。走完一輪 plan → implement → test → review → land。',
'solo.suggest.finishWip.taskPrompt': '把當前 worktree 裡 {count} 個未提交改動({files})收尾。先走 A/B/C 計畫閘門,再實作 → 測試 → 審查 → 落地。',
'solo.suggest.shipAhead.title': '把 {branch} 落地({count} 個領先提交)',
'solo.suggest.shipAhead.detail': '本地領先上游 {count} 個提交,工作已經做完——評審 + 落地。',

View File

@ -1269,7 +1269,7 @@ export const zh: Record<TranslationKey, string> = {
'session.coordinatorChip': '编排模式',
'session.coordinatorChipTooltip': '该会话已开启编排模式。任务会扇出到专家 worker 代理执行,主线程负责协调。在输入框 + 菜单里切换。',
'session.soloPipelineChip': '独立流水线',
'session.soloPipelineChipTooltip': '该会话已开启独立流水线模式。AI 会按 5 个阶段(规划 → 实现 → 测试 → 审查 → 落地)独立运行。在输入框 + 菜单里切换。',
'session.soloPipelineChipTooltip': '该会话已开启独立流水线模式。实现前 AI 会先走 A/B/C 计划闸门Planner → Reviewer → Critic再继续实现 → 测试 → 审查 → 落地。在输入框 + 菜单里切换。',
'empty.recentActivity.handoff.branchLine': '上次在 {branch} 上做了:"{title}"。',
'empty.recentActivity.handoff.titleLine': '上次会话:"{title}"。',
'empty.recentActivity.handoff.filesLine': '动了这些文件:{files}{more}。',
@ -1283,7 +1283,7 @@ export const zh: Record<TranslationKey, string> = {
'solo.suggest.finishWip.title': '收尾 {count} 个未提交改动',
'solo.suggest.finishWip.detail': '未提交:{sample}',
'solo.suggest.finishWip.detailForeign': '未提交(可能含其他 agent 的改动):{sample}',
'solo.suggest.finishWip.taskPrompt': '把当前 worktree 里 {count} 个未提交改动({files})收尾。走完一轮 plan → implement → test → review → land。',
'solo.suggest.finishWip.taskPrompt': '把当前 worktree 里 {count} 个未提交改动({files})收尾。先走 A/B/C 计划闸门,再实现 → 测试 → 审查 → 落地。',
'solo.suggest.shipAhead.title': '把 {branch} 落地({count} 个领先提交)',
'solo.suggest.shipAhead.detail': '本地领先上游 {count} 个提交,工作已经做完——评审 + 落地。',

View File

@ -213,6 +213,7 @@ When calling ${AGENT_TOOL_NAME}, always pass \`subagent_type\`. Two flavours:
| \`commit-pr\` | Draft commit messages or PR descriptions from a real diff. |
| \`Explore\` | Fast read-only codebase search. Specify thoroughness ("quick" / "medium" / "very thorough"). |
| \`Plan\` | Read-only architect that returns an implementation plan and the critical files for it. |
| \`plan-critic\` | Read-only challenge pass for an implementation plan before code is written. Ends in \`PLAN_REVIEW: APPROVE\` / \`CHANGES_NEEDED\`. |
| \`verification\` | Independent adversarial verification of completed work. Use after non-trivial implementation lands. |
${workerCapabilities}

View File

@ -72,14 +72,27 @@ describe('getSoloPipelineSystemPrompt — invariants', () => {
expect(prompt).toContain('Never run the pipeline for chat')
})
it('declares all five stages with the planned specialists', () => {
expect(prompt).toMatch(/1\.\s+\*\*PLAN\*\*/)
it('declares all five Solo pipeline stages', () => {
expect(prompt).toMatch(/1\.\s+\*\*PLAN GATE/)
expect(prompt).toMatch(/2\.\s+\*\*IMPLEMENT\*\*/)
expect(prompt).toMatch(/3\.\s+\*\*TEST\*\*/)
expect(prompt).toMatch(/4\.\s+\*\*REVIEW\*\*/)
expect(prompt).toMatch(/5\.\s+\*\*LAND\*\*/)
})
it('requires the prompt-level A/B/C Plan Gate before implementation', () => {
expect(prompt).toContain('A/B/C Plan Gate')
expect(prompt).toContain('Solo Council')
expect(prompt).toContain('A = Planner')
expect(prompt).toContain('B = Reviewer')
expect(prompt).toContain('C = Critic')
expect(prompt).toContain('final execution plan')
expect(prompt).toContain('use the `Plan` specialist when available')
expect(prompt).toContain('use the `plan-critic` specialist when available')
expect(prompt).toContain('otherwise run the same critique as a prompt-level role')
expect(prompt).toContain('Do not enter Stage 2')
})
it('keeps the HUMAN GATE in Stage 4 (the safety contract)', () => {
// Auto-merging review without explicit user approval would
// defeat the whole point of Solo. Anyone who edits the prompt

View File

@ -4,8 +4,9 @@
* Sibling to `coordinatorMode.ts`. While coordinator mode does
* dynamic fan-out (the lead AI decides what to delegate, when, and
* to whom), Solo Pipeline mode runs a fixed five-stage script with
* gates between stages: plan implement test review (HUMAN
* APPROVAL) land.
* gates between stages: plan gate (A/B/C council) implement test
* review (HUMAN APPROVAL) land. Stage 1 is a prompt-level
* council pass, not a separate worker protocol.
*
* This module owns ONLY the prompt text + the boolean predicate
* (`isSoloPipelineMode`). Wiring (CLI flag plumbing,
@ -86,10 +87,13 @@ const SOLO_PIPELINE_PROMPT = `# Solo Pipeline Mode
You are operating in **Solo Pipeline mode**: a staged, gated workflow for
turning one feature/change request into shipped, verified work. You drive
five stages in order, delegating each to a specialist subagent, and you
pause at human gates. You are the conductor you do NOT write the code,
run the tests, or approve the work yourself; you route to the right
specialist and enforce the gate between stages.
five stages in order. Before any implementation, you must run an internal
**A/B/C Plan Gate Solo Council**: A = Planner proposes, B = Reviewer
audits, C = Critic challenges, then you synthesize the final execution
plan. A/B/C are prompt-level roles, not separate workers or a
worker-to-worker protocol unless the runtime explicitly provides one. You
are the conductor you do NOT write the code, run the tests, or approve
the work yourself; you enforce the gate between stages.
## STAGE 0 Intent triage (ALWAYS run this first, silently)
@ -117,26 +121,42 @@ only justified for real build work.
Open with a one-line, scannable plan, e.g.:
"Solo pipeline: plan implement test review (your
approval) land. Starting Stage 1: planning."
"Solo pipeline: plan gate (A/B/C council) implement test
review (your approval) land. Starting Stage 1: A/B/C planning."
Keep the user oriented: at each stage transition, print a single status
line "▸ Stage N/5: <name> — <one-line goal>".
## The five stages
1. **PLAN** (specialist: planner / context-gatherer)
- Inspect the real code first. Produce a short written plan: the
changed surface, the approach, the files to touch, the success
criteria, and the verification commands.
- Hand-off artifact: \`plan\` (the written plan).
- GATE automatic. Advance when the plan names concrete files +
a verification command. If it can't, loop back with the user.
1. **PLAN GATE Solo Council** (A/B/C; no implementation yet)
- Inspect the real code first. Read enough to ground the plan in actual
files, commands, constraints, and existing patterns.
- **A = Planner**: use the \`Plan\` specialist when available. Produce the
concrete implementation plan changed surface, candidate files,
approach, acceptance criteria, verification commands, and known risks.
- **B = Reviewer**: audit A's plan for missing files, integration points,
test gaps, safety issues, persistence/security boundaries, and repository
conventions.
- **C = Critic**: use the \`plan-critic\` specialist when available;
otherwise run the same critique as a prompt-level role. Challenge
assumptions, look for a smaller / safer / more verifiable alternative,
identify overreach, and name what would make the plan fail. The Critic
must not merely agree.
- **Synthesis**: merge A/B/C into one **final execution plan**. Treat that
final execution plan as the source of truth for Stage 2.
- Hand-off artifact: \`final execution plan\` with concrete files,
implementation steps, success criteria, verification commands, and
residual risks.
- GATE automatic but strict. Do not enter Stage 2 or modify files until
the final execution plan is complete. If B reports a blocking issue or C
reports a blocking objection, revise the plan or ask the user one short
clarifying question before implementation.
2. **IMPLEMENT** (specialist: implementer)
- Receives the plan verbatim. Makes the narrowest change that
satisfies it. Touches only files the plan named (or explains any
deviation).
- Receives the final execution plan verbatim. Makes the narrowest change
that satisfies it. Touches only files the final execution plan named (or
explains any deviation before proceeding).
- Hand-off artifact: \`diff\` (files changed + summary).
3. **TEST** (specialist: verification)
@ -179,10 +199,15 @@ suggestion that prefilled the prompt), respect the entry stage:
## Rules
- The A/B/C Plan Gate is mandatory for TASK messages that enter at
\`entryStage = 'plan'\` or have no entry-stage shortcut. Planning may read
code, but it must not implement or modify files.
- One stage at a time. Never skip the human gate at Stage 4 unless the
entry-stage shortcut placed you past it.
- If a stage's specialist reports it can't proceed, surface it to the
user rather than forcing the next stage.
- The Critic must look for a smaller or safer alternative; the final
execution plan is the source of truth for implementation.
- If the user interrupts mid-pipeline with a new message, re-run Stage 0
intent triage on it: a chat aside shouldn't derail the pipeline, a new
task should ask whether to queue or restart.

View File

@ -23,6 +23,7 @@ describe('coordinator agent registry', () => {
'commit-pr',
'Explore',
'Plan',
'plan-critic',
'verification',
]
for (const expected of required) {

View File

@ -32,6 +32,7 @@ import { EXPLORE_AGENT } from '../tools/AgentTool/built-in/exploreAgent.js'
import { MIGRATION_AGENT } from '../tools/AgentTool/built-in/migrationAgent.js'
import { PERFORMANCE_AGENT } from '../tools/AgentTool/built-in/performanceAgent.js'
import { PLAN_AGENT } from '../tools/AgentTool/built-in/planAgent.js'
import { PLAN_CRITIC_AGENT } from '../tools/AgentTool/built-in/planCriticAgent.js'
import { REFACTOR_AGENT } from '../tools/AgentTool/built-in/refactorAgent.js'
import { SECURITY_REVIEWER_AGENT } from '../tools/AgentTool/built-in/securityReviewerAgent.js'
import { TEST_AUTHOR_AGENT } from '../tools/AgentTool/built-in/testAuthorAgent.js'
@ -95,6 +96,7 @@ export function getCoordinatorAgents(): AgentDefinition[] {
// research / verify
EXPLORE_AGENT,
PLAN_AGENT,
PLAN_CRITIC_AGENT,
VERIFICATION_AGENT,
]
}

View File

@ -265,6 +265,9 @@ describe('ConversationService', () => {
// The injected text must be the Solo prompt — sniff for the unique
// Stage-0 intent-triage phrasing locked in soloPipelinePrompt.test.ts.
expect(args[idx + 1]).toContain('Solo Pipeline mode')
expect(args[idx + 1]).toContain('A/B/C Plan Gate')
expect(args[idx + 1]).toContain('Critic')
expect(args[idx + 1]).toContain('final execution plan')
})
it('should not append the Solo prompt when soloPipelineMode is off', () => {
@ -287,7 +290,7 @@ describe('ConversationService', () => {
expect(flagIndices.length).toBe(2)
const texts = flagIndices.map((i) => args[i + 1] ?? '')
expect(texts.some((t) => t.includes(ORCHESTRATION_PROMPT_MARKER))).toBe(true)
expect(texts.some((t) => t.includes('Solo Pipeline mode'))).toBe(true)
expect(texts.some((t) => t.includes('A/B/C Plan Gate'))).toBe(true)
})
// Locks the orchestrator's "propagate project tool rules into every dispatched

View File

@ -0,0 +1,92 @@
import { BASH_TOOL_NAME } from 'src/tools/BashTool/toolName.js'
import { EXIT_PLAN_MODE_TOOL_NAME } from 'src/tools/ExitPlanModeTool/constants.js'
import { FILE_EDIT_TOOL_NAME } from 'src/tools/FileEditTool/constants.js'
import { FILE_WRITE_TOOL_NAME } from 'src/tools/FileWriteTool/prompt.js'
import { NOTEBOOK_EDIT_TOOL_NAME } from 'src/tools/NotebookEditTool/constants.js'
import { AGENT_TOOL_NAME } from '../constants.js'
import type { BuiltInAgentDefinition } from '../loadAgentsDir.js'
const PLAN_CRITIC_SYSTEM_PROMPT = `You are a plan critic specialist for Claude Code. Your job is to challenge an implementation plan before any code is written. You are not a code reviewer: code reviewers inspect diffs after implementation; you inspect the proposed plan for feasibility, scope, risk, and whether a smaller or safer path exists.
=== CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
This is a READ-ONLY plan critique task. You are STRICTLY PROHIBITED from:
- Creating, modifying, or deleting any files (no Write, Edit, touch, rm, mv, cp)
- Using redirect operators (>, >>) or heredocs to write files
- Running git write operations (add, commit, push) or installing dependencies
- Running ANY command that changes system state
You do NOT have file-editing tools. Use ${BASH_TOOL_NAME} only for read-only inspection (git diff, git log, git status, ls, cat, head, tail, find, grep). Your output is a critique report, not edits the parent agent revises or applies the plan.
=== WHAT YOU RECEIVE ===
You receive the user's requested outcome, a proposed implementation plan, and optionally relevant files or constraints. If important context is missing, inspect the codebase read-only to verify whether the plan is grounded in real files and existing patterns.
=== CRITIQUE DIMENSIONS ===
Challenge the plan in this order:
**1. Feasibility**
- Does the plan name the right files, APIs, data flow, and integration points?
- Does it depend on behavior that does not exist or contradict current code?
- Are sequencing and dependencies realistic?
**2. Minimality and alternatives**
- Is there a smaller change that satisfies the same user outcome?
- Is the plan over-engineered, speculative, or adding abstractions before they are needed?
- Can a safer staged rollout or narrower scope reduce risk?
**3. Risk and contracts**
- Does it touch auth, permissions, persistence, networking, shell execution, external providers, or public APIs?
- Does it require migration, compatibility, or user-visible copy updates?
- Are there hidden cross-process or cross-package effects?
**4. Verification gaps**
- Are success criteria concrete and testable?
- Are the proposed checks the narrowest meaningful checks?
- Is there an E2E/manual smoke path when unit tests cannot prove the behavior?
=== OUTPUT FORMAT (REQUIRED) ===
Return a concise report with these sections:
1. Blocking objections
- List only issues that should prevent implementation until the plan changes.
- Use "None" if there are no blocking objections.
2. Non-blocking concerns
- List risks or improvements that can be handled during implementation.
- Use "None" if there are no non-blocking concerns.
3. Smaller / safer alternative
- Name the best smaller or safer path if one exists.
- If the proposed plan is already minimal, say so explicitly.
4. Verification notes
- State which checks or smoke paths should prove the final implementation.
End with exactly one summary line the caller can parse:
PLAN_REVIEW: APPROVE
or
PLAN_REVIEW: CHANGES_NEEDED
Use CHANGES_NEEDED if any blocking objection remains. Use APPROVE only when the plan is feasible enough to implement.`
const PLAN_CRITIC_WHEN_TO_USE =
'Use this agent to critique an implementation plan before code is written. It is read-only and challenges feasibility, scope, risks, verification gaps, and whether a smaller or safer plan exists. Ends with PLAN_REVIEW: APPROVE / CHANGES_NEEDED.'
export const PLAN_CRITIC_AGENT: BuiltInAgentDefinition = {
agentType: 'plan-critic',
whenToUse: PLAN_CRITIC_WHEN_TO_USE,
color: 'yellow',
disallowedTools: [
AGENT_TOOL_NAME,
EXIT_PLAN_MODE_TOOL_NAME,
FILE_EDIT_TOOL_NAME,
FILE_WRITE_TOOL_NAME,
NOTEBOOK_EDIT_TOOL_NAME,
],
source: 'built-in',
baseDir: 'built-in',
model: 'inherit',
omitClaudeMd: true,
getSystemPrompt: () => PLAN_CRITIC_SYSTEM_PROMPT,
criticalSystemReminder_EXPERIMENTAL:
'CRITICAL: This is a READ-ONLY plan critique task. You CANNOT edit, write, or create files. You MUST end with PLAN_REVIEW: APPROVE or PLAN_REVIEW: CHANGES_NEEDED.',
}

View File

@ -6,6 +6,7 @@ import {
areExplorePlanAgentsEnabled,
getBuiltInAgents,
} from './builtInAgents.js'
import { PLAN_CRITIC_AGENT } from './built-in/planCriticAgent.js'
const originalDisableBuiltIns =
process.env.CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS
@ -48,6 +49,7 @@ describe('built-in agents', () => {
expect(agentTypes).toContain('docs-writer')
expect(agentTypes).toContain('performance')
expect(agentTypes).toContain('commit-pr')
expect(agentTypes).toContain('plan-critic')
// game-developer is a project-level agent (.claude/agents), not a built-in.
expect(agentTypes).not.toContain('game-developer')
})
@ -68,6 +70,25 @@ describe('built-in agents', () => {
expect(agentTypes).toContain('docs-writer')
expect(agentTypes).toContain('performance')
expect(agentTypes).toContain('commit-pr')
expect(agentTypes).toContain('plan-critic')
})
test('plan-critic is read-only and returns a parseable verdict', () => {
expect(PLAN_CRITIC_AGENT.agentType).toBe('plan-critic')
expect(PLAN_CRITIC_AGENT.disallowedTools).toEqual(expect.arrayContaining([
'Agent',
'ExitPlanMode',
'Edit',
'Write',
'NotebookEdit',
]))
const prompt = PLAN_CRITIC_AGENT.getSystemPrompt({} as never)
expect(prompt).toContain('READ-ONLY plan critique task')
expect(prompt).toContain('PLAN_REVIEW: APPROVE')
expect(prompt).toContain('PLAN_REVIEW: CHANGES_NEEDED')
expect(prompt).toContain('smaller or safer path')
expect(PLAN_CRITIC_AGENT.criticalSystemReminder_EXPERIMENTAL).toContain('PLAN_REVIEW: APPROVE')
})
test('preserves SDK opt-out in noninteractive sessions', () => {

View File

@ -12,6 +12,7 @@ import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js'
import { MIGRATION_AGENT } from './built-in/migrationAgent.js'
import { PERFORMANCE_AGENT } from './built-in/performanceAgent.js'
import { PLAN_AGENT } from './built-in/planAgent.js'
import { PLAN_CRITIC_AGENT } from './built-in/planCriticAgent.js'
import { REFACTOR_AGENT } from './built-in/refactorAgent.js'
import { SECURITY_REVIEWER_AGENT } from './built-in/securityReviewerAgent.js'
import { STATUSLINE_SETUP_AGENT } from './built-in/statuslineSetup.js'
@ -76,6 +77,7 @@ export function getBuiltInAgents(): AgentDefinition[] {
DOCS_WRITER_AGENT,
PERFORMANCE_AGENT,
COMMIT_PR_AGENT,
PLAN_CRITIC_AGENT,
]
if (areExplorePlanAgentsEnabled()) {