From 8915fa2898d03b9495000ba9b76d14aede9af192 Mon Sep 17 00:00:00 2001 From: pp Date: Sun, 26 Apr 2026 21:36:34 +0800 Subject: [PATCH] =?UTF-8?q?config(claude):=20=E8=A7=92=E8=89=B2=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=20hook=20(dev/test/pm)=20+=20role-router=20=E5=8D=95?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - claude-config/hooks/role-router.mjs 按 user prompt 关键词检测当前角色,注入推荐 agent hint - dev: 开发/实现/重构/修(复|bug)/dev/implement/refactor/fix/feature → executor/debugger - test: 测试/单测/回归/QA/test/regression/spec → qa-tester/test-engineer/verifier - pm: 产品/需求/排期/规划/PM/product/PRD/roadmap → planner/analyst/scientist 支持多角色同时命中 - claude-config/hooks/role-router.test.mjs 10 fixture 单测 (含 subprocess pipe integration), 10/10 PASS - claude-config/settings.json.snapshot.20260426 全局 settings.json 快照, hooks.UserPromptSubmit 接 keyword-detector + role-router - claude-config/README.md 设计/安装/测试 文档 来源: ralph US-3, a+b 融合方案 (211 中文 agents + 关键词 hook) Co-Authored-By: Claude Opus 4.7 --- claude-config/README.md | 44 +++++++++ claude-config/hooks/role-router.mjs | 94 +++++++++++++++++++ claude-config/hooks/role-router.test.mjs | 66 +++++++++++++ claude-config/settings.json.snapshot.20260426 | 41 ++++++++ 4 files changed, 245 insertions(+) create mode 100644 claude-config/README.md create mode 100755 claude-config/hooks/role-router.mjs create mode 100755 claude-config/hooks/role-router.test.mjs create mode 100644 claude-config/settings.json.snapshot.20260426 diff --git a/claude-config/README.md b/claude-config/README.md new file mode 100644 index 0000000..b6fcdbc --- /dev/null +++ b/claude-config/README.md @@ -0,0 +1,44 @@ +# claude-config snapshot + +存放 ~/.claude/ 下的私人 hook + settings 快照,用于跨机器同步和版本回溯。 + +## 文件 + +- `settings.json.snapshot.20260426` — 全局 settings.json 快照(含 hooks 配置) +- `hooks/role-router.mjs` — 角色路由 hook(开发/测试/PM 自动注入推荐 agent) +- `hooks/role-router.test.mjs` — role-router 单元测试(10 fixture) + +## 角色路由设计 + +按 user prompt 关键词检测当前角色,注入推荐 agent hint: + +| 角色 | 关键词(含中英) | 推荐 agent | +|------|----------------|-----------| +| dev | 开发/实现/重构/修(复\|bug)/写代码/dev/implement/refactor/fix/feature | executor / debugger / code-simplifier + engineering-* 中文 agent | +| test | 测试/单测/回归/QA/test/regression/e2e/spec | qa-tester / test-engineer / verifier + testing-* | +| pm | 产品/需求/排期/规划/PM/product/spec/PRD/roadmap | planner / analyst / scientist + product-* / project-management-* | + +支持多角色同时命中(注入多段 hint)。 + +## 安装到新机器 + +```bash +cp claude-config/hooks/role-router.mjs ~/.claude/hooks/ +cp claude-config/hooks/role-router.test.mjs ~/.claude/hooks/ +chmod +x ~/.claude/hooks/role-router.mjs ~/.claude/hooks/role-router.test.mjs + +# 在 ~/.claude/settings.json 的 hooks.UserPromptSubmit 加入: +# { "type": "command", "command": "sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/find-node.sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/role-router.mjs" } +``` + +## 测试 + +```bash +cd ~/.claude/hooks && node role-router.test.mjs +# 期望 10 pass 0 fail +``` + +## 关联 + +- agency-agents-zh (211 中文 agents) 装到 ~/.claude/agents/,跟 OMC 19 个原生 agent 共存(共 230) +- 项目内有 hook 时优先项目 hook;这里的是全局 fallback diff --git a/claude-config/hooks/role-router.mjs b/claude-config/hooks/role-router.mjs new file mode 100755 index 0000000..70e9a27 --- /dev/null +++ b/claude-config/hooks/role-router.mjs @@ -0,0 +1,94 @@ +#!/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|develop|develops?|implement|implements?|refactor|refactors?|fix|fixes|bug|bugs|hotfix|patch|patches|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|requirement|roadmap|planning|backlog|user\s?story|prd)\b|产品(?:经理)?|需求|排期|规划|计划|调研|路线图|用户故事)/i; + +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(JSON.stringify({ continue: true, suppressOutput: true })); + return; + } + const data = JSON.parse(raw); + const prompt = data.prompt || data.user_prompt || ''; + if (!prompt) { + console.log(JSON.stringify({ continue: true, suppressOutput: true })); + return; + } + const roles = detectRoles(prompt); + const ctx = buildContext(roles); + if (!ctx) { + console.log(JSON.stringify({ continue: true, suppressOutput: true })); + return; + } + console.log(JSON.stringify({ + hookSpecificOutput: { + hookEventName: 'UserPromptSubmit', + additionalContext: ctx, + }, + })); + } catch { + console.log(JSON.stringify({ continue: true, suppressOutput: true })); + } +} + +const isMain = import.meta.url === `file://${process.argv[1]}`; +if (isMain) main(); diff --git a/claude-config/hooks/role-router.test.mjs b/claude-config/hooks/role-router.test.mjs new file mode 100755 index 0000000..e0c7599 --- /dev/null +++ b/claude-config/hooks/role-router.test.mjs @@ -0,0 +1,66 @@ +#!/usr/bin/env node +/** + * role-router unit tests — 6 fixture (每角色 2 条) + * 跑法: node role-router.test.mjs + */ + +import { detectRoles, buildContext, DEV_RE, TEST_RE, PM_RE } from './role-router.mjs'; +import { execFileSync } from 'child_process'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const HOOK = join(__dirname, 'role-router.mjs'); + +const fixtures = [ + { prompt: '帮我实现一下登录页面', expect: ['dev'] }, + { prompt: 'refactor the auth middleware', expect: ['dev'] }, + { prompt: '跑一下回归测试看看', expect: ['test'] }, + { prompt: 'write unit tests for the parser', expect: ['test'] }, + { prompt: '排期一下下周的需求', expect: ['pm'] }, + { prompt: 'draft the PRD for new chat module', expect: ['pm'] }, + { prompt: '今天天气怎么样', expect: [] }, + { prompt: 'PM 要我先调研后排期再让 dev 实现 + 写测试', expect: ['dev', 'test', 'pm'] }, +]; + +let pass = 0, fail = 0; +const failures = []; + +for (const f of fixtures) { + const got = detectRoles(f.prompt); + const ok = JSON.stringify(got.sort()) === JSON.stringify(f.expect.sort()); + if (ok) { + pass++; + console.log(`PASS "${f.prompt}" → [${got.join(',')}]`); + } else { + fail++; + failures.push({ prompt: f.prompt, expected: f.expect, got }); + console.log(`FAIL "${f.prompt}" expected=[${f.expect.join(',')}] got=[${got.join(',')}]`); + } +} + +// integration: pipe JSON to hook subprocess +function runHook(promptObj) { + const input = JSON.stringify(promptObj); + const out = execFileSync('node', [HOOK], { input, encoding: 'utf8' }); + return JSON.parse(out); +} + +console.log('\n--- integration (subprocess pipe) ---'); +const t1 = runHook({ prompt: '帮我修个 bug' }); +const hasContextDev = t1?.hookSpecificOutput?.additionalContext?.includes('role="dev"'); +if (hasContextDev) { pass++; console.log('PASS subprocess injects dev role'); } +else { fail++; failures.push({prompt:'pipe-dev', got:t1}); console.log('FAIL subprocess dev:', JSON.stringify(t1)); } + +const t2 = runHook({ prompt: 'hello world' }); +const continued = t2?.continue === true; +if (continued) { pass++; console.log('PASS no-match passes through'); } +else { fail++; failures.push({prompt:'pipe-noop', got:t2}); console.log('FAIL no-match:', JSON.stringify(t2)); } + +console.log(`\nResult: ${pass} pass, ${fail} fail`); +if (fail > 0) { + console.error('FAILURES:', JSON.stringify(failures, null, 2)); + process.exit(1); +} +process.exit(0); diff --git a/claude-config/settings.json.snapshot.20260426 b/claude-config/settings.json.snapshot.20260426 new file mode 100644 index 0000000..993274c --- /dev/null +++ b/claude-config/settings.json.snapshot.20260426 @@ -0,0 +1,41 @@ +{ + "model": "opus[1m]", + "hooks": { + "UserPromptSubmit": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/find-node.sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/keyword-detector.mjs" + }, + { + "type": "command", + "command": "sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/find-node.sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/role-router.mjs" + } + ] + } + ], + "SessionStart": [], + "PreToolUse": [], + "PostToolUse": [], + "PostToolUseFailure": [], + "Stop": [] + }, + "statusLine": { + "type": "command", + "command": "sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hud/find-node.sh ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hud/omc-hud.mjs" + }, + "enabledPlugins": { + "codex@openai-codex": true + }, + "extraKnownMarketplaces": { + "openai-codex": { + "source": { + "source": "github", + "repo": "openai/codex-plugin-cc" + } + } + }, + "skipDangerousModePermissionPrompt": true +}