From 1c5782a870e803dc60ad7ccb2d2b0d1da995f922 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Thu, 9 Apr 2026 22:59:48 +0900 Subject: [PATCH] feat(mcp-server): add activate tool and promote Teams as primary execution strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add activate handler: one-shot entry point combining rule loading, primary agent resolution, specialist recommendation, and prompt generation in a single call (~70% token reduction vs parse_mode) - Promote Claude native Teams from experimental to auto-enabled in Claude Code environments (claude-native source detection) - Deprecate context-document, briefing, resume, analyze_task tools with Claude Code Memory migration guidance - Remove EXPERIMENTAL label from discussion handler, guide toward activate + Teams for real specialist debate - Rewrite tool-priority.md with 3-layer hierarchy: Claude Code Native > codingbuddy > OMC - Simplify custom-instructions.md: 4 MANDATORY rules → 2 + native feature mapping table - Add activate + Teams guide to claude-code.md adapter - 26 new tests for activate handler, 4 for Teams auto-detection --- .claude/rules/custom-instructions.md | 136 ++----- .claude/rules/tool-priority.md | 74 ++-- .../agent/teams-capability.service.spec.ts | 61 +++- .../src/agent/teams-capability.service.ts | 27 +- .../src/agent/teams-capability.types.ts | 1 + .../src/mcp/handlers/activate.handler.spec.ts | 315 ++++++++++++++++ .../src/mcp/handlers/activate.handler.ts | 337 ++++++++++++++++++ .../src/mcp/handlers/briefing.handler.ts | 2 +- .../mcp/handlers/checklist-context.handler.ts | 2 +- .../mcp/handlers/context-document.handler.ts | 4 +- .../mcp/handlers/discussion.handler.spec.ts | 11 +- .../src/mcp/handlers/discussion.handler.ts | 26 +- .../src/mcp/handlers/discussion.types.ts | 11 +- apps/mcp-server/src/mcp/handlers/index.ts | 6 + .../src/mcp/handlers/resume.handler.ts | 3 +- apps/mcp-server/src/mcp/mcp.module.ts | 2 + .../rules/.ai-rules/adapters/claude-code.md | 54 ++- 17 files changed, 896 insertions(+), 176 deletions(-) create mode 100644 apps/mcp-server/src/mcp/handlers/activate.handler.spec.ts create mode 100644 apps/mcp-server/src/mcp/handlers/activate.handler.ts diff --git a/.claude/rules/custom-instructions.md b/.claude/rules/custom-instructions.md index f64899d0..d6b18ae1 100644 --- a/.claude/rules/custom-instructions.md +++ b/.claude/rules/custom-instructions.md @@ -123,125 +123,50 @@ Even if plan separates TDD into individual steps (e.g., Step 1: Write test, Step -**When user message starts with PLAN, ACT, EVAL, or AUTO keyword (or localized: Korean 계획/실행/평가/자동, Japanese 計画/実行/評価/自動, Chinese 计划/执行/评估/自动, Spanish PLANIFICAR/ACTUAR/EVALUAR/AUTOMÁTICO):** +**When user message starts with PLAN, ACT, EVAL, or AUTO keyword (or localized equivalents):** -1. **IMMEDIATELY** call `parse_mode` MCP tool with the user's prompt -2. Follow the returned `instructions` **EXACTLY** -3. Apply the returned `rules` as context -4. If `warnings` are present, inform the user - -**This is MANDATORY, not optional.** - -Failure to call `parse_mode` when these keywords are present will result in: -- Missed critical checklists (Devil's Advocate Analysis, Impact Radius Analysis) -- Incomplete evaluations -- Quality issues not caught before deployment - -**Red Flags** (STOP if you think these): -| Thought | Reality | -|---------|---------| -| "I can handle EVAL myself" | NO. Call parse_mode FIRST. | -| "The rules are similar anyway" | NO. Each mode has specific checklists. | -| "I'll save a tool call" | NO. parse_mode MUST be called FIRST. | -| "I already know what to do" | NO. Rules may have been updated. | +1. Call `activate` MCP tool with the user's prompt (preferred in Claude Code) +2. **Fallback**: Call `parse_mode` if `activate` is unavailable +3. Follow the returned `rules` as context +4. Use returned `specialists` to run a council via Claude native Teams -Examples: -- `PLAN design auth feature` → **immediately** call parse_mode → work in PLAN mode -- `AUTO implement user dashboard` → **immediately** call parse_mode → autonomous PLAN→ACT→EVAL cycle - -## 🔴 MANDATORY: Parallel Specialist Agent Execution - - - -**When `parse_mode` returns `dispatchReady`, use it directly with the Task tool — no extra calls needed.** - -**Outer Transport Selection (before dispatch):** -- [ ] Check `availableStrategies` in `parse_mode` response (outer transport options) -- [ ] If `["subagent", "taskmaestro"]` → AskUserQuestion to choose outer strategy -- [ ] If `taskmaestroInstallHint` present and user wants taskmaestro → guide installation -- [ ] Pass chosen strategy to `dispatch_agents(executionStrategy: ...)` -- [ ] Teams (inner coordination, experimental) may be used within a session if APIs are available - -**Quick Checklist (Auto-Dispatch - Preferred):** -- [ ] Check `dispatchReady` in `parse_mode` response -- [ ] Use `dispatchReady.primaryAgent.dispatchParams` with Task tool -- [ ] Use `dispatchReady.parallelAgents[].dispatchParams` with Task tool (`run_in_background: true`) -- [ ] Collect results with `TaskOutput` -- [ ] Summarize all findings - -**Fallback (if `dispatchReady` is not present):** -- [ ] Call `dispatch_agents` or `prepare_parallel_agents` with recommended specialists -- [ ] Execute each agent via Task tool (`subagent_type: "general-purpose"`, `run_in_background: true`) -- [ ] Display activation status -- [ ] Collect results with `TaskOutput` -- [ ] Summarize all findings - -**Mode-specific Specialists:** - -| Mode | Specialists | -|------|-------------| -| **PLAN** | 🏛️ architecture, 🧪 test-strategy, 📨 event-architecture, 🔗 integration, 📊 observability, 🔄 migration | -| **ACT** | 📏 code-quality, 🧪 test-strategy, 📨 event-architecture, 🔗 integration | -| **EVAL** | 🔒 security, ♿ accessibility, ⚡ performance, 📏 code-quality, 📨 event-architecture, 🔗 integration, 📊 observability, 🔄 migration | -| **AUTO** | 🏛️ architecture, 🧪 test-strategy, 🔒 security, 📏 code-quality, 📨 event-architecture, 🔗 integration, 📊 observability, 🔄 migration | - -> **Note:** SubAgent and TaskMaestro are **outer transport** strategies (one per invocation, user choice). -> Teams is an **inner coordination** layer (experimental) that can optionally run within either outer strategy. -> See the [Execution Model](../../packages/rules/.ai-rules/adapters/claude-code.md#execution-model-outer-transport-vs-inner-coordination) for details. - -**📖 Full Guide:** [Parallel Specialist Agents Execution](../../packages/rules/.ai-rules/adapters/claude-code.md#parallel-specialist-agents-execution) - - - -## 🔴 MANDATORY: Auto-Dispatch Enforcement - - - -**When `parse_mode` returns `dispatch="auto"`, you MUST dispatch all recommended specialists. No exceptions.** - -### Core Rule - -If the `parse_mode` response contains `dispatch="auto"` or `dispatchReady` with specialist agents: -1. **MUST** dispatch every listed specialist — skipping any is a protocol violation -2. Use the selected **outer transport** (SubAgent or TaskMaestro) to dispatch specialists -3. **Optionally** use Teams as inner coordination within a session (experimental, requires runtime API availability) -4. **Report results** via the dispatch mechanism (TaskOutput for SubAgent, SendMessage for Teams) - -### Red Flags (STOP if you think these) - -| Thought | Reality | -|---------|---------| -| "I can handle this analysis myself" | NO. Specialists have domain expertise you lack. Dispatch them. | -| "It's just a small change, no need for specialists" | NO. dispatch="auto" means the system determined specialists are needed. | -| "I'll save time by skipping dispatch" | NO. Skipping specialists causes missed issues that cost more time later. | -| "The specialists will just repeat what I already know" | NO. Specialists catch domain-specific issues you would miss. | -| "I'll dispatch them later after I look at the code" | NO. Dispatch IMMEDIATELY when dispatch="auto" is returned. | +## 🔴 MANDATORY: Specialist Council Execution - + -## 🔴 MANDATORY: Context Document Management +**When `activate` returns specialists, run them as a council via Claude native Teams.** - +### Workflow -**Fixed file `docs/codingbuddy/context.md` persists PLAN → ACT → EVAL context across context compaction.** +1. Call `activate({ prompt })` → get rules, primaryAgent, specialists +2. Create a Claude native Team with the returned specialists as teammates +3. Each specialist independently analyzes the task +4. Specialists cross-review each other's findings +5. Collect consensus: approve | concern | reject +6. Summarize all findings to user -### How It Works +### Fallback (non-Teams environments) -`parse_mode` **automatically** manages the context document: +If Teams is not available, dispatch specialists as parallel subagents: +- Use `Agent` tool with `run_in_background: true` for each specialist +- Collect results and synthesize -- **PLAN/AUTO mode**: Resets (deletes and recreates) the context document -- **ACT/EVAL mode**: Appends a new section to the existing document + -### Required Workflow +## Claude Code Native Feature Mapping -**In ALL modes:** -1. `parse_mode` automatically reads/creates context -2. Review `contextDocument` for previous decisions and notes -3. Before completing: `update_context` to persist current work +Use Claude Code native features instead of codingbuddy equivalents: - +| Need | Native Feature | Instead of | +|------|----------------|------------| +| Cross-session context | **Claude Code Memory** | `update_context` / `create_briefing` / `resume_session` | +| Specialist debate | **Claude native Teams** | `dispatch_agents` subagent strategy | +| Task exploration | **/dream** | `analyze_task` | +| Planning with approval | **EnterPlanMode** | `parse_mode` planning stage | +| Repeated execution | **/loop** | AUTO mode repetition | +| Clarification | **AskUserQuestion** | clarification gate | ## Claude Code Specific @@ -250,7 +175,6 @@ If the `parse_mode` response contains `dispatch="auto"` or `dispatchReady` with - Provide clear, actionable feedback - Reference project context from `packages/rules/.ai-rules/rules/project.md` - Follow PLAN → ACT → EVAL workflow when appropriate -- Use AUTO mode for autonomous quality-driven development cycles ## Full Documentation diff --git a/.claude/rules/tool-priority.md b/.claude/rules/tool-priority.md index 5469001e..333c7fe1 100644 --- a/.claude/rules/tool-priority.md +++ b/.claude/rules/tool-priority.md @@ -1,73 +1,79 @@ -# Tool Priority: codingbuddy vs oh-my-claudecode (OMC) +# Tool Priority: Claude Code Native > codingbuddy > OMC ## Core Principle -**codingbuddy FIRST** — Use codingbuddy tools for all workflow management, agent dispatch, and quality control. -**OMC for unique features only** — Use OMC tools when they provide capabilities codingbuddy does not. +1. **Claude Code native FIRST** — Memory, Teams, Plan mode, /dream, /loop handle orchestration natively +2. **codingbuddy for unique value** — Rules, agents, checklists, quality reports are codingbuddy's core +3. **OMC for dev tools** — LSP, AST grep, REPL, git-master when needed --- -## codingbuddy FIRST +## Layer 1: Claude Code Native (highest priority) -Always reach for these codingbuddy tools before any OMC equivalent: +These features are built into Claude Code and should be used instead of codingbuddy equivalents: + +| Native Feature | Purpose | Replaces | +|----------------|---------|----------| +| **Claude Code Memory** | Cross-session context persistence | `update_context`, `create_briefing`, `resume_session` | +| **Claude native Teams** | Run specialist agents as teammates for real-time debate | `dispatch_agents` subagent strategy | +| **EnterPlanMode** | Structured planning with user approval | `parse_mode` planning stage routing | +| **/dream** | Autonomous task exploration and analysis | `analyze_task` | +| **/loop** | Recurring execution on interval | `parse_mode` AUTO mode repetition | +| **AskUserQuestion** | Clarification from user | `parse_mode` clarification gate | + +--- + +## Layer 2: codingbuddy (unique value) + +Use codingbuddy tools for capabilities Claude Code does not provide natively: | Tool | Purpose | |------|---------| -| `parse_mode` | Mode management (PLAN/ACT/EVAL/AUTO), agent activation, context init | -| `dispatch_agents` | Agent dispatch with Task tool-ready params | -| `analyze_task` | Pre-planning task analysis, risk assessment, specialist recommendations | -| `update_context` | Context persistence across PLAN → ACT → EVAL modes | -| `generate_checklist` | Contextual checklists (security, a11y, performance, testing) | +| `activate` | **One-shot entry point**: rules + primary agent + specialists + discussion format | +| `parse_mode` | Legacy mode entry (for non-Claude Code hosts: Cursor, Codex, etc.) | | `search_rules` | Query project rules and guidelines | | `get_agent_details` | Agent profile and expertise lookup | +| `generate_checklist` | Contextual checklists (security, a11y, performance, testing) | | `get_project_config` | Tech stack, architecture, language settings | -| `prepare_parallel_agents` | Ready-to-use prompts for parallel specialist agents | | `pr_quality_report` | Run specialist agents on changed files for PR quality | -| `create_briefing` | Capture session state for cross-session recovery | -| `resume_session` | Load previous session briefing | | `get_rule_impact_report` | Rule effectiveness analytics | --- -## OMC Only +## Layer 3: OMC (dev tools) -Use these OMC tools when the capability is not available in codingbuddy: +Use OMC tools for capabilities neither Claude Code nor codingbuddy provide: | Tool / Skill | Purpose | |--------------|---------| | LSP tools (`lsp_hover`, `lsp_goto_definition`, `lsp_find_references`, etc.) | Language server protocol — type info, definitions, references | | AST grep (`ast_grep_search`, `ast_grep_replace`) | Structural code search and refactoring | | Python REPL (`python_repl`) | Interactive data analysis and computation | -| State tools (`state_read`, `state_write`, `state_clear`) | Mode state persistence for autopilot/ralph/ultrawork | -| Notepad tools (`notepad_read`, `notepad_write_*`) | Session notes and compaction-resilient memory | -| Project memory (`project_memory_*`) | Long-term project knowledge persistence | | `/git-master` | Atomic commits, rebasing, history management | | `/build-fix` | Build and TypeScript error resolution | | `/deepsearch` | Thorough multi-pass codebase search | -| `/team`, `/swarm` | Multi-agent coordination with shared task lists | -| `/ultrawork`, `/autopilot`, `/ralph` | Autonomous execution loops | -| `/pipeline` | Sequential/branching agent workflows | --- -## Overlap Matrix - -When both tools could apply, codingbuddy wins: +## Quick Decision Matrix | Use Case | Use This | Not This | |----------|----------|----------| -| Starting PLAN/ACT/EVAL mode | `parse_mode` | OMC mode state tools | -| Dispatching specialist agents | `dispatch_agents` | OMC `/team` or `/swarm` | -| Code/security review | codingbuddy `search_rules` + specialists | OMC `/code-review`, `/security-review` | -| Context across sessions | `update_context` | OMC notepad/project memory | -| Task analysis before planning | `analyze_task` | Ad-hoc OMC tools | -| Checklists (security, a11y) | `generate_checklist` | Manual OMC review | +| Starting a workflow | `activate` | `parse_mode` (in Claude Code) | +| Running specialist council | Claude native Teams | subagent dispatch | +| Cross-session context | Claude Code Memory | `update_context` / `create_briefing` | +| Task analysis | `/dream` | `analyze_task` | +| Repeated execution | `/loop` | AUTO mode cycle | +| Clarification | AskUserQuestion | clarification gate | +| Rules & checklists | codingbuddy `search_rules`, `generate_checklist` | — | +| Code review quality | codingbuddy `pr_quality_report` | OMC `/code-review` | +| Type info & references | OMC LSP tools | — | --- ## Decision Rationale -- codingbuddy tools are project-aware and integrate with the PLAN/ACT/EVAL workflow -- OMC tools are general-purpose developer tools without project context -- Using codingbuddy first ensures consistent quality gates and audit trail via `docs/codingbuddy/context.md` -- OMC's unique capabilities (LSP, AST, REPL) complement codingbuddy; they do not replace it +- Claude Code native features handle orchestration (memory, teams, planning, loops) better than MCP tools +- codingbuddy's unique value is **rules, agents, and checklists** — domain knowledge, not orchestration +- OMC's unique value is **dev tooling** (LSP, AST, REPL) — code intelligence, not workflow +- `parse_mode` remains available for backward compatibility with non-Claude Code hosts diff --git a/apps/mcp-server/src/agent/teams-capability.service.spec.ts b/apps/mcp-server/src/agent/teams-capability.service.spec.ts index 7ac392d5..856391a3 100644 --- a/apps/mcp-server/src/agent/teams-capability.service.spec.ts +++ b/apps/mcp-server/src/agent/teams-capability.service.spec.ts @@ -2,18 +2,28 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { TeamsCapabilityService } from './teams-capability.service'; import type { ConfigService } from '../config/config.service'; -function createMockConfigService(experimental?: { teamsCoordination?: boolean }): ConfigService { +function createMockConfigService( + experimental?: { teamsCoordination?: boolean }, + clientName?: string, +): ConfigService { return { getSettings: vi.fn().mockResolvedValue({ experimental }), + getClientName: vi.fn().mockReturnValue(clientName), } as unknown as ConfigService; } describe('TeamsCapabilityService', () => { let originalEnv: string | undefined; + let originalClaudeCode: string | undefined; + let originalClaudeEntrypoint: string | undefined; beforeEach(() => { originalEnv = process.env.CODINGBUDDY_TEAMS_ENABLED; + originalClaudeCode = process.env.CLAUDE_CODE; + originalClaudeEntrypoint = process.env.CLAUDE_CODE_ENTRYPOINT; delete process.env.CODINGBUDDY_TEAMS_ENABLED; + delete process.env.CLAUDE_CODE; + delete process.env.CLAUDE_CODE_ENTRYPOINT; }); afterEach(() => { @@ -22,6 +32,16 @@ describe('TeamsCapabilityService', () => { } else { delete process.env.CODINGBUDDY_TEAMS_ENABLED; } + if (originalClaudeCode !== undefined) { + process.env.CLAUDE_CODE = originalClaudeCode; + } else { + delete process.env.CLAUDE_CODE; + } + if (originalClaudeEntrypoint !== undefined) { + process.env.CLAUDE_CODE_ENTRYPOINT = originalClaudeEntrypoint; + } else { + delete process.env.CLAUDE_CODE_ENTRYPOINT; + } }); describe('getStatus', () => { @@ -125,6 +145,43 @@ describe('TeamsCapabilityService', () => { }); }); + describe('Claude Code auto-detection', () => { + it('should auto-enable when CLAUDE_CODE=1 is set', async () => { + process.env.CLAUDE_CODE = '1'; + const service = new TeamsCapabilityService(createMockConfigService()); + const status = await service.getStatus(); + + expect(status.available).toBe(true); + expect(status.source).toBe('claude-native'); + expect(status.reason).toContain('Claude Code'); + }); + + it('should auto-enable when CLAUDE_CODE_ENTRYPOINT is set', async () => { + process.env.CLAUDE_CODE_ENTRYPOINT = '/some/path'; + const service = new TeamsCapabilityService(createMockConfigService()); + const status = await service.getStatus(); + + expect(status.available).toBe(true); + expect(status.source).toBe('claude-native'); + }); + + it('should auto-enable when clientName is claude-code', async () => { + const service = new TeamsCapabilityService(createMockConfigService(undefined, 'claude-code')); + const status = await service.getStatus(); + + expect(status.available).toBe(true); + expect(status.source).toBe('claude-native'); + }); + + it('should not auto-enable for other clients', async () => { + const service = new TeamsCapabilityService(createMockConfigService(undefined, 'cursor')); + const status = await service.getStatus(); + + expect(status.available).toBe(false); + expect(status.source).toBe('default'); + }); + }); + describe('readEnvFlag', () => { it('should return undefined when env var is not set', () => { const service = new TeamsCapabilityService(createMockConfigService()); @@ -166,7 +223,7 @@ describe('TeamsCapabilityService', () => { expect(status).toHaveProperty('source'); expect(typeof status.available).toBe('boolean'); expect(typeof status.reason).toBe('string'); - expect(['config', 'environment', 'default']).toContain(status.source); + expect(['config', 'environment', 'claude-native', 'default']).toContain(status.source); }); }); }); diff --git a/apps/mcp-server/src/agent/teams-capability.service.ts b/apps/mcp-server/src/agent/teams-capability.service.ts index 56a352cd..5813d364 100644 --- a/apps/mcp-server/src/agent/teams-capability.service.ts +++ b/apps/mcp-server/src/agent/teams-capability.service.ts @@ -49,10 +49,21 @@ export class TeamsCapabilityService { return status; } - // 3. Default: disabled + // 3. Auto-detect Claude Code environment (native Teams support) + if (this.isClaudeCodeEnvironment()) { + const status: TeamsCapabilityStatus = { + available: true, + reason: 'Auto-enabled: Claude Code environment detected with native Teams support', + source: 'claude-native', + }; + this.logger.debug(`Teams capability: ${status.reason}`); + return status; + } + + // 4. Default: disabled for non-Claude Code hosts const status: TeamsCapabilityStatus = { available: false, - reason: 'Teams coordination is experimental and disabled by default', + reason: 'Teams coordination disabled by default for non-Claude Code hosts', source: 'default', }; this.logger.debug(`Teams capability: ${status.reason}`); @@ -67,6 +78,18 @@ export class TeamsCapabilityService { return status.available; } + /** + * Detect whether running inside a Claude Code environment. + * Claude Code sets specific env vars that distinguish it from other hosts. + */ + private isClaudeCodeEnvironment(): boolean { + return ( + process.env.CLAUDE_CODE === '1' || + process.env.CLAUDE_CODE_ENTRYPOINT !== undefined || + this.configService.getClientName() === 'claude-code' + ); + } + /** * Read the CODINGBUDDY_TEAMS_ENABLED environment variable. * Returns undefined when not set, boolean when explicitly set. diff --git a/apps/mcp-server/src/agent/teams-capability.types.ts b/apps/mcp-server/src/agent/teams-capability.types.ts index 64d8d14e..00898396 100644 --- a/apps/mcp-server/src/agent/teams-capability.types.ts +++ b/apps/mcp-server/src/agent/teams-capability.types.ts @@ -17,4 +17,5 @@ export interface TeamsCapabilityStatus { export type TeamsCapabilitySource = | 'config' // experimental.teamsCoordination in codingbuddy.config.json | 'environment' // CODINGBUDDY_TEAMS_ENABLED env var + | 'claude-native' // Auto-detected Claude Code environment with native Teams support | 'default'; // no explicit signal, default to disabled diff --git a/apps/mcp-server/src/mcp/handlers/activate.handler.spec.ts b/apps/mcp-server/src/mcp/handlers/activate.handler.spec.ts new file mode 100644 index 00000000..a8f653e0 --- /dev/null +++ b/apps/mcp-server/src/mcp/handlers/activate.handler.spec.ts @@ -0,0 +1,315 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ActivateHandler } from './activate.handler'; +import type { AgentService } from '../../agent/agent.service'; +import type { TeamsCapabilityService } from '../../agent/teams-capability.service'; +import type { KeywordService } from '../../keyword/keyword.service'; +import type { RulesService } from '../../rules/rules.service'; + +function createMockKeywordService(): KeywordService { + return { + getRulesForMode: vi.fn().mockResolvedValue([ + { name: 'rules/core.md', content: '# Core rules' }, + { name: 'rules/augmented-coding.md', content: '# Augmented coding' }, + ]), + } as unknown as KeywordService; +} + +function createMockAgentService(): AgentService { + return { + getAgentSystemPrompt: vi.fn().mockResolvedValue({ + agentName: 'plan-mode-agent', + displayName: 'Plan Mode Agent', + systemPrompt: 'You are a planning agent...', + description: 'Planning specialist', + }), + prepareParallelAgents: vi.fn().mockResolvedValue({ + agents: [ + { + id: 'security-specialist', + displayName: 'Security Specialist', + taskPrompt: 'Analyze security...', + description: 'Security analysis', + }, + { + id: 'architecture-specialist', + displayName: 'Architecture Specialist', + taskPrompt: 'Review architecture...', + description: 'Architecture review', + }, + ], + parallelExecutionHint: 'Use Task tool...', + }), + } as unknown as AgentService; +} + +function createMockRulesService(): RulesService { + return { + getRuleContent: vi.fn().mockResolvedValue( + JSON.stringify({ + modes: { + PLAN: { + defaultSpecialists: ['architecture-specialist', 'test-strategy-specialist'], + }, + ACT: { defaultSpecialists: ['code-quality-specialist'] }, + EVAL: { + defaultSpecialists: ['security-specialist', 'performance-specialist'], + }, + AUTO: { defaultSpecialists: ['architecture-specialist'] }, + }, + }), + ), + } as unknown as RulesService; +} + +function createMockTeamsCapability(available = true): TeamsCapabilityService { + return { + getStatus: vi.fn().mockResolvedValue({ + available, + reason: available ? 'Auto-enabled: Claude Code environment detected' : 'Disabled by default', + source: available ? 'claude-native' : 'default', + }), + } as unknown as TeamsCapabilityService; +} + +describe('ActivateHandler', () => { + let handler: ActivateHandler; + let keywordService: KeywordService; + let agentService: AgentService; + let rulesService: RulesService; + let teamsCapability: TeamsCapabilityService; + + beforeEach(() => { + keywordService = createMockKeywordService(); + agentService = createMockAgentService(); + rulesService = createMockRulesService(); + teamsCapability = createMockTeamsCapability(); + handler = new ActivateHandler(keywordService, agentService, rulesService, teamsCapability); + }); + + describe('getToolDefinitions', () => { + it('should define the activate tool', () => { + const definitions = handler.getToolDefinitions(); + expect(definitions).toHaveLength(1); + expect(definitions[0].name).toBe('activate'); + }); + + it('should require prompt parameter', () => { + const definitions = handler.getToolDefinitions(); + expect(definitions[0].inputSchema.required).toEqual(['prompt']); + }); + + it('should have mode and primaryAgent as optional', () => { + const definitions = handler.getToolDefinitions(); + const props = definitions[0].inputSchema.properties; + expect(props).toHaveProperty('prompt'); + expect(props).toHaveProperty('mode'); + expect(props).toHaveProperty('primaryAgent'); + }); + }); + + describe('handle', () => { + it('should return null for unknown tools', async () => { + const result = await handler.handle('unknown_tool', {}); + expect(result).toBeNull(); + }); + + it('should return error for missing prompt', async () => { + const result = await handler.handle('activate', {}); + expect(result?.isError).toBe(true); + expect(result?.content[0].text).toContain('Missing required parameter: prompt'); + }); + + it('should return complete response for valid prompt', async () => { + const result = await handler.handle('activate', { + prompt: 'PLAN design auth feature', + }); + + expect(result).not.toBeNull(); + expect(result?.isError).toBeFalsy(); + + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('PLAN'); + expect(data.rules).toBeDefined(); + expect(data.primaryAgent).toBeDefined(); + expect(data.specialists).toBeDefined(); + expect(data.discussion).toBeDefined(); + expect(data.nativeIntegration).toBeDefined(); + }); + }); + + describe('mode resolution', () => { + it('should auto-detect PLAN from prompt', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN design auth' }); + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('PLAN'); + }); + + it('should auto-detect ACT from prompt', async () => { + const result = await handler.handle('activate', { prompt: 'ACT implement the feature' }); + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('ACT'); + }); + + it('should auto-detect EVAL from prompt', async () => { + const result = await handler.handle('activate', { prompt: 'EVAL review the code' }); + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('EVAL'); + }); + + it('should use explicit mode parameter over prompt keyword', async () => { + const result = await handler.handle('activate', { + prompt: 'PLAN some task', + mode: 'EVAL', + }); + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('EVAL'); + }); + + it('should default to PLAN when no keyword found', async () => { + const result = await handler.handle('activate', { prompt: 'design auth feature' }); + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('PLAN'); + }); + + it('should support Korean keywords', async () => { + const result = await handler.handle('activate', { prompt: '실행 인증 기능 구현' }); + const data = JSON.parse(result!.content[0].text); + expect(data.mode).toBe('ACT'); + }); + }); + + describe('rules loading', () => { + it('should call getRulesForMode with detected mode', async () => { + await handler.handle('activate', { prompt: 'EVAL review code' }); + expect(keywordService.getRulesForMode).toHaveBeenCalledWith('EVAL', 'standard'); + }); + + it('should include rules in response', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.rules).toHaveLength(2); + expect(data.rules[0]).toHaveProperty('name'); + expect(data.rules[0]).toHaveProperty('content'); + }); + }); + + describe('primary agent resolution', () => { + it('should resolve default primary agent for mode', async () => { + await handler.handle('activate', { prompt: 'PLAN task' }); + expect(agentService.getAgentSystemPrompt).toHaveBeenCalledWith( + 'plan-mode-agent', + expect.objectContaining({ mode: 'PLAN' }), + ); + }); + + it('should use explicit primaryAgent parameter', async () => { + await handler.handle('activate', { + prompt: 'PLAN task', + primaryAgent: 'solution-architect', + }); + expect(agentService.getAgentSystemPrompt).toHaveBeenCalledWith( + 'solution-architect', + expect.objectContaining({ mode: 'PLAN' }), + ); + }); + + it('should include primaryAgent in response', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.primaryAgent).toEqual({ + name: 'plan-mode-agent', + displayName: 'Plan Mode Agent', + systemPrompt: 'You are a planning agent...', + }); + }); + + it('should handle primary agent resolution failure gracefully', async () => { + (agentService.getAgentSystemPrompt as ReturnType).mockRejectedValueOnce( + new Error('Agent not found'), + ); + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.primaryAgent).toBeNull(); + }); + }); + + describe('specialist resolution', () => { + it('should load default specialists from keyword-modes.json', async () => { + await handler.handle('activate', { prompt: 'PLAN task' }); + expect(rulesService.getRuleContent).toHaveBeenCalledWith('keyword-modes.json'); + }); + + it('should merge mode defaults with context-aware patterns', async () => { + const result = await handler.handle('activate', { + prompt: 'PLAN design authentication with JWT security', + }); + const data = JSON.parse(result!.content[0].text); + // Should include both mode defaults (architecture, test-strategy) + // and context-detected (security from "authentication" + "security") + const specialistNames = data.specialists.map((s: { name: string }) => s.name); + expect(specialistNames.length).toBeGreaterThan(0); + }); + + it('should prepare specialists with full verbosity', async () => { + await handler.handle('activate', { prompt: 'PLAN task' }); + expect(agentService.prepareParallelAgents).toHaveBeenCalledWith( + 'PLAN', + expect.any(Array), + undefined, + expect.any(String), + 'full', + ); + }); + + it('should include specialist prompts and domains in response', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.specialists.length).toBeGreaterThan(0); + for (const specialist of data.specialists) { + expect(specialist).toHaveProperty('name'); + expect(specialist).toHaveProperty('displayName'); + expect(specialist).toHaveProperty('prompt'); + expect(specialist).toHaveProperty('domain'); + } + }); + }); + + describe('discussion format', () => { + it('should include discussion guide in response', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.discussion).toEqual({ + format: 'Each specialist: approve|concern|reject + reasoning + suggestedChanges', + consensus: 'No rejections = consensus reached', + crossReview: "Specialists review each other's opinions", + }); + }); + }); + + describe('native integration', () => { + it('should indicate Teams available when enabled', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.nativeIntegration.teams).toContain('native Teams'); + }); + + it('should indicate how to enable Teams when disabled', async () => { + handler = new ActivateHandler( + keywordService, + agentService, + rulesService, + createMockTeamsCapability(false), + ); + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.nativeIntegration.teams).toContain('CODINGBUDDY_TEAMS_ENABLED'); + }); + + it('should include Memory and orchestration guidance', async () => { + const result = await handler.handle('activate', { prompt: 'PLAN task' }); + const data = JSON.parse(result!.content[0].text); + expect(data.nativeIntegration.memory).toContain('Memory'); + expect(data.nativeIntegration.orchestration).toContain('natively'); + }); + }); +}); diff --git a/apps/mcp-server/src/mcp/handlers/activate.handler.ts b/apps/mcp-server/src/mcp/handlers/activate.handler.ts new file mode 100644 index 00000000..b4e177f6 --- /dev/null +++ b/apps/mcp-server/src/mcp/handlers/activate.handler.ts @@ -0,0 +1,337 @@ +import { Injectable, Inject, Logger } from '@nestjs/common'; +import type { ToolDefinition } from './base.handler'; +import type { ToolResponse } from '../response.utils'; +import { AbstractHandler } from './abstract-handler'; +import { createJsonResponse, createErrorResponse } from '../response.utils'; +import { AgentService } from '../../agent/agent.service'; +import { TeamsCapabilityService } from '../../agent/teams-capability.service'; +import type { KeywordService } from '../../keyword/keyword.service'; +import { KEYWORD_SERVICE } from '../../keyword/keyword.module'; +import { KEYWORDS, LOCALIZED_KEYWORD_MAP, type Mode } from '../../keyword/keyword.types'; +import { RulesService } from '../../rules/rules.service'; +import { + extractRequiredString, + extractOptionalString, + isValidMode, +} from '../../shared/validation.constants'; + +/** Default primary agents per mode (from keyword-modes.json agent field) */ +const MODE_DEFAULT_AGENTS: Record = { + PLAN: 'plan-mode-agent', + ACT: 'act-mode-agent', + EVAL: 'eval-mode-agent', + AUTO: 'auto-mode-agent', +}; + +/** + * Context-aware specialist detection patterns. + * Mirrors KeywordService.CONTEXT_SPECIALIST_PATTERNS for prompt analysis. + */ +const CONTEXT_SPECIALIST_PATTERNS: ReadonlyArray<{ pattern: RegExp; specialist: string }> = [ + { + pattern: /보안|security|auth|인증|JWT|OAuth|XSS|CSRF|취약점|vulnerability/i, + specialist: 'security-specialist', + }, + { + pattern: /접근성|accessibility|a11y|WCAG|aria|스크린\s*리더|screen\s*reader/i, + specialist: 'accessibility-specialist', + }, + { + pattern: /성능|performance|최적화|optimiz|빠르게|느린|slow|fast|bundle\s*size|로딩/i, + specialist: 'performance-specialist', + }, + { + pattern: /다국어|i18n|internationalization|번역|locale|translation|localization/i, + specialist: 'i18n-specialist', + }, + { + pattern: /SEO|검색\s*엔진|search\s*engine|메타|meta\s*tag|sitemap|구조화\s*데이터/i, + specialist: 'seo-specialist', + }, + { + pattern: /문서화|document|README|API\s*문서|JSDoc|주석|comment/i, + specialist: 'documentation-specialist', + }, + { + pattern: /UI|UX|디자인|design\s*system|사용자\s*경험|user\s*experience|인터랙션/i, + specialist: 'ui-ux-designer', + }, + { + pattern: + /외부\s*서비스|external\s*(api|service)|webhook|웹훅|third-?party|circuit\s*breaker|retry\s*pattern|API\s*integration|서드파티|연동|SDK\s*wrapper/i, + specialist: 'integration-specialist', + }, + { + pattern: + /observability|관측성|distributed\s*trac|분산\s*추적|SLI|SLO|error\s*budget|OpenTelemetry|otel|Prometheus|Grafana|Jaeger|Zipkin|log\s*aggregat|로그\s*수집|alerting\s*strateg|알림\s*전략|메트릭\s*수집|metric\s*collect|tracing\s*infra|monitoring|대시보드|dashboard|logs?\s*manag/i, + specialist: 'observability-specialist', + }, + { + pattern: + /event[- ]?driven|이벤트\s*기반|message\s*queue|메시지\s*큐|Kafka|RabbitMQ|SQS|Azure\s*Service\s*Bus|event\s*sourc|CQRS|saga\s*pattern|분산\s*트랜잭션|distributed\s*transaction|pub\/?sub|dead\s*letter|DLQ|websocket|SSE|server[- ]?sent|real[- ]?time|실시간|async\s*messag|비동기\s*통신/i, + specialist: 'event-architecture-specialist', + }, + { + pattern: + /migration|마이그레이션|migrate|이전|legacy\s*(system|code|moderniz)|레거시|upgrade\s*(framework|version|library)|업그레이드|strangler\s*fig|branch\s*by\s*abstraction|blue[- ]?green|canary\s*(deploy|release)|rollback|롤백|api\s*version|deprecat|dual[- ]?write|backward\s*compatib|호환성|zero[- ]?downtime|data\s*migration|데이터\s*마이그레이션|schema\s*migration|스키마\s*변경|cutover|전환/i, + specialist: 'migration-specialist', + }, +]; + +/** Map specialist IDs to domain names */ +const SPECIALIST_DOMAIN_MAP: Record = { + 'security-specialist': 'security', + 'accessibility-specialist': 'accessibility', + 'performance-specialist': 'performance', + 'i18n-specialist': 'i18n', + 'seo-specialist': 'seo', + 'documentation-specialist': 'documentation', + 'ui-ux-designer': 'ui-ux', + 'integration-specialist': 'integration', + 'observability-specialist': 'observability', + 'event-architecture-specialist': 'event-architecture', + 'migration-specialist': 'migration', + 'architecture-specialist': 'architecture', + 'test-strategy-specialist': 'testing', + 'code-quality-specialist': 'code-quality', +}; + +/** Minimal shape of keyword-modes.json for specialist extraction */ +interface ModeConfigSlice { + defaultSpecialists?: string[]; +} + +/** + * One-shot entry point for collective intelligence workflow. + * + * Combines rule loading, primary agent resolution, specialist recommendation, + * and prompt generation in a single call — replacing the multi-step + * parse_mode + dispatch_agents ceremony with ~70% token reduction. + * + * Designed to work with Claude native Teams for real-time specialist debate. + */ +@Injectable() +export class ActivateHandler extends AbstractHandler { + private readonly logger = new Logger(ActivateHandler.name); + + constructor( + @Inject(KEYWORD_SERVICE) private readonly keywordService: KeywordService, + private readonly agentService: AgentService, + private readonly rulesService: RulesService, + private readonly teamsCapability: TeamsCapabilityService, + ) { + super(); + } + + protected getHandledTools(): string[] { + return ['activate']; + } + + protected async handleTool( + toolName: string, + args: Record | undefined, + ): Promise { + switch (toolName) { + case 'activate': + return this.handleActivate(args); + default: + return createErrorResponse(`Unknown tool: ${toolName}`); + } + } + + getToolDefinitions(): ToolDefinition[] { + return [ + { + name: 'activate', + description: + 'One-shot entry point for collective intelligence workflow. ' + + 'Combines rule loading, primary agent resolution, specialist recommendation, ' + + 'and prompt generation in a single call — replacing the multi-step ' + + 'parse_mode + dispatch_agents ceremony. Returns everything needed to ' + + 'run a specialist council via Claude native Teams.', + inputSchema: { + type: 'object', + properties: { + prompt: { + type: 'string', + description: + 'Task description. May start with a mode keyword (PLAN/ACT/EVAL/AUTO) ' + + 'which will be auto-detected, or use the explicit mode parameter.', + }, + mode: { + type: 'string', + enum: ['PLAN', 'ACT', 'EVAL', 'AUTO'], + description: + 'Explicit workflow mode. If omitted, auto-detected from prompt keywords.', + }, + primaryAgent: { + type: 'string', + description: 'Explicit primary agent name. If omitted, uses mode default.', + }, + }, + required: ['prompt'], + }, + }, + ]; + } + + private async handleActivate(args: Record | undefined): Promise { + const prompt = extractRequiredString(args, 'prompt'); + if (prompt === null) { + return createErrorResponse('Missing required parameter: prompt'); + } + + // 1. Resolve mode from explicit param or prompt keyword + const explicitMode = extractOptionalString(args, 'mode'); + const { mode, taskPrompt } = this.resolveMode(prompt, explicitMode); + + // 2. Load rules for mode (reuses KeywordService caching) + const rules = await this.keywordService.getRulesForMode(mode, 'standard'); + + // 3. Resolve primary agent + const primaryAgentName = + extractOptionalString(args, 'primaryAgent') ?? MODE_DEFAULT_AGENTS[mode]; + let primaryAgent: { + name: string; + displayName: string; + systemPrompt: string; + } | null = null; + try { + const agentPrompt = await this.agentService.getAgentSystemPrompt(primaryAgentName, { + mode, + taskDescription: taskPrompt, + }); + primaryAgent = { + name: primaryAgentName, + displayName: agentPrompt.displayName, + systemPrompt: agentPrompt.systemPrompt, + }; + } catch (error) { + this.logger.warn( + `Failed to resolve primary agent '${primaryAgentName}': ${error instanceof Error ? error.message : 'Unknown'}`, + ); + } + + // 4. Determine specialists (mode defaults + context-aware patterns) + const specialists = await this.resolveSpecialists(mode, taskPrompt); + + // 5. Prepare specialist prompts in parallel (full verbosity for Teams usage) + const specialistResults: Array<{ + name: string; + displayName: string; + prompt: string; + domain: string; + }> = []; + if (specialists.length > 0) { + try { + const prepared = await this.agentService.prepareParallelAgents( + mode, + specialists, + undefined, + taskPrompt, + 'full', + ); + for (const agent of prepared.agents) { + specialistResults.push({ + name: agent.id, + displayName: agent.displayName, + prompt: agent.taskPrompt ?? agent.description ?? '', + domain: SPECIALIST_DOMAIN_MAP[agent.id] ?? agent.id, + }); + } + } catch (error) { + this.logger.warn( + `Failed to prepare specialists: ${error instanceof Error ? error.message : 'Unknown'}`, + ); + } + } + + // 6. Check Teams capability + const teamsStatus = await this.teamsCapability.getStatus(); + + // 7. Build lean response + return createJsonResponse({ + mode, + rules: rules.map(r => ({ name: r.name, content: r.content })), + primaryAgent, + specialists: specialistResults, + discussion: { + format: 'Each specialist: approve|concern|reject + reasoning + suggestedChanges', + consensus: 'No rejections = consensus reached', + crossReview: "Specialists review each other's opinions", + }, + nativeIntegration: { + teams: teamsStatus.available + ? 'Use Claude native Teams to run specialists as teammates for real-time debate' + : 'Enable Teams via CODINGBUDDY_TEAMS_ENABLED=true or experimental.teamsCoordination config', + memory: 'Use Claude Code Memory for context persistence across sessions', + orchestration: 'Host manages mode transitions, clarification, permissions natively', + }, + }); + } + + /** + * Resolve mode from explicit parameter or prompt keyword detection. + * Supports English and localized keywords (Korean, Japanese, Chinese, Spanish). + */ + private resolveMode( + prompt: string, + explicitMode: string | undefined, + ): { mode: Mode; taskPrompt: string } { + if (explicitMode && isValidMode(explicitMode)) { + return { mode: explicitMode as Mode, taskPrompt: prompt }; + } + + const trimmed = prompt.trim(); + const keywordRegex = /^([^\s::]+)\s*[::]?\s*(.*)$/s; + const match = trimmed.match(keywordRegex); + + if (match?.[1]) { + const candidate = match[1]; + const upper = candidate.toUpperCase(); + const rest = (match[2] ?? '').trim(); + + if (KEYWORDS.includes(upper as Mode)) { + return { mode: upper as Mode, taskPrompt: rest || trimmed }; + } + + const localized = LOCALIZED_KEYWORD_MAP[candidate] ?? LOCALIZED_KEYWORD_MAP[upper]; + if (localized) { + return { mode: localized, taskPrompt: rest || trimmed }; + } + } + + return { mode: 'PLAN', taskPrompt: trimmed }; + } + + /** + * Merge mode-default specialists (from keyword-modes.json) with + * context-aware specialist detection (from prompt patterns). + */ + private async resolveSpecialists(mode: Mode, prompt: string): Promise { + let defaultSpecialists: string[] = []; + try { + const configContent = await this.rulesService.getRuleContent('keyword-modes.json'); + const config = JSON.parse(configContent) as { modes: Record }; + defaultSpecialists = config.modes[mode]?.defaultSpecialists ?? []; + } catch { + this.logger.debug('Failed to load keyword-modes.json for default specialists'); + } + + const contextSpecialists = this.getContextAwareSpecialists(prompt); + return [...new Set([...defaultSpecialists, ...contextSpecialists])]; + } + + /** + * Detect additional specialists based on prompt content patterns. + */ + private getContextAwareSpecialists(prompt: string): string[] { + const specialists: string[] = []; + for (const { pattern, specialist } of CONTEXT_SPECIALIST_PATTERNS) { + if (pattern.test(prompt)) { + specialists.push(specialist); + } + } + return specialists; + } +} diff --git a/apps/mcp-server/src/mcp/handlers/briefing.handler.ts b/apps/mcp-server/src/mcp/handlers/briefing.handler.ts index c88421fb..725b186c 100644 --- a/apps/mcp-server/src/mcp/handlers/briefing.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/briefing.handler.ts @@ -52,7 +52,7 @@ export class BriefingHandler extends AbstractHandler { { name: 'create_briefing', description: - 'Capture current session state into a briefing document for cross-session recovery', + '[DEPRECATED — Use Claude Code Memory for cross-session context] Capture current session state into a briefing document for cross-session recovery.', inputSchema: { type: 'object', properties: { diff --git a/apps/mcp-server/src/mcp/handlers/checklist-context.handler.ts b/apps/mcp-server/src/mcp/handlers/checklist-context.handler.ts index 82fb3979..0a31d7a4 100644 --- a/apps/mcp-server/src/mcp/handlers/checklist-context.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/checklist-context.handler.ts @@ -99,7 +99,7 @@ export class ChecklistContextHandler extends AbstractHandler { { name: 'analyze_task', description: - 'Analyze a task to provide contextual recommendations including risk assessment, relevant checklists, specialist recommendations, and workflow suggestions. Use this at the start of PLAN mode to get comprehensive task analysis.', + '[DEPRECATED — Use activate + /dream instead] Analyze a task to provide contextual recommendations including risk assessment, relevant checklists, specialist recommendations, and workflow suggestions.', inputSchema: { type: 'object', properties: { diff --git a/apps/mcp-server/src/mcp/handlers/context-document.handler.ts b/apps/mcp-server/src/mcp/handlers/context-document.handler.ts index b403de62..22f255b2 100644 --- a/apps/mcp-server/src/mcp/handlers/context-document.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/context-document.handler.ts @@ -58,7 +58,7 @@ export class ContextDocumentHandler extends AbstractHandler { return [ { name: 'read_context', - description: `Read the current context document from ${CONTEXT_FILE_PATH}. Returns all accumulated context from PLAN/ACT/EVAL modes including decisions, notes, and recommended agents. Supports verbosity levels for controlling returned data size.`, + description: `[DEPRECATED — Use Claude Code Memory instead for cross-session context persistence] Read the current context document from ${CONTEXT_FILE_PATH}. Returns all accumulated context from PLAN/ACT/EVAL modes including decisions, notes, and recommended agents. Supports verbosity levels for controlling returned data size.`, inputSchema: { type: 'object', properties: { @@ -79,7 +79,7 @@ export class ContextDocumentHandler extends AbstractHandler { }, { name: 'update_context', - description: `MANDATORY: Update the context document at ${CONTEXT_FILE_PATH}. + description: `[DEPRECATED — Use Claude Code Memory instead for cross-session context persistence] Update the context document at ${CONTEXT_FILE_PATH}. - PLAN mode: Resets (clears) existing content and starts fresh. - ACT/EVAL modes: Appends new section to existing content (requires PLAN first). - Automatic cleanup: If document exceeds size threshold, older sections are automatically summarized. diff --git a/apps/mcp-server/src/mcp/handlers/discussion.handler.spec.ts b/apps/mcp-server/src/mcp/handlers/discussion.handler.spec.ts index 7501ec15..ec478b3e 100644 --- a/apps/mcp-server/src/mcp/handlers/discussion.handler.spec.ts +++ b/apps/mcp-server/src/mcp/handlers/discussion.handler.spec.ts @@ -112,9 +112,8 @@ describe('DiscussionHandler', () => { const data = JSON.parse(result!.content[0].text) as ExperimentalDiscussionResult; expect(data.experimental).toBe(true); expect(data.warning).toBe(EXPERIMENTAL_DISCUSSION_WARNING); - expect(data.warning).toContain('experimental'); - expect(data.warning).toContain('templated synthesis'); - expect(data.warning).toContain('not real specialist execution'); + expect(data.warning).toContain('synthesis'); + expect(data.warning).toContain('activate'); }); it('should return error for missing topic', async () => { @@ -300,12 +299,12 @@ describe('DiscussionHandler', () => { expect(definitions[0].name).toBe('agent_discussion'); }); - it('should mark tool description as experimental', () => { + it('should describe collective intelligence and activation guidance', () => { const definitions = handler.getToolDefinitions(); const description = definitions[0].description.toLowerCase(); - expect(description).toContain('experimental'); - expect(description).toContain('templated synthesis'); + expect(description).toContain('specialist'); + expect(description).toContain('activate'); expect(description).toContain(EXPERIMENTAL_DISCUSSION_ENV.toLowerCase()); }); diff --git a/apps/mcp-server/src/mcp/handlers/discussion.handler.ts b/apps/mcp-server/src/mcp/handlers/discussion.handler.ts index e39405ba..481ccb9d 100644 --- a/apps/mcp-server/src/mcp/handlers/discussion.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/discussion.handler.ts @@ -44,16 +44,13 @@ const SEVERITY_ORDER: readonly OpinionSeverity[] = ['info', 'low', 'medium', 'hi /** * Handler for the agent_discussion tool. * - * EXPERIMENTAL — disabled by default. Current output is templated synthesis of - * specialist opinions rather than real specialist execution, so it does NOT - * represent collective intelligence from live agents. The tool returns a - * {@link DisabledDiscussionResult} unless the caller explicitly opts in via - * `CODINGBUDDY_EXPERIMENTAL_DISCUSSION=1`, in which case it returns an - * {@link ExperimentalDiscussionResult} that carries a warning banner so - * consumers cannot mistake the output for a real multi-agent debate. + * Produces structured specialist opinions for collective intelligence workflows. + * In Claude Code environments with native Teams support, use the `activate` tool + * to run real specialist agents as teammates for live debate. This tool provides + * a structured discussion format that can seed or complement Teams-based review. * - * A follow-up effort should rebuild this on top of real specialist dispatch; - * until then callers should prefer `dispatch_agents` for actual expert input. + * When `CODINGBUDDY_EXPERIMENTAL_DISCUSSION=1` is NOT set, the tool returns + * guidance on using `activate` + Claude native Teams for real specialist execution. */ @Injectable() export class DiscussionHandler extends AbstractHandler { @@ -82,12 +79,11 @@ export class DiscussionHandler extends AbstractHandler { { name: 'agent_discussion', description: - '[EXPERIMENTAL — disabled by default] Produces templated synthesis of ' + - 'specialist opinions, not real specialist execution. The output does ' + - 'not reflect collective intelligence from live agents; treat it as a ' + - 'placeholder until the rebuild lands. Enable by setting ' + - `${EXPERIMENTAL_DISCUSSION_ENV}=1. When disabled, the tool returns ` + - '{disabled: true} without invoking synthesis.', + 'Structured specialist discussion for collective intelligence workflows. ' + + 'Collects and synthesizes opinions from multiple specialist agents on a topic. ' + + 'For real-time specialist debate, use the `activate` tool with Claude native Teams. ' + + `Set ${EXPERIMENTAL_DISCUSSION_ENV}=1 to enable built-in synthesis. ` + + 'When unset, returns guidance on using activate + Teams instead.', inputSchema: { type: 'object', properties: { diff --git a/apps/mcp-server/src/mcp/handlers/discussion.types.ts b/apps/mcp-server/src/mcp/handlers/discussion.types.ts index 59539f69..fed7f2c1 100644 --- a/apps/mcp-server/src/mcp/handlers/discussion.types.ts +++ b/apps/mcp-server/src/mcp/handlers/discussion.types.ts @@ -72,17 +72,20 @@ export const VALID_SEVERITIES: readonly OpinionSeverity[] = [ export const EXPERIMENTAL_DISCUSSION_ENV = 'CODINGBUDDY_EXPERIMENTAL_DISCUSSION'; /** - * Warning banner attached to any enabled agent_discussion response so callers - * cannot mistake templated synthesis for real specialist execution. + * Warning banner attached to any enabled agent_discussion response. + * Indicates that results are from built-in synthesis, not live agent execution. + * For real specialist debate, use activate + Claude native Teams. */ export const EXPERIMENTAL_DISCUSSION_WARNING = - '\u26a0\ufe0f experimental — templated synthesis, not real specialist execution'; + 'Built-in synthesis — for real specialist debate, use activate + Claude native Teams'; /** * Reason surfaced when the tool is disabled (default state). + * Guides users toward activate + Claude native Teams for real specialist execution. */ export const DISABLED_DISCUSSION_REASON = - 'templated synthesis not aligned with collective intelligence promise'; + 'Use the `activate` tool with Claude native Teams for real specialist debate. ' + + 'Set CODINGBUDDY_EXPERIMENTAL_DISCUSSION=1 to enable built-in synthesis as an alternative.'; /** * Response returned when the agent_discussion tool is disabled (default). diff --git a/apps/mcp-server/src/mcp/handlers/index.ts b/apps/mcp-server/src/mcp/handlers/index.ts index 03ce799c..acef0cb0 100644 --- a/apps/mcp-server/src/mcp/handlers/index.ts +++ b/apps/mcp-server/src/mcp/handlers/index.ts @@ -168,6 +168,12 @@ export { RuleImpactHandler } from './rule-impact.handler'; */ export { ReviewPrHandler } from './review-pr.handler'; +/** + * Handler for activate tool (one-shot collective intelligence entry point) + * @see {@link ActivateHandler} + */ +export { ActivateHandler } from './activate.handler'; + /** * Injection token for the array of all tool handlers. * diff --git a/apps/mcp-server/src/mcp/handlers/resume.handler.ts b/apps/mcp-server/src/mcp/handlers/resume.handler.ts index eff41da1..af6fff49 100644 --- a/apps/mcp-server/src/mcp/handlers/resume.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/resume.handler.ts @@ -47,7 +47,8 @@ export class ResumeHandler extends AbstractHandler { return [ { name: 'resume_session', - description: 'Load a previous session briefing to restore context and continue work', + description: + '[DEPRECATED — Use Claude Code Memory for session resume] Load a previous session briefing to restore context and continue work.', inputSchema: { type: 'object', properties: { diff --git a/apps/mcp-server/src/mcp/mcp.module.ts b/apps/mcp-server/src/mcp/mcp.module.ts index 3665f89a..553e5c92 100644 --- a/apps/mcp-server/src/mcp/mcp.module.ts +++ b/apps/mcp-server/src/mcp/mcp.module.ts @@ -43,6 +43,7 @@ import { BriefingHandler, ResumeHandler, RuleImpactHandler, + ActivateHandler, } from './handlers'; const handlers = [ @@ -68,6 +69,7 @@ const handlers = [ BriefingHandler, ResumeHandler, RuleImpactHandler, + ActivateHandler, ]; @Module({ diff --git a/packages/rules/.ai-rules/adapters/claude-code.md b/packages/rules/.ai-rules/adapters/claude-code.md index 4608eb45..441947c9 100644 --- a/packages/rules/.ai-rules/adapters/claude-code.md +++ b/packages/rules/.ai-rules/adapters/claude-code.md @@ -6,6 +6,52 @@ This guide explains how to use the common AI rules (`.ai-rules/`) in Claude Code Claude Code uses the `.claude/` directory for project-specific custom instructions, referencing the common rules from `.ai-rules/`. +## Collective Intelligence with `activate` + Claude Native Teams + +### The `activate` Tool (Recommended Entry Point) + +The `activate` MCP tool is the **one-shot entry point** for collective intelligence workflows in Claude Code. It replaces the multi-step `parse_mode` + `dispatch_agents` ceremony with a single call. + +``` +activate({ prompt: "design auth feature", mode: "PLAN" }) +``` + +**Returns:** +- `mode` — resolved workflow mode +- `rules` — loaded rules for the mode +- `primaryAgent` — agent name + full system prompt +- `specialists` — recommended specialists with full prompts +- `discussion` — format guide for approve/concern/reject consensus +- `nativeIntegration` — guidance for Teams, Memory, orchestration + +### Running a Specialist Council via Claude Native Teams + +When `activate` returns specialists, use Claude native Teams for real-time debate: + +``` +1. activate({ prompt }) → get specialists +2. TeamCreate({ team_name: "plan-council" }) +3. For each specialist: Agent({ team_name, name: specialist.name, prompt: specialist.prompt }) +4. Specialists analyze independently → cross-review → consensus +5. Collect findings → summarize to user +``` + +### Claude Code Native Feature Mapping + +Use Claude Code native features instead of legacy codingbuddy tools: + +| Need | Use This | Instead of | +|------|----------|------------| +| Workflow entry | `activate` | `parse_mode` + `dispatch_agents` | +| Cross-session context | Claude Code Memory | `update_context` / `create_briefing` / `resume_session` | +| Specialist execution | Claude native Teams | subagent dispatch | +| Task exploration | `/dream` | `analyze_task` | +| Planning approval | `EnterPlanMode` | planning stage routing | +| Repeated execution | `/loop` | AUTO mode repetition | +| Clarification | `AskUserQuestion` | clarification gate | + +> **Note**: `parse_mode` remains available for non-Claude Code hosts (Cursor, Codex, etc.). + ## 🆕 Code Conventions Support CodingBuddy now automatically enforces project code conventions from config files: @@ -325,9 +371,13 @@ AI assistants should display the `activation_message.formatted` field at the sta CodingBuddy supports parallel execution of multiple specialist agents for comprehensive analysis. -### When to Use Parallel Execution +### Recommended: Claude Native Teams (Primary Strategy) + +In Claude Code environments, use **Claude native Teams** as the primary execution strategy for specialist councils. The `activate` tool returns specialist prompts ready for Teams execution. See the "Collective Intelligence with `activate`" section above for the full workflow. + +### Legacy: SubAgent / TaskMaestro Strategies -Parallel execution is recommended when `parse_mode` returns a `parallelAgentsRecommendation` field: +For non-Claude Code hosts or when Teams is not available, parallel execution is recommended when `parse_mode` returns a `parallelAgentsRecommendation` field: | Mode | Default Specialists | Use Case | |------|---------------------|----------|