按 critic + 3 reviewer agent 反馈:
- HIGH: bare catch {} 改 catch(e){console.error(...)} 暴露 bug
- MED: regex 去重复 (develop|develops? → develops?, fix|fixes → fixes?, requirement|requirements? → requirements?)
- MED: 4 处 {continue:true,suppressOutput:true} 抽 NOOP 常量
- MED: test JSON.stringify(.sort()) → 不可变的 sortKey helper
- skip: createHookOutput 抽 lib (违反"不改 OMC keyword-detector" 决策)
- skip: ROLES enum (3 值 over-engineering)
post-deslop 回归: 10/10 PASS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
88 lines
3.4 KiB
JavaScript
Executable File
88 lines
3.4 KiB
JavaScript
Executable File
#!/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: `<role-routing role="dev">
|
||
检测到**开发角色**关键词。本回合优先路由:
|
||
- 主 agent: executor (sonnet/opus by 复杂度) / debugger / code-simplifier
|
||
- 中文角色补强: engineering-backend-architect / engineering-frontend-architect / engineering-ai-engineer
|
||
- 工作流: 默认开 TDD(先写失败测试再实现),用 git-master 处理分支
|
||
</role-routing>`,
|
||
test: `<role-routing role="test">
|
||
检测到**测试角色**关键词。本回合优先路由:
|
||
- 主 agent: qa-tester / test-engineer / verifier
|
||
- 中文角色补强: testing-* 系列(中文 agent 库)
|
||
- 工作流: 测试先行;产出 PRD acceptance criteria 跟测试断言一一对应;要 evidence-based 验收
|
||
</role-routing>`,
|
||
pm: `<role-routing role="pm">
|
||
检测到**PM 角色**关键词。本回合优先路由:
|
||
- 主 agent: planner / analyst / scientist
|
||
- 中文角色补强: product-* / project-management-* 系列(中文 agent 库)
|
||
- 工作流: 先 deep-interview 拆需求,再 ralplan 起 PRD,明确 user stories + acceptance criteria 后才落代码
|
||
</role-routing>`,
|
||
};
|
||
|
||
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();
|