#!/usr/bin/env node /** * Role Router Hook (UserPromptSubmit) * 按 user prompt 关键词检测当前角色(dev/test/pm),注入推荐 agent hint。 * * 三种角色: * dev → executor / debugger / code-simplifier + engineering-* 中文 agent * test → qa-tester / test-engineer / verifier + testing-* 中文 agent * pm → planner / analyst / scientist + product-* / project-management-* 中文 agent * * 输出: stdout JSON { hookSpecificOutput: { additionalContext, hookEventName } } * 不阻塞、不退出非零码(保证主流程继续)。 */ import { readFileSync } from 'fs'; export const DEV_RE = /(?:\b(?:dev|develops?|implements?|refactors?|fixes?|bugs?|hotfix|patch(?:es)?|impl)\b|开发|实现|重构|修(?:复|bug)|写代码|编码|添加功能|新功能|改(?:bug|代码))/i; export const TEST_RE = /(?:\b(?:tests?|qa|unit\s?tests?|regression|e2e|integration\s?tests?|specs?)\b|测试|单测|单元测试|回归(?:测试)?|集成测试|端到端|质量保证|QA)/i; export const PM_RE = /(?:\b(?:pm|product|spec|requirements?|roadmap|planning|backlog|user\s?story|prd)\b|产品(?:经理)?|需求|排期|规划|计划|调研|路线图|用户故事)/i; const NOOP = JSON.stringify({ continue: true, suppressOutput: true }); const ROLE_HINTS = { dev: ` 检测到**开发角色**关键词。本回合优先路由: - 主 agent: executor (sonnet/opus by 复杂度) / debugger / code-simplifier - 中文角色补强: engineering-backend-architect / engineering-frontend-architect / engineering-ai-engineer - 工作流: 默认开 TDD(先写失败测试再实现),用 git-master 处理分支 `, test: ` 检测到**测试角色**关键词。本回合优先路由: - 主 agent: qa-tester / test-engineer / verifier - 中文角色补强: testing-* 系列(中文 agent 库) - 工作流: 测试先行;产出 PRD acceptance criteria 跟测试断言一一对应;要 evidence-based 验收 `, pm: ` 检测到**PM 角色**关键词。本回合优先路由: - 主 agent: planner / analyst / scientist - 中文角色补强: product-* / project-management-* 系列(中文 agent 库) - 工作流: 先 deep-interview 拆需求,再 ralplan 起 PRD,明确 user stories + acceptance criteria 后才落代码 `, }; export function detectRoles(prompt) { const roles = []; if (DEV_RE.test(prompt)) roles.push('dev'); if (TEST_RE.test(prompt)) roles.push('test'); if (PM_RE.test(prompt)) roles.push('pm'); return roles; } export function buildContext(roles) { if (roles.length === 0) return null; return roles.map(r => ROLE_HINTS[r]).join('\n\n'); } function readStdinSync() { try { return readFileSync(0, 'utf8'); } catch { return ''; } } function main() { try { const raw = readStdinSync(); if (!raw) { console.log(NOOP); return; } const data = JSON.parse(raw); const prompt = data.prompt || data.user_prompt || ''; if (!prompt) { console.log(NOOP); return; } const ctx = buildContext(detectRoles(prompt)); if (!ctx) { console.log(NOOP); return; } console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: ctx, }, })); } catch (e) { console.error('[role-router]', e.message); console.log(NOOP); } } const isMain = import.meta.url === `file://${process.argv[1]}`; if (isMain) main();