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
+}