@haposoft/cafekit 0.8.15 → 0.8.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Claude Code-first spec-driven workflow and runtime bundle for AI coding assistants.
4
4
 
5
- [![Version](https://img.shields.io/badge/version-0.8.11-blue.svg)](https://github.com/haposoft/cafekit)
5
+ [![Version](https://img.shields.io/badge/version-0.8.16-blue.svg)](https://github.com/haposoft/cafekit)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
  [![Claude%20Code](https://img.shields.io/badge/Claude%20Code-Primary-orange.svg)](https://claude.ai/code)
8
8
 
@@ -87,6 +87,12 @@ CafeKit ships many skills, but the main release surface is:
87
87
 
88
88
  Common companion skills bundled in this package include `inspect`, `impact-analysis`, `research`, `ai-multimodal`, `frontend-development`, `backend-development`, and `react-best-practices`.
89
89
 
90
+ CafeKit uses rule-based skill routing guidance instead of an automatic prompt-scoring hook. See `.claude/rules/skill-workflow-routing.md`, `.claude/rules/skill-domain-routing.md`, or run:
91
+
92
+ ```bash
93
+ node .claude/scripts/generate-skill-catalog.cjs --skills
94
+ ```
95
+
90
96
  ## Quick Start
91
97
 
92
98
  For unclear ideas, brainstorm first:
package/bin/install.js CHANGED
@@ -780,6 +780,79 @@ function copyClaudeRuntimeFiles(platformKey, results, options = {}) {
780
780
  });
781
781
  }
782
782
 
783
+ function removeObsoleteClaudeRuntimeFiles(platformKey, results) {
784
+ if (platformKey !== 'claude') return;
785
+
786
+ const obsoleteFiles = CLAUDE_MIGRATION_MANIFEST?.obsolete?.runtimeFiles || [];
787
+ const targetBase = path.join(PLATFORMS.claude.folder);
788
+
789
+ obsoleteFiles.forEach(relPath => {
790
+ const targetPath = path.join(targetBase, relPath);
791
+
792
+ if (!fs.existsSync(targetPath)) {
793
+ return;
794
+ }
795
+
796
+ fs.rmSync(targetPath, { force: true });
797
+ console.log(` ↻ Removed obsolete runtime: ${relPath}`);
798
+ results.updated++;
799
+ });
800
+ }
801
+
802
+ function pruneObsoleteSettingsHooks(settings, results) {
803
+ const obsoleteSubstrings = CLAUDE_MIGRATION_MANIFEST?.obsolete?.settingsHookCommandSubstrings || [];
804
+
805
+ if (!settings.hooks || obsoleteSubstrings.length === 0) {
806
+ return settings;
807
+ }
808
+
809
+ let removedCount = 0;
810
+ const prunedHooks = {};
811
+
812
+ Object.entries(settings.hooks).forEach(([eventName, matcherEntries]) => {
813
+ if (!Array.isArray(matcherEntries)) {
814
+ prunedHooks[eventName] = matcherEntries;
815
+ return;
816
+ }
817
+
818
+ const prunedEntries = [];
819
+
820
+ matcherEntries.forEach(entry => {
821
+ if (!Array.isArray(entry?.hooks)) {
822
+ prunedEntries.push(entry);
823
+ return;
824
+ }
825
+
826
+ const remainingHooks = entry.hooks.filter(hook => {
827
+ const command = hook?.command || '';
828
+ const isObsolete = obsoleteSubstrings.some(substring => command.includes(substring));
829
+
830
+ if (isObsolete) {
831
+ removedCount++;
832
+ }
833
+
834
+ return !isObsolete;
835
+ });
836
+
837
+ if (remainingHooks.length > 0) {
838
+ prunedEntries.push({ ...entry, hooks: remainingHooks });
839
+ }
840
+ });
841
+
842
+ if (prunedEntries.length > 0) {
843
+ prunedHooks[eventName] = prunedEntries;
844
+ }
845
+ });
846
+
847
+ if (removedCount > 0) {
848
+ console.log(` ↻ Settings: removed ${removedCount} obsolete hook(s)`);
849
+ results.updated++;
850
+ return { ...settings, hooks: prunedHooks };
851
+ }
852
+
853
+ return settings;
854
+ }
855
+
783
856
  // Merge Claude settings.json
784
857
  function mergeClaudeSettings(platformKey, results, options = {}) {
785
858
  if (platformKey !== 'claude') return;
@@ -802,7 +875,7 @@ function mergeClaudeSettings(platformKey, results, options = {}) {
802
875
  existingSettings = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
803
876
  }
804
877
 
805
- const mergedSettings = { ...existingSettings };
878
+ const mergedSettings = pruneObsoleteSettingsHooks({ ...existingSettings }, results);
806
879
 
807
880
  // Merge statusLine
808
881
  if (managedSettings.statusLine) {
@@ -1106,6 +1179,7 @@ async function main() {
1106
1179
  if (platformKey === 'claude') {
1107
1180
  copyRoutingFile(platformKey, results, installerOptions);
1108
1181
  copyClaudeRuntimeFiles(platformKey, results, installerOptions);
1182
+ removeObsoleteClaudeRuntimeFiles(platformKey, results);
1109
1183
  mergeClaudeSettings(platformKey, results, installerOptions);
1110
1184
  copyClaudeMdFile(platformKey, results, installerOptions);
1111
1185
  copyRulesDirectory(platformKey, results, installerOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haposoft/cafekit",
3
- "version": "0.8.15",
3
+ "version": "0.8.16",
4
4
  "description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
5
5
  "author": "Haposoft <nghialt@haposoft.com>",
6
6
  "license": "MIT",
@@ -88,6 +88,8 @@ Consult these when the task touches the relevant area:
88
88
 
89
89
  - Primary workflow: `./.claude/rules/workflow.md`
90
90
  - Development rules: `./.claude/rules/ai-dev-rules.md`
91
+ - Skill workflow routing: `./.claude/rules/skill-workflow-routing.md`
92
+ - Skill domain routing: `./.claude/rules/skill-domain-routing.md`
91
93
  - Subagent coordination: `./.claude/rules/orchestrator.md`
92
94
  - Docs maintenance: `./.claude/rules/manage-docs.md`
93
95
  - State sync: `./.claude/rules/state-sync.md`
@@ -97,6 +99,8 @@ Consult these when the task touches the relevant area:
97
99
  ## Skill And Script Use
98
100
 
99
101
  - Evaluate the available skills catalog before work and activate the relevant skill(s).
102
+ - Use `./.claude/rules/skill-workflow-routing.md` and `./.claude/rules/skill-domain-routing.md` to choose skills. CafeKit does not auto-route prompts through a scoring hook.
103
+ - When needed, run `node .claude/scripts/generate-skill-catalog.cjs --skills` to inspect the installed skill catalog.
100
104
  - If there is a reasonable chance a skill applies, prefer the skill workflow over ad hoc execution.
101
105
  - If modifying skills, edit the current project/runtime files, not `~/.claude/skills` directly.
102
106
  - Run Python skill scripts with the skill venv:
@@ -64,7 +64,6 @@ const DEFAULT_CONFIG = {
64
64
  'session-init': true,
65
65
  'subagent-init': true,
66
66
  'dev-rules-reminder': true,
67
- 'skill-router': true,
68
67
  'usage': true,
69
68
  'context-tracking': true,
70
69
  'scout-block': true,
@@ -358,7 +358,7 @@ function buildRulesSection({ devRulesPath, catalogScript, skillsVenv, plansPath,
358
358
  lines.push(`- **IMPORTANT:** DO NOT create markdown files outside of "${plansRef}" or "${docsRef}" UNLESS the user explicitly requests it.`);
359
359
 
360
360
  if (catalogScript) {
361
- lines.push(`- Activate skills: Run \`python ${catalogScript} --skills\` to generate a skills catalog and analyze it, then activate the relevant skills that are needed for the task during the process.`);
361
+ lines.push(`- Activate skills: Use \`.claude/rules/skill-workflow-routing.md\` and \`.claude/rules/skill-domain-routing.md\`; run \`node ${catalogScript} --skills\` when you need a generated skills catalog.`);
362
362
  }
363
363
 
364
364
  if (skillsVenv) {
@@ -525,7 +525,7 @@ function buildReminderContext({ sessionId, config, staticEnv, configDirName = '.
525
525
 
526
526
  // Resolve paths
527
527
  const devRulesPath = resolveRulesPath('ai-dev-rules.md', configDirName);
528
- const catalogScript = resolveScriptPath('generate_catalogs.py', configDirName);
528
+ const catalogScript = resolveScriptPath('generate-skill-catalog.cjs', configDirName);
529
529
  const skillsVenv = resolveSkillsVenv(configDirName);
530
530
 
531
531
  // Build plan context
@@ -91,6 +91,13 @@ try {
91
91
  lines.push('- Ensure token efficiency while maintaining high quality.');
92
92
  lines.push('');
93
93
 
94
+ // Skill routing: advisory rule docs, no automatic prompt router.
95
+ lines.push('## Skill Routing');
96
+ lines.push('- Choose skills from intent using `.claude/rules/skill-workflow-routing.md` and `.claude/rules/skill-domain-routing.md`.');
97
+ lines.push('- If needed, run `node .claude/scripts/generate-skill-catalog.cjs --skills` to inspect installed skills.');
98
+ lines.push('- Explicit user commands and direct-answer requests override routing suggestions.');
99
+ lines.push('');
100
+
94
101
  // Modularization
95
102
  lines.push('## [IMPORTANT] Consider Modularization:');
96
103
  lines.push('- If a file exceeds 200 lines, consider splitting it');
@@ -58,6 +58,7 @@
58
58
  "required": [
59
59
  "validate-docs.cjs",
60
60
  "validate-docs-reconstruct.cjs",
61
+ "generate-skill-catalog.cjs",
61
62
  "browser-tool.cjs",
62
63
  "validate-spec-output.cjs"
63
64
  ]
@@ -68,6 +69,15 @@
68
69
  "hooks": {
69
70
  "assumed": []
70
71
  },
72
+ "obsolete": {
73
+ "runtimeFiles": [
74
+ "hooks/skill-router.cjs",
75
+ "hooks/lib/skill-router-routes.cjs"
76
+ ],
77
+ "settingsHookCommandSubstrings": [
78
+ "hooks/skill-router.cjs"
79
+ ]
80
+ },
71
81
  "runtime": {
72
82
  "files": [
73
83
  "gitignore",
@@ -80,7 +90,6 @@
80
90
  "hooks/privacy-block.cjs",
81
91
  "hooks/inspect-block.cjs",
82
92
  "hooks/rules.cjs",
83
- "hooks/skill-router.cjs",
84
93
  "hooks/spec-state.cjs",
85
94
  "hooks/state.cjs",
86
95
  "hooks/lib/color.cjs",
@@ -89,8 +98,7 @@
89
98
  "hooks/lib/git.cjs",
90
99
  "hooks/lib/config.cjs",
91
100
  "hooks/lib/context.cjs",
92
- "hooks/lib/detect.cjs",
93
- "hooks/lib/skill-router-routes.cjs"
101
+ "hooks/lib/detect.cjs"
94
102
  ]
95
103
  },
96
104
  "settings": {
@@ -0,0 +1,87 @@
1
+ # Skill Domain Routing
2
+
3
+ Use this decision tree to choose domain skills from the installed CafeKit skill set. This is advisory routing, not an automatic hook.
4
+
5
+ ## Frontend And UI
6
+
7
+ ```text
8
+ User wants to...
9
+ ├── choose style, color, layout, UX, design system -> /hapo:frontend-design
10
+ ├── build React/TypeScript UI components -> /hapo:frontend-development
11
+ ├── optimize React/Next.js performance or rerenders -> /hapo:react-best-practices
12
+ ├── apply deep UI/UX intelligence, visual polish -> /hapo:ui-ux-pro-max
13
+ └── test UI in browser -> /hapo:web-testing or /hapo:agent-browser
14
+ ```
15
+
16
+ Use `/hapo:react-best-practices` for performance/re-render patterns. Use `/hapo:frontend-development` for normal implementation.
17
+
18
+ ## Backend, Data, And Infrastructure
19
+
20
+ ```text
21
+ User wants to...
22
+ ├── API, service, auth, database-backed feature -> /hapo:backend-development
23
+ ├── deployment, Docker, Kubernetes, Cloudflare, CI/CD -> /hapo:devops
24
+ ├── mobile apps, iOS, Android, React Native, Flutter -> /hapo:mobile-development
25
+ └── impact of changing existing behavior -> /hapo:impact-analysis
26
+ ```
27
+
28
+ ## Codebase Understanding
29
+
30
+ ```text
31
+ User wants to...
32
+ ├── find files, locate code, scan project structure -> /hapo:inspect
33
+ ├── diagnose an error or failing check -> /hapo:debug
34
+ ├── understand possible side effects -> /hapo:impact-analysis
35
+ └── compare external approaches or best practices -> /hapo:research
36
+ ```
37
+
38
+ Local source truth comes from `/hapo:inspect`; external/current knowledge comes from `/hapo:research`.
39
+
40
+ ## Documentation
41
+
42
+ ```text
43
+ User wants to...
44
+ ├── create missing project docs -> /hapo:docs --init
45
+ ├── update docs after source changes -> /hapo:docs --update
46
+ ├── summarize codebase quickly -> /hapo:docs --summarize
47
+ ├── reconstruct as-is docs from legacy source code -> /hapo:docs --reconstruct <scope>
48
+ └── create a future feature spec -> /hapo:specs
49
+ ```
50
+
51
+ Keep as-is reconstruction separate from future requirements. Reconstructed docs document current behavior; specs define requested changes.
52
+
53
+ ## Testing And Browser
54
+
55
+ ```text
56
+ User wants to...
57
+ ├── run test suites, coverage, scoped verification -> /hapo:test
58
+ ├── E2E/unit/integration/load/security/a11y strategy -> /hapo:web-testing
59
+ ├── drive a browser with snapshots or Browserbase -> /hapo:agent-browser
60
+ └── Chrome DevTools/Puppeteer evidence -> /hapo:chrome-devtools
61
+ ```
62
+
63
+ ## Documents And Media
64
+
65
+ ```text
66
+ User wants to...
67
+ ├── PowerPoint/slides -> /hapo:pptx
68
+ ├── Word documents -> /hapo:docx
69
+ ├── PDFs -> /hapo:pdf
70
+ ├── spreadsheets/CSV/XLSX -> /hapo:xlsx
71
+ └── image/audio/video/document multimodal analysis -> /hapo:ai-multimodal
72
+ ```
73
+
74
+ ## Git And Release
75
+
76
+ ```text
77
+ User wants to...
78
+ ├── commit, branch, tag, push, release prep -> /hapo:git
79
+ └── review before release -> /hapo:code-review
80
+ ```
81
+
82
+ ## Usage Notes
83
+
84
+ - Read the selected skill's `SKILL.md` before acting.
85
+ - Do not activate a skill only because one keyword appears. Use the whole user intent.
86
+ - If a task spans multiple domains, name the primary skill and mention the secondary skill in the plan.
87
+ - If no domain skill fits, use the core CafeKit workflow in `skill-workflow-routing.md`.
@@ -0,0 +1,84 @@
1
+ # Skill Workflow Routing
2
+
3
+ Use this rule instead of automatic prompt routing. Read the user's intent, choose the primary CafeKit workflow, then load the relevant `.claude/skills/<skill>/SKILL.md`.
4
+
5
+ Do not inject or force a skill when the user explicitly asks for a direct answer, a specific command, or a different workflow.
6
+
7
+ ## Core Specification Workflow
8
+
9
+ ```text
10
+ /hapo:brainstorm -> /hapo:specs -> /hapo:develop -> /hapo:test -> /hapo:code-review -> /hapo:git
11
+ ```
12
+
13
+ | User intent | Suggested start |
14
+ |---|---|
15
+ | unclear idea, possible approaches, scope tradeoffs | `/hapo:brainstorm` |
16
+ | create requirements, design, task breakdown, validate spec | `/hapo:specs` |
17
+ | implement an approved spec or a specific task file | `/hapo:develop` |
18
+ | run tests, QA, runtime verification | `/hapo:test` |
19
+ | review code quality, security, regressions, maintainability | `/hapo:code-review` |
20
+ | commit, tag, push, branch, release prep | `/hapo:git` |
21
+
22
+ ## Bug And Incident Workflow
23
+
24
+ ```text
25
+ /hapo:debug -> /hapo:hotfix -> /hapo:test -> /hapo:code-review
26
+ ```
27
+
28
+ | User intent | Suggested start |
29
+ |---|---|
30
+ | diagnose why something fails, find root cause, inspect CI/logs | `/hapo:debug` |
31
+ | fix a known bug after diagnosis, urgent regression, production issue | `/hapo:hotfix` |
32
+ | verify the fix and affected behavior | `/hapo:test` |
33
+ | check side effects before closeout | `/hapo:code-review` |
34
+
35
+ `/hapo:debug` is diagnostic-only. Do not edit product code there. Use `/hapo:hotfix` when the user asks to fix.
36
+
37
+ ## Existing System Documentation Workflow
38
+
39
+ ```text
40
+ /hapo:inspect -> /hapo:docs --reconstruct -> human review -> /hapo:specs -> /hapo:develop
41
+ ```
42
+
43
+ | User intent | Suggested start |
44
+ |---|---|
45
+ | understand source structure or locate implementation areas | `/hapo:inspect` |
46
+ | rebuild as-is docs/requirements from a legacy codebase | `/hapo:docs --reconstruct <scope>` |
47
+ | update normal project docs | `/hapo:docs --update` |
48
+ | initialize missing docs | `/hapo:docs --init` |
49
+ | summarize a codebase without full docs work | `/hapo:docs --summarize` |
50
+
51
+ Do not turn reconstructed docs directly into implementation. Human review must approve the as-is docs before `/hapo:specs` defines future changes.
52
+
53
+ ## Research And Analysis Workflow
54
+
55
+ ```text
56
+ /hapo:inspect -> /hapo:impact-analysis -> /hapo:research
57
+ ```
58
+
59
+ | User intent | Suggested start |
60
+ |---|---|
61
+ | file discovery, codebase structure, source scouting | `/hapo:inspect` |
62
+ | affected files, blast radius, dependency impact, side effects | `/hapo:impact-analysis` |
63
+ | external best practices, tool comparison, technical investigation | `/hapo:research` |
64
+
65
+ Use `/hapo:research` for external knowledge. Use `/hapo:inspect` for local source truth.
66
+
67
+ ## Output Artifact Workflow
68
+
69
+ | User intent | Suggested skill |
70
+ |---|---|
71
+ | diagrams, architecture maps, Mermaid/SVG/PNG graph output | `/hapo:generate-graph` |
72
+ | PowerPoint or slide deck creation/editing | `/hapo:pptx` |
73
+ | Word documents | `/hapo:docx` |
74
+ | PDF processing | `/hapo:pdf` |
75
+ | spreadsheets | `/hapo:xlsx` |
76
+ | screenshots, images, audio, video, OCR, multimodal understanding | `/hapo:ai-multimodal` |
77
+
78
+ ## Selection Rules
79
+
80
+ - Pick one primary workflow first.
81
+ - Add a secondary skill only when it materially changes execution.
82
+ - Prefer explicit user commands over this table.
83
+ - If two workflows conflict, ask one concise clarification.
84
+ - If the user asks to "fix" and root cause is unknown, start with `/hapo:debug` unless the issue is trivial and local.
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CafeKit skill catalog scanner.
4
+ *
5
+ * This mirrors the Research/.claude approach: expose the installed skill set as
6
+ * a readable catalog so the model can choose skills from rules, not from an
7
+ * automatic prompt-scoring hook.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ function usage() {
14
+ console.log(`Usage:
15
+ node .claude/scripts/generate-skill-catalog.cjs [--skills] [--json] [--root <skills-dir>]
16
+
17
+ Examples:
18
+ node .claude/scripts/generate-skill-catalog.cjs --skills
19
+ node .claude/scripts/generate-skill-catalog.cjs --json --root .claude/skills`);
20
+ }
21
+
22
+ function parseArgs(argv) {
23
+ const args = { json: false, help: false, root: null };
24
+ for (let i = 0; i < argv.length; i += 1) {
25
+ const arg = argv[i];
26
+ if (arg === '--help' || arg === '-h') args.help = true;
27
+ else if (arg === '--json') args.json = true;
28
+ else if (arg === '--skills') continue;
29
+ else if (arg === '--root') {
30
+ args.root = argv[i + 1] || null;
31
+ i += 1;
32
+ } else {
33
+ args.root = args.root || arg;
34
+ }
35
+ }
36
+ return args;
37
+ }
38
+
39
+ function defaultSkillsRoot() {
40
+ const installed = path.resolve(process.cwd(), '.claude', 'skills');
41
+ if (fs.existsSync(installed)) return installed;
42
+ return path.resolve(__dirname, '..', 'skills');
43
+ }
44
+
45
+ function readText(filePath) {
46
+ return fs.readFileSync(filePath, 'utf8');
47
+ }
48
+
49
+ function extractFrontmatter(content) {
50
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
51
+ if (!match) return {};
52
+ const frontmatter = {};
53
+ const lines = match[1].split(/\r?\n/);
54
+ for (let i = 0; i < lines.length; i += 1) {
55
+ const line = lines[i];
56
+ const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
57
+ if (!kv) continue;
58
+ const key = kv[1];
59
+ let value = kv[2].trim();
60
+ if (value === '>' || value === '>-' || value === '|' || value === '|-') {
61
+ const block = [];
62
+ i += 1;
63
+ while (i < lines.length && /^\s+/.test(lines[i])) {
64
+ block.push(lines[i].trim());
65
+ i += 1;
66
+ }
67
+ i -= 1;
68
+ value = block.join(' ');
69
+ }
70
+ if (
71
+ (value.startsWith('"') && value.endsWith('"')) ||
72
+ (value.startsWith("'") && value.endsWith("'"))
73
+ ) {
74
+ value = value.slice(1, -1);
75
+ }
76
+ frontmatter[key] = value;
77
+ }
78
+ return frontmatter;
79
+ }
80
+
81
+ function firstParagraph(content) {
82
+ const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, '');
83
+ const paragraph = [];
84
+ for (const rawLine of body.split(/\r?\n/)) {
85
+ const line = rawLine.trim();
86
+ if (!line || line.startsWith('#')) {
87
+ if (paragraph.length > 0) break;
88
+ continue;
89
+ }
90
+ paragraph.push(line);
91
+ if (paragraph.join(' ').length > 160) break;
92
+ }
93
+ return paragraph.join(' ').slice(0, 360);
94
+ }
95
+
96
+ function categorize(name, description) {
97
+ const text = `${name} ${description}`.toLowerCase();
98
+ if (/spec|brainstorm|develop|sync/.test(text)) return 'workflow';
99
+ if (/debug|hotfix|inspect|impact|review|test|agent-browser|chrome/.test(text)) return 'quality';
100
+ if (/frontend|react|ui|ux|mobile/.test(text)) return 'frontend-mobile';
101
+ if (/pdf|docx|pptx|xlsx|multimodal/.test(text)) return 'artifacts-tools';
102
+ if (/backend|devops|api|database|deploy/.test(text)) return 'backend-infra';
103
+ if (/docs|research|graph/.test(text)) return 'knowledge';
104
+ if (/git/.test(text)) return 'git';
105
+ return 'other';
106
+ }
107
+
108
+ function scanSkills(root) {
109
+ if (!fs.existsSync(root)) {
110
+ throw new Error(`skills root not found: ${root}`);
111
+ }
112
+ const entries = fs.readdirSync(root, { withFileTypes: true });
113
+ const skills = [];
114
+ for (const entry of entries) {
115
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
116
+ const skillPath = path.join(root, entry.name, 'SKILL.md');
117
+ if (!fs.existsSync(skillPath)) continue;
118
+ const content = readText(skillPath);
119
+ const frontmatter = extractFrontmatter(content);
120
+ const name = frontmatter.name || entry.name;
121
+ const description = frontmatter.description || firstParagraph(content);
122
+ skills.push({
123
+ name,
124
+ directory: entry.name,
125
+ description,
126
+ category: categorize(name, description),
127
+ has_references: fs.existsSync(path.join(root, entry.name, 'references')),
128
+ has_scripts: fs.existsSync(path.join(root, entry.name, 'scripts')),
129
+ });
130
+ }
131
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
132
+ }
133
+
134
+ function renderMarkdown(skills, root) {
135
+ const lines = [
136
+ '# CafeKit Skills Catalog',
137
+ '',
138
+ `Skills root: \`${root}\``,
139
+ `Total skills: ${skills.length}`,
140
+ '',
141
+ ];
142
+ const categories = [...new Set(skills.map((skill) => skill.category))].sort();
143
+ for (const category of categories) {
144
+ lines.push(`## ${category}`, '');
145
+ for (const skill of skills.filter((item) => item.category === category)) {
146
+ const badges = [
147
+ skill.has_references ? 'refs' : null,
148
+ skill.has_scripts ? 'scripts' : null,
149
+ ].filter(Boolean);
150
+ lines.push(
151
+ `- \`${skill.name}\` (${skill.directory})${badges.length ? ` [${badges.join(', ')}]` : ''}: ${skill.description}`
152
+ );
153
+ }
154
+ lines.push('');
155
+ }
156
+ return lines.join('\n').trim() + '\n';
157
+ }
158
+
159
+ function main() {
160
+ const args = parseArgs(process.argv.slice(2));
161
+ if (args.help) {
162
+ usage();
163
+ return;
164
+ }
165
+
166
+ const root = path.resolve(process.cwd(), args.root || defaultSkillsRoot());
167
+ const skills = scanSkills(root);
168
+ if (args.json) {
169
+ console.log(JSON.stringify({ root, total: skills.length, skills }, null, 2));
170
+ return;
171
+ }
172
+ console.log(renderMarkdown(skills, root));
173
+ }
174
+
175
+ try {
176
+ main();
177
+ } catch (error) {
178
+ console.error(`[skill-catalog] ${error.message}`);
179
+ process.exit(1);
180
+ }
@@ -42,10 +42,6 @@
42
42
  "type": "command",
43
43
  "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/rules.cjs\""
44
44
  },
45
- {
46
- "type": "command",
47
- "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-router.cjs\""
48
- },
49
45
  {
50
46
  "type": "command",
51
47
  "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/spec-state.cjs\""
@@ -1,196 +0,0 @@
1
- const MIN_SCORE = 4;
2
- const WEIGHTS = { strong: 6, medium: 3, weak: 1, negative: -5 };
3
-
4
- const ROUTES = [
5
- route('hapo:hotfix', 'urgent fix or production regression', 100, {
6
- strong: ['hotfix', 'fix bug', 'fix lỗi', 'fix loi', 'sửa lỗi', 'sua loi', 'production bug', 'prod bug', 'regression', 'lỗi production', 'loi production', 'sửa gấp', 'sua gap', 'バグ修正', '不具合修正', '本番障害', '本番バグ', '緊急修正', '至急修正', 'リグレッション'],
7
- medium: ['khẩn cấp', 'khan cap', 'production', 'critical bug', 'rollback', '修正して', '直して', '緊急', '本番', '重大バグ', 'ロールバック', '障害対応'],
8
- weak: ['fix', 'fixing', 'sửa', 'sua', 'urgent', 'emergency', 'release is broken', '修正', '直す', '至急', 'リリースが壊れた'],
9
- negative: ['slide', 'pptx', 'spec', 'brainstorm'],
10
- }),
11
- route('hapo:debug', 'bug investigation or failure diagnosis', 90, {
12
- strong: ['debug', 'root cause', 'diagnose', 'stack trace', 'không chạy', 'khong chay', 'デバッグ', '原因調査', '根本原因', 'スタックトレース', '動かない'],
13
- medium: ['bug', 'error', 'exception', 'failing', 'failed', 'failure', 'broken', 'lỗi', 'loi', 'tại sao', 'tai sao', 'vì sao', 'vi sao', 'nguyên nhân', 'nguyen nhan', 'ci fail', 'build fail', 'バグ', 'エラー', '例外', '失敗', '壊れている', 'なぜ', '原因', 'ci失敗', 'ビルド失敗'],
14
- weak: ['sai', 'fail', 'issue', 'problem', '問題', '不具合'],
15
- negative: ['commit', 'push', 'slide', 'pptx'],
16
- }),
17
- route('hapo:specs', 'specification, requirements, design, tasks, or spec validation', 80, {
18
- strong: ['spec', 'specs', 'requirements', 'acceptance criteria', 'task breakdown', 'đặc tả', 'dac ta', '仕様', '仕様書', '要件', '受け入れ条件', 'タスク分解', '仕様を作って', '仕様を作成'],
19
- medium: ['requirement', 'ears', 'design doc', 'scope', '--validate', 'yêu cầu', 'yeu cau', 'phạm vi', 'pham vi', 'validate spec', 'kiểm tra spec', 'kiem tra spec', '要求', '設計書', 'スコープ', '検証', '仕様を確認'],
20
- weak: ['tính năng mới', 'tinh nang moi', 'feature idea', 'user story', 'criteria', 'task list', '新機能', 'ユーザーストーリー', '基準', 'タスクリスト'],
21
- negative: ['commit', 'push', 'bug', 'error', 'production', 'pptx', 'pdf', 'reconstruct requirements', 'as-is requirements', 'legacy system documentation', 'documentation from source code', 'docs from source code', 'ソースコードから'],
22
- }),
23
- route('hapo:develop', 'implementation from an approved spec or task list', 75, {
24
- strong: ['develop', 'implement', 'implementation', 'theo spec', 'theo specs', 'approved spec', 'làm theo spec', 'lam theo spec', '実装', '開発', '仕様に沿って', '仕様どおり', '承認済み仕様'],
25
- medium: ['build this', 'code this', 'start task', 'run task', 'thực hiện task', 'thuc hien task', 'phát triển', 'phat trien', 'bắt đầu implement', 'bat dau implement', 'vào code', 'vao code', '作って', 'コードを書いて', 'タスクを開始', 'タスクを実行', '開発して', '実装して'],
26
- weak: ['triển khai', 'trien khai', 'đưa vào code', 'dua vao code', 'code feature', 'コード化', '機能を作る'],
27
- negative: ['bug', 'debug', 'review', 'test only', 'commit'],
28
- }),
29
- route('hapo:test', 'test, verification, QA, or runtime validation', 70, {
30
- strong: ['unit test', 'integration test', 'e2e', 'playwright', 'coverage', 'test all', 'run all tests', 'test toàn bộ', 'test toan bo', 'kiểm thử', 'kiem thu', 'kiểm tra toàn bộ', 'kiem tra toan bo', '単体テスト', '結合テスト', '全体テスト', 'カバレッジ', 'テストして'],
31
- medium: ['test', 'tests', 'testing', 'qa', 'verify', 'verification', 'kiểm tra chạy', 'kiem tra chay', 'xác minh', 'xac minh', 'テスト', '検証', '確認', '動作確認', '全体確認'],
32
- weak: ['assert', 'runtime proof', 'manual qa', 'end to end', 'smoke test', 'アサート', 'スモークテスト'],
33
- negative: ['spec', 'requirements', 'commit', 'push'],
34
- }),
35
- route('hapo:code-review', 'code review, audit, security, or quality assessment', 68, {
36
- strong: ['code review', 'security review', 'performance review', 'đánh giá code', 'danh gia code', 'コードレビュー', 'セキュリティレビュー', '性能レビュー'],
37
- medium: ['review', 'audit', 'quality', 'đánh giá', 'danh gia', 'kiểm tra chất lượng', 'kiem tra chat luong', 'レビュー', '監査', '品質', '品質確認'],
38
- weak: ['risk', 'maintainability', 'readability', 'vulnerability', 'lỗ hổng', 'lo hong', 'リスク', '保守性', '可読性', '脆弱性'],
39
- negative: ['slide', 'pptx', 'commit and push'],
40
- }),
41
- route('hapo:git', 'git, commit, push, branch, tag, or pull request workflow', 65, {
42
- strong: ['commit', 'push', 'pull request', 'git', 'đẩy lên', 'day len', 'コミット', 'プッシュ', 'プルリク', 'リリースして'],
43
- medium: ['pr ', 'branch', 'tag', 'release', 'publish', 'merge', 'rebase', 'ブランチ', 'タグ', 'リリース', '公開', 'マージ', 'リベース'],
44
- weak: ['origin', 'remote', 'checkout', 'stash', 'version bump', 'リモート', 'チェックアウト', 'スタッシュ', 'バージョン更新'],
45
- negative: ['deploy', 'test', 'review only'],
46
- }),
47
- route('hapo:inspect', 'codebase discovery, file search, structure scan, or locating implementation areas', 64, {
48
- strong: ['inspect', 'codebase scan', 'scan codebase', 'scan source', 'file discovery', 'find files', 'locate files', 'xem source', 'xem codebase', 'kiểm tra source', 'kiem tra source', 'kiểm tra source code', 'kiem tra source code', 'quét source', 'quet source', 'quét codebase', 'quet codebase', 'kiểm tra cấu trúc', 'kiem tra cau truc', 'コード構造', 'ソース確認', 'コードベース確認', 'ファイル探索', '構造を確認'],
49
- medium: ['search files', 'find where', 'where is', 'project structure', 'code structure', 'repo structure', 'source code', 'tìm file', 'tim file', 'tìm trong source', 'tim trong source', 'cấu trúc project', 'cau truc project', 'ở đâu', 'o dau', 'nằm đâu', 'nam dau', '関連ファイル', 'どこにある', 'プロジェクト構造', 'リポジトリ構造', '探して'],
50
- weak: ['scan', 'inspect code', 'explore code', 'xem qua', 'xem giúp', '調べて', '確認して'],
51
- negative: ['bug', 'error', 'lỗi', 'loi', 'fail', 'failure', 'production', 'hotfix', 'fix', 'sửa', 'sua', 'debug', 'develop', 'implement', 'test', 'commit', 'push', 'slide', 'pptx'],
52
- }),
53
- route('hapo:impact-analysis', 'impact analysis before changing existing behavior', 64, {
54
- strong: ['impact analysis', 'blast radius', 'ảnh hưởng', 'anh huong', 'tác động', 'tac dong', '影響分析', '影響範囲', '影響', '副作用'],
55
- medium: ['impact', 'liên quan những đâu', 'lien quan nhung dau', 'affected files', 'dependency impact', '関連箇所', '影響ファイル', '依存関係'],
56
- weak: ['before changing', 'risk area', 'downstream', 'upstream', 'side effect', '変更前', 'リスク範囲', '下流', '上流'],
57
- negative: ['slide', 'pptx'],
58
- }),
59
- route('hapo:frontend-design', 'UI/UX design, visual style, layout, or color system', 62, {
60
- strong: ['ui design', 'visual style', 'color system', 'thiết kế giao diện', 'thiet ke giao dien', 'màu sắc', 'mau sac', 'uiデザイン', '画面デザイン', 'ビジュアルスタイル', '配色'],
61
- medium: ['ux', 'layout', 'style', 'theme', 'responsive design', 'giao diện', 'giao dien', 'wireframe', 'レイアウト', 'スタイル', 'テーマ', 'レスポンシブデザイン', '画面', 'ワイヤーフレーム', '色'],
62
- weak: ['palette', 'typography', 'spacing', 'polish ui', 'mockup', 'prototype', 'パレット', 'タイポグラフィ', '余白', 'モックアップ', 'プロトタイプ'],
63
- negative: ['backend', 'api', 'database'],
64
- }),
65
- route('hapo:react-best-practices', 'React and Next.js performance patterns, rerender optimization, and Vercel best practices', 60, {
66
- strong: ['react best practices', 'next.js best practices', 'vercel react best practices', 'react performance', 'next.js performance', 'optimize react', 'optimize next.js', 'rerender nhiều', 'rerender nhieu', 'nhiều rerender', 'nhieu rerender', 'tối ưu react', 'toi uu react', 'tối ưu next.js', 'toi uu next.js', 'reactベストプラクティス', 'next.jsベストプラクティス', 'react最適化', 'next.js最適化', 'react性能'],
67
- medium: ['bundle optimization', 'bundle size', 'rerender optimization', 're-render optimization', 'data fetching', 'server component', 'client component', 'suspense', 'hydration', 'waterfall', 'usememo', 'usecallback', 'react cache', 'optimize', 'optimization', 'tối ưu', 'toi uu', 'tối ưu rerender', 'toi uu rerender', 'tối ưu bundle', 'toi uu bundle', 'バンドル最適化', '再レンダー最適化', '最適化', 'データ取得', 'サーバーコンポーネント', 'クライアントコンポーネント', 'ハイドレーション'],
68
- weak: ['react', 'next.js', 'memo', 'rerender', 're-render', 'render performance', 'component performance', 'waterfalls', 'lazy state', 'dynamic import', 'react pattern', 'next.js pattern', 'レンダー性能', 'コンポーネント性能', '動的インポート'],
69
- negative: ['backend', 'api', 'database', 'slide', 'pptx', 'commit', 'push'],
70
- }),
71
- route('hapo:frontend-development', 'frontend implementation work', 58, {
72
- strong: ['react', 'next.js', 'vite', 'frontend', 'tailwind', 'web app', 'フロントエンド', 'webアプリ'],
73
- medium: ['component', 'css', 'html', 'browser ui', 'client side', 'giao diện react', 'コンポーネント', 'ブラウザui', 'クライアント側', 'react画面'],
74
- weak: ['state management', 'hook', 'form', 'table', 'dashboard ui', '状態管理', 'フック', 'フォーム', 'テーブル', 'ダッシュボードui'],
75
- negative: ['best practices', 'performance', 'optimize', 'optimization', 'rerender', 're-render', 'tối ưu', 'toi uu', '再レンダー', '最適化', '性能', 'backend only', 'database only', 'slide', 'pptx'],
76
- }),
77
- route('hapo:backend-development', 'backend, API, database, or service implementation', 58, {
78
- strong: ['backend', 'api', 'database', 'endpoint', 'server', 'service', 'バックエンド', 'データベース', 'エンドポイント', 'サーバー', 'サービス'],
79
- medium: ['db', 'sql', 'postgres', 'mysql', 'migration', 'schema', 'worker', 'queue', 'マイグレーション', 'スキーマ', 'ワーカー', 'キュー'],
80
- weak: ['controller', 'route handler', 'repository', 'model', 'auth service', 'コントローラ', 'ルートハンドラ', 'リポジトリ', 'モデル', '認証サービス'],
81
- negative: ['frontend only', 'slide', 'pptx'],
82
- }),
83
- route('hapo:mobile-development', 'mobile app implementation', 56, {
84
- strong: ['mobile', 'ios', 'android', 'react native', 'flutter', 'モバイル'],
85
- medium: ['app store', 'play store', 'native app', 'mobile screen', 'アプリストア', 'playストア', 'ネイティブアプリ', 'モバイル画面'],
86
- weak: ['gesture', 'push notification', 'offline sync', 'ジェスチャー', 'プッシュ通知', 'オフライン同期'],
87
- negative: ['web only', 'desktop only'],
88
- }),
89
- route('hapo:devops', 'deployment, infrastructure, CI/CD, or operations work', 54, {
90
- strong: ['deploy', 'deployment', 'docker', 'kubernetes', 'ci/cd', 'github actions', 'デプロイ', 'デプロイメント'],
91
- medium: ['vercel', 'infra', 'infrastructure', 'devops', 'pipeline', 'environment variable', 'インフラ', 'パイプライン', '環境変数'],
92
- weak: ['build server', 'container', 'helm', 'terraform', 'monitoring', 'ビルドサーバー', 'コンテナ', '監視'],
93
- negative: ['slide', 'pptx', 'spec only'],
94
- }),
95
- route('hapo:generate-graph', 'diagram, graph, architecture map, or flow visualization', 52, {
96
- strong: ['diagram', 'graph', 'mermaid', 'flowchart', 'architecture diagram', 'sơ đồ', 'so do', '図', '図解', 'ダイアグラム', 'グラフ', 'フローチャート', '構成図'],
97
- medium: ['biểu đồ', 'bieu do', 'visualize', 'sequence diagram', 'data flow', '可視化', 'シーケンス図', 'データフロー'],
98
- weak: ['mind map', 'dependency map', 'system map', 'マインドマップ', '依存関係図', 'システム図'],
99
- negative: ['pptx', 'slide deck'],
100
- }),
101
- route('hapo:docs', 'project documentation, codebase docs, or source-backed as-is reconstruction', 51, {
102
- strong: ['hapo:docs', 'project docs', 'system docs', 'codebase docs', 'codebase documentation', 'documentation from source code', 'docs from source code', 'generate docs from codebase', 'dựng tài liệu từ source code', 'dung tai lieu tu source code', 'tài liệu hệ thống', 'tai lieu he thong', 'tài liệu hiện trạng', 'tai lieu hien trang', 'tạo tài liệu project', 'tao tai lieu project', 'legacy documentation', 'legacy system documentation', 'as-is documentation', 'as-is requirements', 'requirement reconstruction', 'reconstruct requirements', 'reconstruct requirements from legacy system', 'ソースコードから仕様書', 'ソースコードから仕様書を作成', 'ソースコードからドキュメント', '既存システムのドキュメント', '現行仕様書', 'as-isドキュメント'],
103
- medium: ['create docs', 'update docs', 'refresh docs', 'summarize codebase', 'system documentation', 'source to docs', 'reverse documentation', 'current-state docs', 'existing system docs', 'legacy docs', 'legacy system', 'dựng docs', 'dung docs', 'tạo docs', 'tao docs', 'cập nhật docs', 'cap nhat docs', 'tóm tắt codebase', 'tom tat codebase', 'dựng lại tài liệu', 'dung lai tai lieu', '仕様書を作成', 'ドキュメント作成', 'ドキュメント更新', 'コードベース要約', '既存システム', 'レガシーシステム'],
104
- weak: ['docs', 'documentation', 'document project', 'document codebase', 'as-is', 'reconstruct', 'hiện trạng', 'hien trang', 'tài liệu', 'tai lieu', 'ドキュメント', '資料化', '仕様化', '現状'],
105
- negative: ['official docs', 'latest docs', 'library docs', 'framework docs', 'api docs lookup', 'context7', 'best practice', 'research', 'pptx', 'slide'],
106
- }),
107
- route('hapo:brainstorm', 'early ideation or unclear solution direction', 50, {
108
- strong: ['brainstorm', 'ý tưởng', 'y tuong', 'phương án', 'phuong an', 'gợi ý', 'goi y', 'ブレスト', 'アイデア', '案', '提案して', '相談'],
109
- medium: ['idea', 'ideas', 'approach', 'options', 'tradeoff', 'chủ đề', 'chu de', 'cần làm gì', 'can lam gi', 'アプローチ', '選択肢', 'トレードオフ', 'テーマ', '何をすれば'],
110
- weak: ['proposal', 'strategy', 'plan options', 'explore', 'direction', 'seminar topic', '提案', '戦略', '方向性', '検討'],
111
- negative: ['commit', 'push', 'bug', 'error'],
112
- }),
113
- route('hapo:research', 'technical research or best-practice lookup', 48, {
114
- strong: ['research', 'best practice', 'tìm hiểu', 'tim hieu', 'nghiên cứu', 'nghien cuu', '調査', 'リサーチ', 'ベストプラクティス'],
115
- medium: ['documentation', 'docs', 'compare tools', 'latest docs', 'official docs', 'ドキュメント', '比較', '最新ドキュメント', '公式ドキュメント'],
116
- weak: ['investigate options', 'market scan', 'reference', 'source material', '選択肢を調べる', '参考資料', '資料'],
117
- negative: ['commit', 'push'],
118
- }),
119
- route('hapo:pptx', 'presentation or PowerPoint work', 46, {
120
- strong: ['pptx', 'powerpoint', 'slide deck', 'presentation deck', 'スライド資料', 'プレゼン資料'],
121
- medium: ['slide', 'slides', 'deck', 'presentation', 'seminar slides', 'スライド', 'プレゼン', 'セミナー資料'],
122
- weak: ['speaker notes', 'appendix', 'mục lục slide', 'muc luc slide', '発表ノート', '付録', '目次'],
123
- negative: ['source code', 'api', 'database'],
124
- }),
125
- route('hapo:agent-browser', 'browser automation with snapshot refs, web interaction, recording, or Browserbase cloud browser workflows', 45, {
126
- strong: ['agent-browser', 'browser automation', 'web automation', 'browserbase', 'cloud browser', 'snapshot refs', 'browser snapshot', 'automate browser', 'tự động trình duyệt', 'tu dong trinh duyet', 'tự động thao tác trình duyệt', 'tu dong thao tac trinh duyet', 'ブラウザ自動化', 'クラウドブラウザ', 'ブラウザ操作', 'ブラウザスナップショット'],
127
- medium: ['open url', 'navigate site', 'click in browser', 'fill form in browser', 'record browser', 'browser session', 'multi tab', 'browser test session', 'mở website', 'mo website', 'truy cập website', 'truy cap website', 'click trên web', 'click tren web', 'điền form web', 'dien form web', 'サイトを開く', 'ブラウザで開く', 'ブラウザで', 'フォーム入力', 'フォーム入力を自動化', '自動化して', 'クリック操作', '録画'],
128
- weak: ['click button', 'fill form', 'open site', 'web session', 'browser ref', 'viewport', 'cookies', 'localstorage', 'ボタンをクリック', 'ビューポート', 'クッキー'],
129
- negative: ['attached screenshot', 'ảnh đính kèm', 'anh dinh kem', '画像添付', 'source code', 'codebase', 'commit', 'push', 'pptx', 'pdf'],
130
- }),
131
- route('hapo:pdf', 'PDF reading, extraction, or generation', 44, {
132
- strong: ['pdf'], medium: ['export pdf', 'read pdf', 'extract pdf', 'pdf出力', 'pdfを読む', 'pdf抽出'], weak: ['page render', 'ページレンダー'], negative: [],
133
- }),
134
- route('hapo:docx', 'Word document work', 42, {
135
- strong: ['docx', 'word document', 'word file', 'word文書', 'wordファイル'], medium: ['word'], weak: ['tracked changes', 'document file', '変更履歴', '文書ファイル'], negative: [],
136
- }),
137
- route('hapo:xlsx', 'spreadsheet or Excel work', 42, {
138
- strong: ['xlsx', 'excel', 'spreadsheet', 'スプレッドシート'], medium: ['csv', 'sheet', 'workbook', 'シート', 'ワークブック'], weak: ['formula', 'pivot table', '数式', 'ピボットテーブル'], negative: [],
139
- }),
140
- route('hapo:ai-multimodal', 'image, video, audio, or multimodal artifact analysis', 40, {
141
- strong: ['screenshot', 'video', 'audio', 'multimodal', 'ảnh', 'anh', 'hình', 'hinh', 'スクリーンショット', '動画', '音声', '画像', '写真'],
142
- medium: ['image', 'screen capture', 'recording', 'file attached', 'イメージ', '画面キャプチャ', '録画', '添付ファイル'],
143
- weak: ['visual', 'describe image', 'ocr', 'ビジュアル', '画像説明'],
144
- negative: ['pptx', 'pdf export'],
145
- }),
146
- ];
147
-
148
- function route(skill, reason, priority, signals) {
149
- return { skill, reason, priority, signals };
150
- }
151
-
152
- function normalize(value) {
153
- return String(value || '')
154
- .normalize('NFKC')
155
- .normalize('NFD')
156
- .replace(/[\u0300-\u036f]/g, '')
157
- .normalize('NFC')
158
- .toLowerCase();
159
- }
160
-
161
- function scoreRoute(prompt, routeItem) {
162
- const normalized = normalize(prompt);
163
- const matched = [];
164
- let score = 0;
165
- for (const [bucket, weight] of Object.entries(WEIGHTS)) {
166
- for (const term of routeItem.signals[bucket] || []) {
167
- if (!normalized.includes(normalize(term))) continue;
168
- score += weight;
169
- matched.push({ bucket, term, weight });
170
- }
171
- }
172
- return { ...routeItem, score, matched, confidence: confidence(score) };
173
- }
174
-
175
- function confidence(score) {
176
- if (score >= 12) return 'high';
177
- if (score >= 7) return 'medium';
178
- if (score >= MIN_SCORE) return 'low';
179
- return 'none';
180
- }
181
-
182
- function findRoute(prompt) {
183
- const candidates = ROUTES
184
- .map((routeItem, index) => ({ ...scoreRoute(prompt, routeItem), index }))
185
- .filter((routeItem) => routeItem.score >= MIN_SCORE)
186
- .sort((a, b) => b.score - a.score || b.priority - a.priority || a.index - b.index);
187
- return candidates[0] || null;
188
- }
189
-
190
- module.exports = {
191
- MIN_SCORE,
192
- ROUTES,
193
- findRoute,
194
- normalize,
195
- scoreRoute,
196
- };
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Copyright (c) 2026 Haposoft. MIT License.
4
- *
5
- * UserPromptSubmit Hook - skill-router.cjs
6
- *
7
- * Adds a deterministic CafeKit skill suggestion for natural-language prompts.
8
- * It never overrides explicit slash commands; it only injects a short routing hint.
9
- *
10
- * Exit: 0 always (fail-open)
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
-
16
- function logCrash(error) {
17
- try {
18
- const dir = path.join(__dirname, '.logs');
19
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
20
- fs.appendFileSync(
21
- path.join(dir, 'hook-log.jsonl'),
22
- JSON.stringify({
23
- ts: new Date().toISOString(),
24
- hook: 'skill-router',
25
- status: 'crash',
26
- error: error.message,
27
- }) + '\n'
28
- );
29
- } catch (_) {}
30
- }
31
-
32
- try {
33
- const { isHookEnabled } = require('./lib/config.cjs');
34
- const { findRoute } = require('./lib/skill-router-routes.cjs');
35
-
36
- function isExplicitCommand(prompt) {
37
- const trimmed = prompt.trim();
38
- return trimmed.startsWith('/') || /^hapo:[a-z-]+/i.test(trimmed);
39
- }
40
-
41
- const stdin = fs.readFileSync(0, 'utf8').trim();
42
- if (!stdin) process.exit(0);
43
-
44
- const payload = JSON.parse(stdin);
45
- const cwd = payload.cwd || process.cwd();
46
- const prompt = payload.prompt || '';
47
- if (!isHookEnabled('skill-router', { cwd })) process.exit(0);
48
- if (!prompt || isExplicitCommand(prompt)) process.exit(0);
49
-
50
- const route = findRoute(prompt);
51
- if (!route) process.exit(0);
52
-
53
- const skillDir = route.skill.replace(/^hapo:/, '');
54
- const lines = [
55
- '## CafeKit Skill Router',
56
- `- Suggested skill: \`${route.skill}\``,
57
- `- Why: ${route.reason}.`,
58
- `- Confidence: ${route.confidence} (score ${route.score}).`,
59
- `- Action: before acting, read \`.claude/skills/${skillDir}/SKILL.md\` and follow that workflow.`,
60
- '- If the user explicitly names another workflow or asks for direct answering only, follow the user request.',
61
- ];
62
-
63
- console.log(lines.join('\n'));
64
- process.exit(0);
65
- } catch (error) {
66
- try { logCrash(error); } catch (_) {}
67
- process.exit(0);
68
- }