From f28be9d71f4eed97de3980e3f44a839c4f6571f9 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Thu, 19 Mar 2026 18:50:56 +0900 Subject: [PATCH 1/3] fix(release): add .mcp.json to bump-version and verify-release scripts - Add conditional .mcp.json update to bump-version.sh (step 6) - Add conditional .mcp.json version check to verify-release-versions.sh - Both handle missing .mcp.json gracefully (gitignored, local only) Closes #705 --- scripts/bump-version.sh | 14 ++++++++++++++ scripts/verify-release-versions.sh | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 6b5c41b..bb850da 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -81,6 +81,20 @@ node -e " " echo " ✅ packages/claude-code-plugin/.claude-plugin/plugin.json" +# 6. .mcp.json (if exists — gitignored, local only) +if [ -f ".mcp.json" ]; then + node -e " + const fs = require('fs'); + const p = '.mcp.json'; + const content = fs.readFileSync(p, 'utf-8'); + const updated = content.replace(/codingbuddy@[0-9]+\.[0-9]+\.[0-9]+/, 'codingbuddy@$NEW_VERSION'); + fs.writeFileSync(p, updated); + " + echo " ✅ .mcp.json" +else + echo " ⏭️ .mcp.json (not found, skipped)" +fi + echo "" echo "✅ All files bumped to v$NEW_VERSION" echo " Next: git commit -am \"chore(release): prepare v$NEW_VERSION\"" diff --git a/scripts/verify-release-versions.sh b/scripts/verify-release-versions.sh index a6ecb26..a923456 100755 --- a/scripts/verify-release-versions.sh +++ b/scripts/verify-release-versions.sh @@ -62,6 +62,23 @@ for package_info in "${PACKAGES[@]}"; do fi done +# Check .mcp.json (gitignored, local only) +if [ -f ".mcp.json" ]; then + mcp_version=$(node -e " + const content = require('fs').readFileSync('.mcp.json', 'utf-8'); + const match = content.match(/codingbuddy@([0-9]+\.[0-9]+\.[0-9]+)/); + console.log(match ? match[1] : ''); + ") + if [ "$mcp_version" = "$TAG_VERSION" ]; then + echo "✅ .mcp.json codingbuddy: @$mcp_version (matches tag)" + else + echo "❌ .mcp.json codingbuddy: @$mcp_version (tag is v$TAG_VERSION)" + ALL_MATCH=false + fi +else + echo "⏭️ .mcp.json (not found, skipped)" +fi + echo "" if [ "$ALL_MATCH" = true ]; then From 11edc4389ffc9fd65768c13eae8398b60f59fbf4 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Thu, 19 Mar 2026 19:07:28 +0900 Subject: [PATCH 2/3] docs: add taskmaestro execution strategy to adapter and instructions - Add Execution Strategy Selection subsection to claude-code.md - Add TaskMaestro Execution Flow with response format documentation - Add Strategy Selection checklist to custom-instructions.md - Add dual strategy support note to Mode-specific Specialists table Closes #711 --- .claude/rules/custom-instructions.md | 9 +++++ .../rules/.ai-rules/adapters/claude-code.md | 39 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/.claude/rules/custom-instructions.md b/.claude/rules/custom-instructions.md index 510ea56..7189ff6 100644 --- a/.claude/rules/custom-instructions.md +++ b/.claude/rules/custom-instructions.md @@ -120,6 +120,12 @@ Examples: **When `parse_mode` returns `dispatchReady`, use it directly with the Task tool — no extra calls needed.** +**Strategy Selection (before dispatch):** +- [ ] Check `availableStrategies` in `parse_mode` response +- [ ] If `["subagent", "taskmaestro"]` → AskUserQuestion to choose +- [ ] If `taskmaestroInstallHint` present and user wants taskmaestro → guide installation +- [ ] Pass chosen strategy to `dispatch_agents(executionStrategy: ...)` + **Quick Checklist (Auto-Dispatch - Preferred):** - [ ] Check `dispatchReady` in `parse_mode` response - [ ] Use `dispatchReady.primaryAgent.dispatchParams` with Task tool @@ -143,6 +149,9 @@ Examples: | **EVAL** | 🔒 security, ♿ accessibility, ⚡ performance, 📏 code-quality, 📨 event-architecture, 🔗 integration, 📊 observability, 🔄 migration | | **AUTO** | 🏛️ architecture, 🧪 test-strategy, 🔒 security, 📏 code-quality, 📨 event-architecture, 🔗 integration, 📊 observability, 🔄 migration | +> **Note:** All modes support both SubAgent and TaskMaestro execution strategies. +> The strategy is selected per-invocation via user choice. + **📖 Full Guide:** [Parallel Specialist Agents Execution](../../packages/rules/.ai-rules/adapters/claude-code.md#parallel-specialist-agents-execution) diff --git a/packages/rules/.ai-rules/adapters/claude-code.md b/packages/rules/.ai-rules/adapters/claude-code.md index cb3a1e0..2679d00 100644 --- a/packages/rules/.ai-rules/adapters/claude-code.md +++ b/packages/rules/.ai-rules/adapters/claude-code.md @@ -523,6 +523,45 @@ Each workflow mode activates different specialist agents: **Important:** Specialists from one mode do NOT carry over to the next mode. Each mode has its own recommended specialist set. +### Execution Strategy Selection (MANDATORY) + +When `parse_mode` returns `availableStrategies`: + +1. **Check `availableStrategies`** in the response +2. **If both strategies available** (`["subagent", "taskmaestro"]`), ask user with AskUserQuestion: + - Option A: "SubAgent (background agents, fast)" (Recommended) + - Option B: "TaskMaestro (tmux parallel panes, visual monitoring)" +3. **If only `["subagent"]`** and `taskmaestroInstallHint` present: + - Ask: "TaskMaestro is not installed. Would you like to install it for tmux-based parallel execution?" + - Yes → invoke `/taskmaestro` skill to guide installation, then re-check + - No → proceed with subagent +4. **Call `dispatch_agents`** with chosen `executionStrategy` parameter: + - `dispatch_agents({ mode, specialists, executionStrategy: "subagent" })` — existing Agent tool flow + - `dispatch_agents({ mode, specialists, executionStrategy: "taskmaestro" })` — returns tmux assignments +5. **Execute** based on strategy: + - **subagent**: Use `dispatchParams` with Agent tool (`run_in_background: true`) + - **taskmaestro**: Follow `executionHint` — start panes, assign prompts, monitor, collect results + +### TaskMaestro Execution Flow + +When `executionStrategy: "taskmaestro"` is chosen, `dispatch_agents` returns: + +```json +{ + "taskmaestro": { + "sessionName": "eval-specialists", + "paneCount": 5, + "assignments": [ + { "name": "security-specialist", "displayName": "Security Specialist", "prompt": "..." }, + { "name": "performance-specialist", "displayName": "Performance Specialist", "prompt": "..." } + ] + }, + "executionHint": "1. /taskmaestro start --panes 5\n2. ..." +} +``` + +Execute by following the `executionHint` commands sequentially. + ## PR All-in-One Skill Unified commit and PR workflow that: From 2619ec2e44e55303d2def230bce3a53ade98b7be Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Fri, 20 Mar 2026 11:38:51 +0900 Subject: [PATCH 3/3] test(mcp): add integration tests for subagent vs taskmaestro dispatch strategies - Add ExecutionStrategy type, TaskMaestroAssignment, TaskMaestroResult interfaces - Implement strategy branching in dispatchAgents (subagent vs taskmaestro) - Add executionStrategy to dispatch_agents handler inputSchema - Add 6 integration tests: subagent, taskmaestro, backward compat, sessionName, prompt format, executionHint Closes #712 --- .../src/agent/agent.service.spec.ts | 110 ++++++++++++++++++ apps/mcp-server/src/agent/agent.service.ts | 55 +++++++-- apps/mcp-server/src/agent/agent.types.ts | 18 +++ .../src/mcp/handlers/agent.handler.ts | 11 ++ 4 files changed, 185 insertions(+), 9 deletions(-) diff --git a/apps/mcp-server/src/agent/agent.service.spec.ts b/apps/mcp-server/src/agent/agent.service.spec.ts index 6e49ed8..ad72f8e 100644 --- a/apps/mcp-server/src/agent/agent.service.spec.ts +++ b/apps/mcp-server/src/agent/agent.service.spec.ts @@ -578,4 +578,114 @@ describe('AgentService', () => { expect(result.parallelAgents).toHaveLength(1); }); }); + + describe('execution strategy integration', () => { + it('subagent strategy returns parallelAgents with dispatchParams and run_in_background', async () => { + vi.mocked(mockRulesService.getAgent!) + .mockResolvedValueOnce(mockSecurityAgent) + .mockResolvedValueOnce(mockAccessibilityAgent); + + const result = await service.dispatchAgents({ + mode: 'EVAL', + specialists: ['security-specialist', 'accessibility-specialist'], + executionStrategy: 'subagent', + includeParallel: true, + }); + + expect(result.executionStrategy).toBe('subagent'); + expect(result.parallelAgents).toBeDefined(); + expect(result.parallelAgents!.length).toBe(2); + result.parallelAgents!.forEach(agent => { + expect(agent.dispatchParams).toBeDefined(); + expect(agent.dispatchParams.subagent_type).toBe('general-purpose'); + expect(agent.dispatchParams.run_in_background).toBe(true); + expect(agent.dispatchParams.prompt).toBeTruthy(); + }); + expect(result.taskmaestro).toBeUndefined(); + }); + + it('taskmaestro strategy returns assignments without dispatchParams', async () => { + vi.mocked(mockRulesService.getAgent!) + .mockResolvedValueOnce(mockSecurityAgent) + .mockResolvedValueOnce(mockAccessibilityAgent); + + const result = await service.dispatchAgents({ + mode: 'EVAL', + specialists: ['security-specialist', 'accessibility-specialist'], + executionStrategy: 'taskmaestro', + }); + + expect(result.executionStrategy).toBe('taskmaestro'); + expect(result.taskmaestro).toBeDefined(); + expect(result.taskmaestro!.paneCount).toBe(2); + expect(result.taskmaestro!.assignments).toHaveLength(2); + result.taskmaestro!.assignments.forEach(assignment => { + expect(assignment.name).toBeTruthy(); + expect(assignment.displayName).toBeTruthy(); + expect(assignment.prompt).toBeTruthy(); + }); + expect(result.parallelAgents).toBeUndefined(); + }); + + it('backward compatibility: no executionStrategy defaults to subagent behavior', async () => { + vi.mocked(mockRulesService.getAgent!).mockResolvedValue(mockSecurityAgent); + + const result = await service.dispatchAgents({ + mode: 'EVAL', + specialists: ['security-specialist'], + includeParallel: true, + // NO executionStrategy — must default to subagent + }); + + expect(result.executionStrategy).toBe('subagent'); + expect(result.parallelAgents).toBeDefined(); + expect(result.taskmaestro).toBeUndefined(); + }); + + it('taskmaestro sessionName reflects the mode', async () => { + const modes = ['PLAN', 'ACT', 'EVAL', 'AUTO'] as const; + for (const mode of modes) { + vi.mocked(mockRulesService.getAgent!).mockResolvedValue(mockSecurityAgent); + + const result = await service.dispatchAgents({ + mode, + specialists: ['security-specialist'], + executionStrategy: 'taskmaestro', + }); + expect(result.taskmaestro!.sessionName).toBe(`${mode.toLowerCase()}-specialists`); + } + }); + + it('taskmaestro prompt includes Output Format instructions', async () => { + vi.mocked(mockRulesService.getAgent!).mockResolvedValue(mockSecurityAgent); + + const result = await service.dispatchAgents({ + mode: 'EVAL', + specialists: ['security-specialist'], + executionStrategy: 'taskmaestro', + }); + + const prompt = result.taskmaestro!.assignments[0].prompt; + expect(prompt).toContain('Severity: CRITICAL / HIGH / MEDIUM / LOW / INFO'); + expect(prompt).toContain('File reference'); + expect(prompt).toContain('Recommendation'); + }); + + it('taskmaestro executionHint includes all required commands', async () => { + vi.mocked(mockRulesService.getAgent!) + .mockResolvedValueOnce(mockSecurityAgent) + .mockResolvedValueOnce(mockAccessibilityAgent); + + const result = await service.dispatchAgents({ + mode: 'EVAL', + specialists: ['security-specialist', 'accessibility-specialist'], + executionStrategy: 'taskmaestro', + }); + + expect(result.executionHint).toContain('/taskmaestro start --panes 2'); + expect(result.executionHint).toContain('/taskmaestro assign'); + expect(result.executionHint).toContain('/taskmaestro status'); + expect(result.executionHint).toContain('/taskmaestro stop all'); + }); + }); }); diff --git a/apps/mcp-server/src/agent/agent.service.ts b/apps/mcp-server/src/agent/agent.service.ts index f9690db..4332bff 100644 --- a/apps/mcp-server/src/agent/agent.service.ts +++ b/apps/mcp-server/src/agent/agent.service.ts @@ -10,6 +10,8 @@ import type { DispatchAgentsInput, DispatchResult, DispatchedAgent, + ExecutionStrategy, + TaskMaestroAssignment, } from './agent.types'; import { FILE_PATTERN_SPECIALISTS } from './agent.types'; import { @@ -191,6 +193,7 @@ export class AgentService { * Claude Code's Task tool, eliminating the need for manual prompt assembly. */ async dispatchAgents(input: DispatchAgentsInput): Promise { + const strategy: ExecutionStrategy = input.executionStrategy || 'subagent'; const context: AgentContext = { mode: input.mode, targetFiles: input.targetFiles, @@ -198,11 +201,12 @@ export class AgentService { }; const result: DispatchResult = { + executionStrategy: strategy, executionHint: buildParallelExecutionHint(), }; - // Dispatch primary agent - if (input.primaryAgent) { + // Dispatch primary agent (subagent strategy only) + if (strategy === 'subagent' && input.primaryAgent) { try { const agentPrompt = await this.getAgentSystemPrompt(input.primaryAgent, context); result.primaryAgent = { @@ -222,14 +226,34 @@ export class AgentService { } } - // Dispatch parallel agents - if (input.includeParallel && input.specialists?.length) { + if (strategy === 'taskmaestro' && input.specialists?.length) { + // TaskMaestro strategy: return tmux assignments const uniqueSpecialists = Array.from(new Set(input.specialists)); - const { agents, failedAgents } = await this.loadAgents( - uniqueSpecialists, - context, - true, // always include full prompt for dispatch - ); + const { agents, failedAgents } = await this.loadAgents(uniqueSpecialists, context, true); + + const assignments: TaskMaestroAssignment[] = agents.map(agent => ({ + name: agent.id, + displayName: agent.displayName, + prompt: this.buildTaskMaestroPrompt( + agent.taskPrompt || `Perform ${agent.displayName} analysis in ${input.mode} mode`, + ), + })); + + const sessionName = `${input.mode.toLowerCase()}-specialists`; + result.taskmaestro = { + sessionName, + paneCount: assignments.length, + assignments, + }; + result.executionHint = this.buildTaskMaestroHint(sessionName, assignments.length); + + if (failedAgents.length > 0) { + result.failedAgents = failedAgents; + } + } else if (strategy === 'subagent' && input.includeParallel && input.specialists?.length) { + // SubAgent strategy: existing behavior + const uniqueSpecialists = Array.from(new Set(input.specialists)); + const { agents, failedAgents } = await this.loadAgents(uniqueSpecialists, context, true); result.parallelAgents = agents.map( (agent): DispatchedAgent => ({ @@ -253,4 +277,17 @@ export class AgentService { return result; } + + private buildTaskMaestroPrompt(basePrompt: string): string { + return `${basePrompt}\n\n## Output Format\n\nFor each finding, include:\n- Severity: CRITICAL / HIGH / MEDIUM / LOW / INFO\n- File reference\n- Recommendation`; + } + + private buildTaskMaestroHint(sessionName: string, paneCount: number): string { + return [ + `1. /taskmaestro start --panes ${paneCount}`, + `2. /taskmaestro assign --session ${sessionName}`, + `3. /taskmaestro status`, + `4. /taskmaestro stop all`, + ].join('\n'); + } } diff --git a/apps/mcp-server/src/agent/agent.types.ts b/apps/mcp-server/src/agent/agent.types.ts index 58c943f..14bf1bc 100644 --- a/apps/mcp-server/src/agent/agent.types.ts +++ b/apps/mcp-server/src/agent/agent.types.ts @@ -73,13 +73,30 @@ export interface DispatchedAgent { dispatchParams: DispatchParams; } +export type ExecutionStrategy = 'subagent' | 'taskmaestro'; + +export interface TaskMaestroAssignment { + name: string; + displayName: string; + prompt: string; +} + +export interface TaskMaestroResult { + sessionName: string; + paneCount: number; + assignments: TaskMaestroAssignment[]; +} + /** * Result of dispatching agents for execution */ export interface DispatchResult { primaryAgent?: DispatchedAgent; parallelAgents?: DispatchedAgent[]; + executionStrategy: ExecutionStrategy; executionHint: string; + /** TaskMaestro-specific result (only when executionStrategy is 'taskmaestro') */ + taskmaestro?: TaskMaestroResult; /** Agents that failed to load */ failedAgents?: FailedAgent[]; } @@ -94,6 +111,7 @@ export interface DispatchAgentsInput { specialists?: string[]; includeParallel?: boolean; primaryAgent?: string; + executionStrategy?: ExecutionStrategy; } /** diff --git a/apps/mcp-server/src/mcp/handlers/agent.handler.ts b/apps/mcp-server/src/mcp/handlers/agent.handler.ts index 6062419..2618906 100644 --- a/apps/mcp-server/src/mcp/handlers/agent.handler.ts +++ b/apps/mcp-server/src/mcp/handlers/agent.handler.ts @@ -158,6 +158,12 @@ export class AgentHandler extends AbstractHandler { type: 'boolean', description: 'Whether to include parallel specialist agents (default: false)', }, + executionStrategy: { + type: 'string', + enum: ['subagent', 'taskmaestro'], + description: + 'Execution strategy for specialist agents (default: subagent). Use "taskmaestro" for tmux-based parallel execution.', + }, }, required: ['mode'], }, @@ -182,6 +188,10 @@ export class AgentHandler extends AbstractHandler { const targetFiles = extractStringArray(args, 'targetFiles'); const taskDescription = extractOptionalString(args, 'taskDescription'); const includeParallel = args?.includeParallel === true; + const executionStrategy = extractOptionalString(args, 'executionStrategy') as + | 'subagent' + | 'taskmaestro' + | undefined; try { const result = await this.agentService.dispatchAgents({ @@ -191,6 +201,7 @@ export class AgentHandler extends AbstractHandler { targetFiles, taskDescription, includeParallel, + executionStrategy, }); return createJsonResponse(result); } catch (error) {