diff --git a/RESULT.json b/RESULT.json new file mode 100644 index 0000000..b79b2a3 --- /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 0000000..dafb3cb --- /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/config/config.schema.ts b/apps/mcp-server/src/config/config.schema.ts index 4bcc30b..7281d2a 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 a1c313c..3d5c530 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 62bdc16..d9a5d8f 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,12 @@ 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 +262,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 +463,24 @@ 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 */