From a6e287fc8c4746aa2c444f703b6a7831de95e2fc Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sun, 22 Mar 2026 21:13:37 +0900 Subject: [PATCH 1/2] feat(mcp): wire plan-reviewer as automatic gate after PLAN completion Add planReviewGate field to parse_mode PLAN/AUTO response that recommends plan-reviewer dispatch after plan completion. Configurable via ai.planReviewGate in codingbuddy.config.json (default: enabled). Closes #840 --- apps/mcp-server/src/config/config.schema.ts | 13 +++ .../src/mcp/handlers/mode.handler.spec.ts | 99 +++++++++++++++++++ .../src/mcp/handlers/mode.handler.ts | 40 +++++++- 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/apps/mcp-server/src/config/config.schema.ts b/apps/mcp-server/src/config/config.schema.ts index 4bcc30b7..7281d2ab 100644 --- a/apps/mcp-server/src/config/config.schema.ts +++ b/apps/mcp-server/src/config/config.schema.ts @@ -112,6 +112,19 @@ const AIConfigSchema = z.object({ * ``` */ maxIncludedSkills: z.number().int().min(0).max(10).optional(), + /** + * Enable/disable automatic plan-reviewer gate after PLAN completion. + * When enabled, parse_mode PLAN response includes a planReviewGate recommendation. + * Default: true (enabled) + * + * @example + * ```javascript + * ai: { + * planReviewGate: false, // disable plan review gate + * } + * ``` + */ + planReviewGate: z.boolean().optional(), }); const AutoConfigSchema = z.object({ diff --git a/apps/mcp-server/src/mcp/handlers/mode.handler.spec.ts b/apps/mcp-server/src/mcp/handlers/mode.handler.spec.ts index a1c313ce..3d5c530b 100644 --- a/apps/mcp-server/src/mcp/handlers/mode.handler.spec.ts +++ b/apps/mcp-server/src/mcp/handlers/mode.handler.spec.ts @@ -1014,4 +1014,103 @@ describe('ModeHandler', () => { expect(parsed.deepThinkingInstructions).toBeUndefined(); }); }); + + describe('planReviewGate in PLAN mode', () => { + it('should include planReviewGate in PLAN mode response by default', async () => { + const result = await handler.handle('parse_mode', { + prompt: 'PLAN design auth feature', + }); + + expect(result?.isError).toBeFalsy(); + const parsed = JSON.parse(result!.content[0].text as string); + expect(parsed.planReviewGate).toBeDefined(); + expect(parsed.planReviewGate).toEqual({ + enabled: true, + agent: 'plan-reviewer', + dispatch: 'recommend', + }); + }); + + it('should include planReviewGate in AUTO mode response', async () => { + mockKeywordService.parseMode = vi.fn().mockResolvedValue({ + ...mockParseModeResult, + mode: 'AUTO', + originalPrompt: 'implement dashboard', + }); + + const result = await handler.handle('parse_mode', { + prompt: 'AUTO implement dashboard', + }); + + expect(result?.isError).toBeFalsy(); + const parsed = JSON.parse(result!.content[0].text as string); + expect(parsed.planReviewGate).toBeDefined(); + expect(parsed.planReviewGate.enabled).toBe(true); + expect(parsed.planReviewGate.agent).toBe('plan-reviewer'); + }); + + it('should NOT include planReviewGate in ACT mode', async () => { + mockKeywordService.parseMode = vi.fn().mockResolvedValue({ + ...mockParseModeResult, + mode: 'ACT', + originalPrompt: 'implement feature', + }); + + const result = await handler.handle('parse_mode', { + prompt: 'ACT implement feature', + }); + + expect(result?.isError).toBeFalsy(); + const parsed = JSON.parse(result!.content[0].text as string); + expect(parsed.planReviewGate).toBeUndefined(); + }); + + it('should NOT include planReviewGate in EVAL mode', async () => { + mockKeywordService.parseMode = vi.fn().mockResolvedValue({ + ...mockParseModeResult, + mode: 'EVAL', + originalPrompt: 'evaluate implementation', + }); + + const result = await handler.handle('parse_mode', { + prompt: 'EVAL evaluate implementation', + }); + + expect(result?.isError).toBeFalsy(); + const parsed = JSON.parse(result!.content[0].text as string); + expect(parsed.planReviewGate).toBeUndefined(); + }); + + it('should disable planReviewGate when config ai.planReviewGate is false', async () => { + (mockConfigService.getSettings as ReturnType).mockResolvedValue({ + ai: { planReviewGate: false }, + }); + + const result = await handler.handle('parse_mode', { + prompt: 'PLAN design feature', + }); + + expect(result?.isError).toBeFalsy(); + const parsed = JSON.parse(result!.content[0].text as string); + expect(parsed.planReviewGate).toBeDefined(); + expect(parsed.planReviewGate.enabled).toBe(false); + }); + + it('should enable planReviewGate when config ai.planReviewGate is true', async () => { + (mockConfigService.getSettings as ReturnType).mockResolvedValue({ + ai: { planReviewGate: true }, + }); + + const result = await handler.handle('parse_mode', { + prompt: 'PLAN design feature', + }); + + expect(result?.isError).toBeFalsy(); + const parsed = JSON.parse(result!.content[0].text as string); + expect(parsed.planReviewGate).toBeDefined(); + expect(parsed.planReviewGate.enabled).toBe(true); + expect(parsed.planReviewGate.agent).toBe('plan-reviewer'); + expect(parsed.planReviewGate.dispatch).toBe('recommend'); + }); + }); }); diff --git a/apps/mcp-server/src/mcp/handlers/mode.handler.ts b/apps/mcp-server/src/mcp/handlers/mode.handler.ts index 62bdc160..5eae4d52 100644 --- a/apps/mcp-server/src/mcp/handlers/mode.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/mode.handler.ts @@ -48,6 +48,16 @@ interface DeepThinkingInstructions { detailLevel: string; } +/** Plan review gate recommendation included in PLAN/AUTO mode responses */ +interface PlanReviewGate { + /** Whether the plan review gate is enabled */ + enabled: boolean; + /** Agent name for plan review */ + agent: string; + /** Dispatch strength for the gate */ + dispatch: string; +} + /** Result type for context document handling */ interface ContextResult { /** Path to the context file */ @@ -215,9 +225,11 @@ export class ModeHandler extends AbstractHandler { // Persist state for context recovery after compaction await this.persistModeState(result.mode); + // Load settings once for dispatch strength and plan review gate + const settings = await this.configService.getSettings(); + // Enrich parallelAgentsRecommendation with dispatch strength if (result.parallelAgentsRecommendation) { - const settings = await this.configService.getSettings(); const configDispatch = settings.ai?.dispatchStrength as DispatchStrength | undefined; result.parallelAgentsRecommendation.dispatch = configDispatch ?? MODE_DISPATCH_DEFAULTS[result.mode as Mode] ?? 'recommend'; @@ -235,6 +247,9 @@ export class ModeHandler extends AbstractHandler { // Build deep thinking instructions for PLAN/AUTO modes const deepThinkingInstructions = this.buildDeepThinkingInstructions(result.mode as Mode); + // Build plan review gate for PLAN/AUTO modes + const planReviewGate = this.buildPlanReviewGate(result.mode as Mode, settings?.ai?.planReviewGate); + return createJsonResponse({ ...result, language, @@ -244,6 +259,8 @@ export class ModeHandler extends AbstractHandler { ...(dispatchReady && { dispatchReady }), // Include deep thinking instructions for PLAN/AUTO modes ...(deepThinkingInstructions && { deepThinkingInstructions }), + // Include plan review gate for PLAN/AUTO modes + ...(planReviewGate && { planReviewGate }), // Include context document info (mandatory) ...contextResult, // Include project root warning when auto-detected and config missing @@ -443,6 +460,27 @@ export class ModeHandler extends AbstractHandler { }; } + /** + * Build plan review gate for PLAN/AUTO modes. + * Returns undefined for ACT/EVAL modes (not applicable). + */ + private buildPlanReviewGate( + mode: Mode, + configValue?: boolean, + ): PlanReviewGate | undefined { + if (mode !== 'PLAN' && mode !== 'AUTO') { + return undefined; + } + + const enabled = configValue !== false; + + return { + enabled, + agent: 'plan-reviewer', + dispatch: 'recommend', + }; + } + /** * Persist mode state for context recovery after compaction */ From 13e42061e87e4c8fa031ca5c921dff10ebe69406 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sun, 22 Mar 2026 21:19:40 +0900 Subject: [PATCH 2/2] style: fix prettier formatting --- RESULT.json | 1 + TASK.md | 24 +++++++++++++++++++ .../src/mcp/handlers/mode.handler.ts | 10 ++++---- 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 RESULT.json create mode 100644 TASK.md diff --git a/RESULT.json b/RESULT.json new file mode 100644 index 00000000..b79b2a39 --- /dev/null +++ b/RESULT.json @@ -0,0 +1 @@ +{"status":"success","issue":"#840","pr_number":879,"pr_url":"https://github.com/JeremyDev87/codingbuddy/pull/879","timestamp":"2026-03-22T21:15:00.000Z","cost":null,"error":null} diff --git a/TASK.md b/TASK.md new file mode 100644 index 00000000..dafb3cb0 --- /dev/null +++ b/TASK.md @@ -0,0 +1,24 @@ +# Task: Issue #840 - wire plan-reviewer as automatic gate after PLAN completion + +## Objective +Modify `apps/mcp-server/src/mcp/handlers/mode.handler.ts` to automatically recommend plan-reviewer dispatch after PLAN completion. + +## Requirements +- When parse_mode returns PLAN mode response, include plan-reviewer in specialist recommendations +- Add `planReviewGate` field to PLAN response: { enabled: true, agent: "plan-reviewer", dispatch: "recommend" } +- Configurable via project config (can be disabled) +- TDD: write tests FIRST in mode.handler.spec.ts +- DO NOT modify agent.handler.ts or context-document.handler.ts + +## Read First +- `apps/mcp-server/src/mcp/handlers/mode.handler.ts` (current PLAN response) +- `apps/mcp-server/src/mcp/handlers/mode.handler.spec.ts` (existing tests) +- `packages/rules/.ai-rules/agents/plan-reviewer.json` (agent definition) + +## Methodology — MANDATORY +Use codingbuddy PLAN→ACT→EVAL. Use /ship to create PR closing #840. Write RESULT.json after completion. + +## RESULT.json Protocol +```json +{"status":"success|failure|error","issue":"#840","pr_number":null,"pr_url":null,"timestamp":"","cost":null,"error":null} +``` diff --git a/apps/mcp-server/src/mcp/handlers/mode.handler.ts b/apps/mcp-server/src/mcp/handlers/mode.handler.ts index 5eae4d52..d9a5d8fc 100644 --- a/apps/mcp-server/src/mcp/handlers/mode.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/mode.handler.ts @@ -248,7 +248,10 @@ export class ModeHandler extends AbstractHandler { const deepThinkingInstructions = this.buildDeepThinkingInstructions(result.mode as Mode); // Build plan review gate for PLAN/AUTO modes - const planReviewGate = this.buildPlanReviewGate(result.mode as Mode, settings?.ai?.planReviewGate); + const planReviewGate = this.buildPlanReviewGate( + result.mode as Mode, + settings?.ai?.planReviewGate, + ); return createJsonResponse({ ...result, @@ -464,10 +467,7 @@ export class ModeHandler extends AbstractHandler { * Build plan review gate for PLAN/AUTO modes. * Returns undefined for ACT/EVAL modes (not applicable). */ - private buildPlanReviewGate( - mode: Mode, - configValue?: boolean, - ): PlanReviewGate | undefined { + private buildPlanReviewGate(mode: Mode, configValue?: boolean): PlanReviewGate | undefined { if (mode !== 'PLAN' && mode !== 'AUTO') { return undefined; }