@hailer/mcp 0.0.1

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.
Files changed (163) hide show
  1. package/.claude/commands/tool-builder.md +37 -0
  2. package/.claude/commands/ws-pull.md +44 -0
  3. package/.claude/settings.json +8 -0
  4. package/.claude/settings.local.json +49 -0
  5. package/.claude/skills/activity-api/SKILL.md +96 -0
  6. package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
  7. package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
  8. package/.claude/skills/agent-building/SKILL.md +243 -0
  9. package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
  10. package/.claude/skills/agent-building/references/code-examples.md +587 -0
  11. package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
  12. package/.claude/skills/app-api/SKILL.md +219 -0
  13. package/.claude/skills/app-api/references/app-endpoints.md +759 -0
  14. package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
  15. package/.claude/skills/create-app-skill/SKILL.md +1101 -0
  16. package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
  17. package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
  18. package/.claude/skills/hailer-api/SKILL.md +283 -0
  19. package/.claude/skills/hailer-api/references/activities.md +620 -0
  20. package/.claude/skills/hailer-api/references/authentication.md +216 -0
  21. package/.claude/skills/hailer-api/references/datasets.md +437 -0
  22. package/.claude/skills/hailer-api/references/files.md +301 -0
  23. package/.claude/skills/hailer-api/references/insights.md +469 -0
  24. package/.claude/skills/hailer-api/references/workflows.md +720 -0
  25. package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
  26. package/.claude/skills/insight-api/SKILL.md +185 -0
  27. package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
  28. package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
  29. package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
  30. package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
  31. package/.claude/skills/local-first-skill/SKILL.md +570 -0
  32. package/.claude/skills/mcp-tools/SKILL.md +419 -0
  33. package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
  34. package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
  35. package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
  36. package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
  37. package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
  38. package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
  39. package/.claude/skills/remove-app-skill/SKILL.md +985 -0
  40. package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
  41. package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
  42. package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
  43. package/.claude/skills/skill-testing/README.md +137 -0
  44. package/.claude/skills/skill-testing/SKILL.md +348 -0
  45. package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
  46. package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
  47. package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
  48. package/.claude/skills/tool-builder/SKILL.md +328 -0
  49. package/.claude/skills/update-app-skill/SKILL.md +970 -0
  50. package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
  51. package/.env.example +81 -0
  52. package/.mcp.json +13 -0
  53. package/README.md +297 -0
  54. package/dist/app.d.ts +4 -0
  55. package/dist/app.js +74 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +5 -0
  58. package/dist/client/adaptive-documentation-bot.d.ts +108 -0
  59. package/dist/client/adaptive-documentation-bot.js +475 -0
  60. package/dist/client/adaptive-documentation-types.d.ts +66 -0
  61. package/dist/client/adaptive-documentation-types.js +9 -0
  62. package/dist/client/agent-activity-bot.d.ts +51 -0
  63. package/dist/client/agent-activity-bot.js +166 -0
  64. package/dist/client/agent-tracker.d.ts +499 -0
  65. package/dist/client/agent-tracker.js +659 -0
  66. package/dist/client/description-updater.d.ts +56 -0
  67. package/dist/client/description-updater.js +259 -0
  68. package/dist/client/log-parser.d.ts +72 -0
  69. package/dist/client/log-parser.js +387 -0
  70. package/dist/client/mcp-client.d.ts +50 -0
  71. package/dist/client/mcp-client.js +532 -0
  72. package/dist/client/message-processor.d.ts +35 -0
  73. package/dist/client/message-processor.js +352 -0
  74. package/dist/client/multi-bot-manager.d.ts +24 -0
  75. package/dist/client/multi-bot-manager.js +74 -0
  76. package/dist/client/providers/anthropic-provider.d.ts +19 -0
  77. package/dist/client/providers/anthropic-provider.js +631 -0
  78. package/dist/client/providers/llm-provider.d.ts +47 -0
  79. package/dist/client/providers/llm-provider.js +367 -0
  80. package/dist/client/providers/openai-provider.d.ts +23 -0
  81. package/dist/client/providers/openai-provider.js +621 -0
  82. package/dist/client/simple-llm-caller.d.ts +19 -0
  83. package/dist/client/simple-llm-caller.js +100 -0
  84. package/dist/client/skill-generator.d.ts +81 -0
  85. package/dist/client/skill-generator.js +386 -0
  86. package/dist/client/test-adaptive-bot.d.ts +9 -0
  87. package/dist/client/test-adaptive-bot.js +82 -0
  88. package/dist/client/token-pricing.d.ts +38 -0
  89. package/dist/client/token-pricing.js +127 -0
  90. package/dist/client/token-tracker.d.ts +232 -0
  91. package/dist/client/token-tracker.js +457 -0
  92. package/dist/client/token-usage-bot.d.ts +53 -0
  93. package/dist/client/token-usage-bot.js +153 -0
  94. package/dist/client/tool-executor.d.ts +69 -0
  95. package/dist/client/tool-executor.js +159 -0
  96. package/dist/client/tool-schema-loader.d.ts +60 -0
  97. package/dist/client/tool-schema-loader.js +178 -0
  98. package/dist/client/types.d.ts +69 -0
  99. package/dist/client/types.js +7 -0
  100. package/dist/config.d.ts +162 -0
  101. package/dist/config.js +296 -0
  102. package/dist/core.d.ts +26 -0
  103. package/dist/core.js +147 -0
  104. package/dist/lib/context-manager.d.ts +111 -0
  105. package/dist/lib/context-manager.js +431 -0
  106. package/dist/lib/logger.d.ts +74 -0
  107. package/dist/lib/logger.js +277 -0
  108. package/dist/lib/materialize.d.ts +3 -0
  109. package/dist/lib/materialize.js +101 -0
  110. package/dist/lib/normalizedName.d.ts +7 -0
  111. package/dist/lib/normalizedName.js +48 -0
  112. package/dist/lib/prompt-length-manager.d.ts +81 -0
  113. package/dist/lib/prompt-length-manager.js +457 -0
  114. package/dist/lib/terminal-prompt.d.ts +9 -0
  115. package/dist/lib/terminal-prompt.js +108 -0
  116. package/dist/mcp/UserContextCache.d.ts +56 -0
  117. package/dist/mcp/UserContextCache.js +163 -0
  118. package/dist/mcp/auth.d.ts +2 -0
  119. package/dist/mcp/auth.js +29 -0
  120. package/dist/mcp/hailer-clients.d.ts +42 -0
  121. package/dist/mcp/hailer-clients.js +246 -0
  122. package/dist/mcp/signal-handler.d.ts +45 -0
  123. package/dist/mcp/signal-handler.js +317 -0
  124. package/dist/mcp/tool-registry.d.ts +100 -0
  125. package/dist/mcp/tool-registry.js +306 -0
  126. package/dist/mcp/tools/activity.d.ts +15 -0
  127. package/dist/mcp/tools/activity.js +955 -0
  128. package/dist/mcp/tools/app.d.ts +20 -0
  129. package/dist/mcp/tools/app.js +1488 -0
  130. package/dist/mcp/tools/discussion.d.ts +19 -0
  131. package/dist/mcp/tools/discussion.js +950 -0
  132. package/dist/mcp/tools/file.d.ts +15 -0
  133. package/dist/mcp/tools/file.js +119 -0
  134. package/dist/mcp/tools/insight.d.ts +17 -0
  135. package/dist/mcp/tools/insight.js +806 -0
  136. package/dist/mcp/tools/skill.d.ts +10 -0
  137. package/dist/mcp/tools/skill.js +279 -0
  138. package/dist/mcp/tools/user.d.ts +10 -0
  139. package/dist/mcp/tools/user.js +108 -0
  140. package/dist/mcp/tools/workflow-template.d.ts +19 -0
  141. package/dist/mcp/tools/workflow-template.js +822 -0
  142. package/dist/mcp/tools/workflow.d.ts +18 -0
  143. package/dist/mcp/tools/workflow.js +1362 -0
  144. package/dist/mcp/utils/api-errors.d.ts +45 -0
  145. package/dist/mcp/utils/api-errors.js +160 -0
  146. package/dist/mcp/utils/data-transformers.d.ts +102 -0
  147. package/dist/mcp/utils/data-transformers.js +194 -0
  148. package/dist/mcp/utils/file-upload.d.ts +33 -0
  149. package/dist/mcp/utils/file-upload.js +148 -0
  150. package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
  151. package/dist/mcp/utils/hailer-api-client.js +323 -0
  152. package/dist/mcp/utils/index.d.ts +13 -0
  153. package/dist/mcp/utils/index.js +39 -0
  154. package/dist/mcp/utils/logger.d.ts +42 -0
  155. package/dist/mcp/utils/logger.js +103 -0
  156. package/dist/mcp/utils/types.d.ts +286 -0
  157. package/dist/mcp/utils/types.js +7 -0
  158. package/dist/mcp/workspace-cache.d.ts +42 -0
  159. package/dist/mcp/workspace-cache.js +97 -0
  160. package/dist/mcp-server.d.ts +42 -0
  161. package/dist/mcp-server.js +280 -0
  162. package/package.json +56 -0
  163. package/tsconfig.json +23 -0
@@ -0,0 +1,705 @@
1
+ # Reusable Test Patterns
2
+
3
+ Copy-paste templates and patterns for common skill testing scenarios.
4
+
5
+ ## Quick Test Script Template
6
+
7
+ Save as `test-{skill-name}.ts`:
8
+
9
+ ```typescript
10
+ /**
11
+ * Quick validation test for {skill-name}
12
+ * Run: npx tsx test-{skill-name}.ts
13
+ */
14
+
15
+ import { SkillLoader } from './src/client/skill-loader';
16
+ import { SkillManager } from './src/client/skill-manager';
17
+ import * as path from 'path';
18
+ import * as fs from 'fs/promises';
19
+
20
+ const SKILL_NAME = 'my-skill';
21
+ const TEST_QUERIES = [
22
+ 'primary keyword test',
23
+ 'secondary keyword test',
24
+ 'edge case test',
25
+ ];
26
+
27
+ async function testSkill() {
28
+ console.log(`\n๐Ÿงช Testing Skill: ${SKILL_NAME}\n`);
29
+
30
+ const skillsPath = path.join(process.cwd(), '..', '.claude', 'skills');
31
+ const skillPath = path.join(skillsPath, SKILL_NAME);
32
+ const loader = new SkillLoader(skillsPath);
33
+ const manager = new SkillManager(loader);
34
+
35
+ let passed = 0;
36
+ let failed = 0;
37
+
38
+ // Test 1: File Structure
39
+ console.log('๐Ÿ“ Test 1: File Structure');
40
+ try {
41
+ const skillMd = await fs.readFile(path.join(skillPath, 'SKILL.md'), 'utf-8');
42
+ console.log(` โœ… SKILL.md exists (${skillMd.length} chars)`);
43
+ passed++;
44
+ } catch (error) {
45
+ console.log(` โŒ SKILL.md not found`);
46
+ failed++;
47
+ }
48
+
49
+ // Test 2: Load Skill
50
+ console.log('\n๐Ÿ“š Test 2: Skill Loading');
51
+ try {
52
+ const start = Date.now();
53
+ const skill = await loader.load(SKILL_NAME);
54
+ const loadTime = Date.now() - start;
55
+
56
+ console.log(` โœ… Loaded successfully in ${loadTime}ms`);
57
+ console.log(` โœ… Content size: ${(skill.content.length / 1024).toFixed(1)} KB`);
58
+ console.log(` โœ… Description: ${skill.description.substring(0, 60)}...`);
59
+ passed += 3;
60
+ } catch (error) {
61
+ console.log(` โŒ Failed to load: ${error}`);
62
+ failed++;
63
+ }
64
+
65
+ // Test 3: Caching
66
+ console.log('\nโšก Test 3: Caching');
67
+ try {
68
+ const start = Date.now();
69
+ await loader.load(SKILL_NAME);
70
+ const cacheTime = Date.now() - start;
71
+
72
+ if (cacheTime < 10) {
73
+ console.log(` โœ… Cached load: ${cacheTime}ms (fast!)`);
74
+ passed++;
75
+ } else {
76
+ console.log(` โš ๏ธ Cached load: ${cacheTime}ms (should be < 10ms)`);
77
+ failed++;
78
+ }
79
+ } catch (error) {
80
+ console.log(` โŒ Cache test failed: ${error}`);
81
+ failed++;
82
+ }
83
+
84
+ // Test 4: Keyword Matching
85
+ console.log('\n๐Ÿ” Test 4: Keyword Matching');
86
+ for (const query of TEST_QUERIES) {
87
+ try {
88
+ const guidance = await manager.analyzeRequest(query);
89
+
90
+ if (guidance.skills.includes(SKILL_NAME)) {
91
+ console.log(` โœ… "${query}" โ†’ Matched (${(guidance.confidence * 100).toFixed(0)}%)`);
92
+ passed++;
93
+ } else {
94
+ console.log(` โŒ "${query}" โ†’ No match`);
95
+ failed++;
96
+ }
97
+ } catch (error) {
98
+ console.log(` โŒ "${query}" โ†’ Error: ${error}`);
99
+ failed++;
100
+ }
101
+ }
102
+
103
+ // Test 5: Content Quality
104
+ console.log('\n๐Ÿ“ Test 5: Content Quality');
105
+ try {
106
+ const skill = await loader.load(SKILL_NAME);
107
+ const content = skill.content;
108
+
109
+ const checks = [
110
+ { name: 'Has headings', test: () => content.includes('#'), },
111
+ { name: 'Has examples', test: () => content.includes('```') },
112
+ { name: 'Not too short', test: () => content.length > 500 },
113
+ { name: 'Not too long', test: () => content.length < 100000 },
114
+ { name: 'Has structure', test: () => content.includes('##') },
115
+ ];
116
+
117
+ for (const check of checks) {
118
+ if (check.test()) {
119
+ console.log(` โœ… ${check.name}`);
120
+ passed++;
121
+ } else {
122
+ console.log(` โŒ ${check.name}`);
123
+ failed++;
124
+ }
125
+ }
126
+ } catch (error) {
127
+ console.log(` โŒ Quality checks failed: ${error}`);
128
+ failed++;
129
+ }
130
+
131
+ // Summary
132
+ console.log('\n' + '='.repeat(50));
133
+ console.log(`Results: ${passed} passed, ${failed} failed`);
134
+ console.log('='.repeat(50) + '\n');
135
+
136
+ if (failed === 0) {
137
+ console.log(`โœ… ${SKILL_NAME} is ready to use!\n`);
138
+ process.exit(0);
139
+ } else {
140
+ console.log(`โŒ ${SKILL_NAME} needs fixes before deployment\n`);
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ testSkill().catch((error) => {
146
+ console.error('\nโŒ Test crashed:', error);
147
+ process.exit(1);
148
+ });
149
+ ```
150
+
151
+ ## Pattern: Test All Skills
152
+
153
+ Save as `test-all-skills.ts`:
154
+
155
+ ```typescript
156
+ /**
157
+ * Test all skills in the system
158
+ * Run: npx tsx test-all-skills.ts
159
+ */
160
+
161
+ import { SkillLoader } from './src/client/skill-loader';
162
+ import { SkillManager } from './src/client/skill-manager';
163
+ import * as path from 'path';
164
+
165
+ interface SkillTestResult {
166
+ skillName: string;
167
+ passed: boolean;
168
+ loadTime: number;
169
+ cacheTime: number;
170
+ sizeKB: number;
171
+ errors: string[];
172
+ }
173
+
174
+ async function testSkill(
175
+ skillName: string,
176
+ loader: SkillLoader,
177
+ manager: SkillManager
178
+ ): Promise<SkillTestResult> {
179
+ const result: SkillTestResult = {
180
+ skillName,
181
+ passed: true,
182
+ loadTime: 0,
183
+ cacheTime: 0,
184
+ sizeKB: 0,
185
+ errors: [],
186
+ };
187
+
188
+ try {
189
+ // Test loading
190
+ loader.clearCache(skillName);
191
+ const start1 = Date.now();
192
+ const skill = await loader.load(skillName);
193
+ result.loadTime = Date.now() - start1;
194
+ result.sizeKB = skill.content.length / 1024;
195
+
196
+ // Test caching
197
+ const start2 = Date.now();
198
+ await loader.load(skillName);
199
+ result.cacheTime = Date.now() - start2;
200
+
201
+ // Validations
202
+ if (result.loadTime > 150) {
203
+ result.errors.push(`Load time too high: ${result.loadTime}ms`);
204
+ result.passed = false;
205
+ }
206
+
207
+ if (result.cacheTime > 10) {
208
+ result.errors.push(`Cache time too high: ${result.cacheTime}ms`);
209
+ result.passed = false;
210
+ }
211
+
212
+ if (result.sizeKB > 100) {
213
+ result.errors.push(`Size too large: ${result.sizeKB.toFixed(1)} KB`);
214
+ result.passed = false;
215
+ }
216
+
217
+ if (skill.content.length < 500) {
218
+ result.errors.push('Content too short');
219
+ result.passed = false;
220
+ }
221
+ } catch (error) {
222
+ result.passed = false;
223
+ result.errors.push(String(error));
224
+ }
225
+
226
+ return result;
227
+ }
228
+
229
+ async function testAllSkills() {
230
+ console.log('๐Ÿงช Testing All Skills\n');
231
+
232
+ const skillsPath = path.join(process.cwd(), '..', '.claude', 'skills');
233
+ const loader = new SkillLoader(skillsPath);
234
+ const manager = new SkillManager(loader);
235
+
236
+ const allSkills = await loader.getAvailableSkills();
237
+ console.log(`Found ${allSkills.length} skills to test\n`);
238
+
239
+ const results: SkillTestResult[] = [];
240
+
241
+ for (const skillName of allSkills) {
242
+ process.stdout.write(`Testing ${skillName}... `);
243
+ const result = await testSkill(skillName, loader, manager);
244
+ results.push(result);
245
+
246
+ if (result.passed) {
247
+ console.log('โœ…');
248
+ } else {
249
+ console.log('โŒ');
250
+ result.errors.forEach(err => console.log(` - ${err}`));
251
+ }
252
+ }
253
+
254
+ // Summary
255
+ console.log('\n' + '='.repeat(70));
256
+ console.log('Summary:');
257
+ console.log('='.repeat(70));
258
+
259
+ const passed = results.filter(r => r.passed).length;
260
+ const failed = results.filter(r => !r.passed).length;
261
+
262
+ console.log(`\nSkills: ${passed} passed, ${failed} failed\n`);
263
+
264
+ console.log('Performance Metrics:');
265
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
266
+ console.log('Skill Name'.padEnd(25) + 'Load'.padEnd(15) + 'Cache'.padEnd(15) + 'Size');
267
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
268
+
269
+ for (const result of results) {
270
+ const status = result.passed ? 'โœ…' : 'โŒ';
271
+ const name = `${status} ${result.skillName}`.padEnd(25);
272
+ const load = `${result.loadTime}ms`.padEnd(15);
273
+ const cache = `${result.cacheTime}ms`.padEnd(15);
274
+ const size = `${result.sizeKB.toFixed(1)} KB`;
275
+
276
+ console.log(name + load + cache + size);
277
+ }
278
+
279
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n');
280
+
281
+ // Exit with error if any failed
282
+ process.exit(failed > 0 ? 1 : 0);
283
+ }
284
+
285
+ testAllSkills().catch(console.error);
286
+ ```
287
+
288
+ ## Pattern: Keyword Matching Tests
289
+
290
+ Save as `test-keywords.ts`:
291
+
292
+ ```typescript
293
+ /**
294
+ * Test keyword matching accuracy
295
+ * Run: npx tsx test-keywords.ts
296
+ */
297
+
298
+ import { SkillManager } from './src/client/skill-manager';
299
+ import { SkillLoader } from './src/client/skill-loader';
300
+ import * as path from 'path';
301
+
302
+ interface KeywordTest {
303
+ query: string;
304
+ expectedSkill: string;
305
+ minConfidence?: number;
306
+ }
307
+
308
+ const KEYWORD_TESTS: KeywordTest[] = [
309
+ // MCP Tools
310
+ { query: 'create a workflow', expectedSkill: 'mcp-tools', minConfidence: 0.6 },
311
+ { query: 'list workflow phases', expectedSkill: 'mcp-tools', minConfidence: 0.6 },
312
+ { query: 'update workflow field', expectedSkill: 'mcp-tools', minConfidence: 0.6 },
313
+ { query: 'show me the schema', expectedSkill: 'mcp-tools', minConfidence: 0.5 },
314
+
315
+ // Hailer API
316
+ { query: 'list all activities', expectedSkill: 'hailer-api', minConfidence: 0.6 },
317
+ { query: 'filter activities by date', expectedSkill: 'hailer-api', minConfidence: 0.6 },
318
+ { query: 'search for tasks', expectedSkill: 'hailer-api', minConfidence: 0.5 },
319
+ { query: 'show activity details', expectedSkill: 'hailer-api', minConfidence: 0.5 },
320
+
321
+ // Agent Building
322
+ { query: 'how do I build an agent?', expectedSkill: 'agent-building', minConfidence: 0.5 },
323
+ { query: 'create skill system', expectedSkill: 'agent-building', minConfidence: 0.5 },
324
+ { query: 'multi-agent architecture', expectedSkill: 'agent-building', minConfidence: 0.5 },
325
+
326
+ // Skill Testing
327
+ { query: 'how do I test skills?', expectedSkill: 'skill-testing', minConfidence: 0.5 },
328
+ { query: 'validate skill quality', expectedSkill: 'skill-testing', minConfidence: 0.5 },
329
+ { query: 'skill testing patterns', expectedSkill: 'skill-testing', minConfidence: 0.6 },
330
+ ];
331
+
332
+ async function testKeywordMatching() {
333
+ console.log('๐Ÿ” Testing Keyword Matching\n');
334
+
335
+ const skillsPath = path.join(process.cwd(), '..', '.claude', 'skills');
336
+ const loader = new SkillLoader(skillsPath);
337
+ const manager = new SkillManager(loader);
338
+
339
+ let passed = 0;
340
+ let failed = 0;
341
+
342
+ for (const test of KEYWORD_TESTS) {
343
+ process.stdout.write(`Testing: "${test.query.substring(0, 40)}"... `);
344
+
345
+ try {
346
+ const guidance = await manager.analyzeRequest(test.query);
347
+ const matchedSkill = guidance.skills[0];
348
+ const confidence = guidance.confidence;
349
+ const minConf = test.minConfidence || 0.5;
350
+
351
+ const skillMatches = matchedSkill === test.expectedSkill;
352
+ const confEnough = confidence >= minConf;
353
+
354
+ if (skillMatches && confEnough) {
355
+ console.log(`โœ… ${test.expectedSkill} (${(confidence * 100).toFixed(0)}%)`);
356
+ passed++;
357
+ } else if (skillMatches && !confEnough) {
358
+ console.log(`โš ๏ธ ${test.expectedSkill} but low confidence (${(confidence * 100).toFixed(0)}%)`);
359
+ failed++;
360
+ } else {
361
+ console.log(`โŒ Got ${matchedSkill || 'none'}, expected ${test.expectedSkill}`);
362
+ failed++;
363
+ }
364
+ } catch (error) {
365
+ console.log(`โŒ Error: ${error}`);
366
+ failed++;
367
+ }
368
+ }
369
+
370
+ // Summary
371
+ console.log('\n' + '='.repeat(50));
372
+ const total = passed + failed;
373
+ const accuracy = (passed / total * 100).toFixed(1);
374
+ console.log(`Results: ${passed}/${total} passed (${accuracy}% accuracy)`);
375
+ console.log('='.repeat(50) + '\n');
376
+
377
+ process.exit(failed > 0 ? 1 : 0);
378
+ }
379
+
380
+ testKeywordMatching().catch(console.error);
381
+ ```
382
+
383
+ ## Pattern: Performance Benchmarks
384
+
385
+ Save as `benchmark-skills.ts`:
386
+
387
+ ```typescript
388
+ /**
389
+ * Benchmark skill system performance
390
+ * Run: npx tsx benchmark-skills.ts
391
+ */
392
+
393
+ import { SkillLoader } from './src/client/skill-loader';
394
+ import { SkillManager } from './src/client/skill-manager';
395
+ import * as path from 'path';
396
+
397
+ interface BenchmarkResult {
398
+ operation: string;
399
+ iterations: number;
400
+ totalTime: number;
401
+ avgTime: number;
402
+ minTime: number;
403
+ maxTime: number;
404
+ }
405
+
406
+ async function benchmark(
407
+ name: string,
408
+ fn: () => Promise<void>,
409
+ iterations: number
410
+ ): Promise<BenchmarkResult> {
411
+ const times: number[] = [];
412
+
413
+ // Warmup
414
+ await fn();
415
+
416
+ // Benchmark
417
+ for (let i = 0; i < iterations; i++) {
418
+ const start = performance.now();
419
+ await fn();
420
+ times.push(performance.now() - start);
421
+ }
422
+
423
+ const totalTime = times.reduce((a, b) => a + b, 0);
424
+ const avgTime = totalTime / iterations;
425
+ const minTime = Math.min(...times);
426
+ const maxTime = Math.max(...times);
427
+
428
+ return {
429
+ operation: name,
430
+ iterations,
431
+ totalTime,
432
+ avgTime,
433
+ minTime,
434
+ maxTime,
435
+ };
436
+ }
437
+
438
+ function printResult(result: BenchmarkResult) {
439
+ console.log(`\n${result.operation}`);
440
+ console.log('โ”€'.repeat(50));
441
+ console.log(`Iterations: ${result.iterations}`);
442
+ console.log(`Total time: ${result.totalTime.toFixed(2)}ms`);
443
+ console.log(`Average: ${result.avgTime.toFixed(2)}ms`);
444
+ console.log(`Min: ${result.minTime.toFixed(2)}ms`);
445
+ console.log(`Max: ${result.maxTime.toFixed(2)}ms`);
446
+ }
447
+
448
+ async function runBenchmarks() {
449
+ console.log('โšก Skill System Performance Benchmarks\n');
450
+
451
+ const skillsPath = path.join(process.cwd(), '..', '.claude', 'skills');
452
+ const loader = new SkillLoader(skillsPath);
453
+ const manager = new SkillManager(loader);
454
+
455
+ // Benchmark 1: First load (uncached)
456
+ const firstLoadResult = await benchmark(
457
+ 'First Load (from disk)',
458
+ async () => {
459
+ loader.clearCache('mcp-tools');
460
+ await loader.load('mcp-tools');
461
+ },
462
+ 10
463
+ );
464
+ printResult(firstLoadResult);
465
+
466
+ // Benchmark 2: Cached load
467
+ await loader.load('mcp-tools'); // Ensure cached
468
+ const cachedLoadResult = await benchmark(
469
+ 'Cached Load (from memory)',
470
+ async () => {
471
+ await loader.load('mcp-tools');
472
+ },
473
+ 100
474
+ );
475
+ printResult(cachedLoadResult);
476
+
477
+ // Benchmark 3: Keyword analysis
478
+ const analysisResult = await benchmark(
479
+ 'Keyword Analysis',
480
+ async () => {
481
+ await manager.analyzeRequest('create a workflow with phases');
482
+ },
483
+ 100
484
+ );
485
+ printResult(analysisResult);
486
+
487
+ // Benchmark 4: Full flow (analysis + load)
488
+ const fullFlowResult = await benchmark(
489
+ 'Full Flow (analysis + load)',
490
+ async () => {
491
+ const guidance = await manager.analyzeRequest('create workflow');
492
+ // Skill is already loaded during analysis
493
+ },
494
+ 100
495
+ );
496
+ printResult(fullFlowResult);
497
+
498
+ // Summary
499
+ console.log('\n' + '='.repeat(50));
500
+ console.log('Performance Summary:');
501
+ console.log('='.repeat(50));
502
+ console.log(`First load: ${firstLoadResult.avgTime.toFixed(2)}ms avg (target: < 100ms)`);
503
+ console.log(`Cached load: ${cachedLoadResult.avgTime.toFixed(2)}ms avg (target: < 10ms)`);
504
+ console.log(`Analysis: ${analysisResult.avgTime.toFixed(2)}ms avg (target: < 5ms)`);
505
+ console.log(`Full flow: ${fullFlowResult.avgTime.toFixed(2)}ms avg (target: < 50ms)`);
506
+ console.log('='.repeat(50) + '\n');
507
+
508
+ // Check if targets are met
509
+ const targets = [
510
+ { name: 'First load', actual: firstLoadResult.avgTime, target: 100 },
511
+ { name: 'Cached load', actual: cachedLoadResult.avgTime, target: 10 },
512
+ { name: 'Analysis', actual: analysisResult.avgTime, target: 5 },
513
+ { name: 'Full flow', actual: fullFlowResult.avgTime, target: 50 },
514
+ ];
515
+
516
+ const allPassed = targets.every(t => t.actual <= t.target);
517
+
518
+ if (allPassed) {
519
+ console.log('โœ… All performance targets met!\n');
520
+ process.exit(0);
521
+ } else {
522
+ console.log('โŒ Some performance targets not met:\n');
523
+ targets.forEach(t => {
524
+ if (t.actual > t.target) {
525
+ console.log(` โš ๏ธ ${t.name}: ${t.actual.toFixed(2)}ms (target: ${t.target}ms)`);
526
+ }
527
+ });
528
+ console.log();
529
+ process.exit(1);
530
+ }
531
+ }
532
+
533
+ runBenchmarks().catch(console.error);
534
+ ```
535
+
536
+ ## Pattern: Integration with Jest
537
+
538
+ If using Jest for testing, here's the pattern:
539
+
540
+ ```typescript
541
+ // skills.test.ts
542
+ import { SkillLoader } from '../src/client/skill-loader';
543
+ import { SkillManager } from '../src/client/skill-manager';
544
+ import * as path from 'path';
545
+
546
+ describe('Skill System', () => {
547
+ let loader: SkillLoader;
548
+ let manager: SkillManager;
549
+
550
+ beforeAll(() => {
551
+ const skillsPath = path.join(__dirname, '../../.claude/skills');
552
+ loader = new SkillLoader(skillsPath);
553
+ manager = new SkillManager(loader);
554
+ });
555
+
556
+ afterEach(() => {
557
+ loader.clearCache();
558
+ });
559
+
560
+ describe('SkillLoader', () => {
561
+ it('should load skill successfully', async () => {
562
+ const skill = await loader.load('mcp-tools');
563
+ expect(skill.name).toBe('mcp-tools');
564
+ expect(skill.content.length).toBeGreaterThan(0);
565
+ });
566
+
567
+ it('should cache skills', async () => {
568
+ await loader.load('mcp-tools');
569
+ const start = performance.now();
570
+ await loader.load('mcp-tools');
571
+ const time = performance.now() - start;
572
+ expect(time).toBeLessThan(10);
573
+ });
574
+
575
+ it('should handle errors', async () => {
576
+ await expect(loader.load('invalid-skill')).rejects.toThrow();
577
+ });
578
+ });
579
+
580
+ describe('SkillManager', () => {
581
+ it('should match keywords', async () => {
582
+ const guidance = await manager.analyzeRequest('create workflow');
583
+ expect(guidance.skills).toContain('mcp-tools');
584
+ });
585
+
586
+ it('should return default for non-matching queries', async () => {
587
+ const guidance = await manager.analyzeRequest('xyz123random');
588
+ expect(guidance.skills).toHaveLength(0);
589
+ expect(guidance.confidence).toBeLessThan(0.5);
590
+ });
591
+ });
592
+ });
593
+ ```
594
+
595
+ ## Pattern: CI/CD Integration
596
+
597
+ ```yaml
598
+ # .github/workflows/test-skills.yml
599
+ name: Test Skills
600
+
601
+ on:
602
+ push:
603
+ paths:
604
+ - '.claude/skills/**'
605
+ - 'server/src/client/skill-*.ts'
606
+ pull_request:
607
+ paths:
608
+ - '.claude/skills/**'
609
+ - 'server/src/client/skill-*.ts'
610
+
611
+ jobs:
612
+ test-skills:
613
+ runs-on: ubuntu-latest
614
+ steps:
615
+ - uses: actions/checkout@v3
616
+
617
+ - uses: actions/setup-node@v3
618
+ with:
619
+ node-version: '18'
620
+
621
+ - name: Install dependencies
622
+ working-directory: ./server
623
+ run: npm install
624
+
625
+ - name: Run skill tests
626
+ working-directory: ./server
627
+ run: npx tsx test-all-skills.ts
628
+
629
+ - name: Run keyword tests
630
+ working-directory: ./server
631
+ run: npx tsx test-keywords.ts
632
+
633
+ - name: Run performance benchmarks
634
+ working-directory: ./server
635
+ run: npx tsx benchmark-skills.ts
636
+ ```
637
+
638
+ ## Pattern: Watch Mode for Development
639
+
640
+ ```typescript
641
+ /**
642
+ * Watch mode for skill development
643
+ * Run: npx tsx watch-skills.ts
644
+ *
645
+ * Automatically re-tests skills when files change
646
+ */
647
+
648
+ import * as chokidar from 'chokidar';
649
+ import { exec } from 'child_process';
650
+ import * as path from 'path';
651
+
652
+ const skillsPath = path.join(process.cwd(), '..', '.claude', 'skills');
653
+
654
+ console.log('๐Ÿ‘€ Watching skills directory for changes...\n');
655
+
656
+ const watcher = chokidar.watch(skillsPath, {
657
+ ignored: /(^|[\/\\])\../, // ignore dotfiles
658
+ persistent: true,
659
+ });
660
+
661
+ let testTimeout: NodeJS.Timeout;
662
+
663
+ function runTests() {
664
+ console.log('\n๐Ÿงช Running tests...\n');
665
+ exec('npx tsx test-all-skills.ts', (error, stdout, stderr) => {
666
+ if (stdout) console.log(stdout);
667
+ if (stderr) console.error(stderr);
668
+ console.log('\n๐Ÿ‘€ Watching for changes...\n');
669
+ });
670
+ }
671
+
672
+ watcher.on('change', (filePath) => {
673
+ console.log(`\n๐Ÿ“ Changed: ${path.relative(skillsPath, filePath)}`);
674
+
675
+ // Debounce: wait 500ms before running tests
676
+ clearTimeout(testTimeout);
677
+ testTimeout = setTimeout(runTests, 500);
678
+ });
679
+
680
+ watcher.on('add', (filePath) => {
681
+ console.log(`\nโž• Added: ${path.relative(skillsPath, filePath)}`);
682
+ clearTimeout(testTimeout);
683
+ testTimeout = setTimeout(runTests, 500);
684
+ });
685
+
686
+ // Initial test run
687
+ runTests();
688
+ ```
689
+
690
+ ## Usage Tips
691
+
692
+ 1. **Start with the quick test script** - Fastest way to validate a new skill
693
+ 2. **Use test-all-skills** - Before committing changes to the skill system
694
+ 3. **Run keyword tests** - When adding new skills or modifying keyword mappings
695
+ 4. **Benchmark regularly** - Catch performance regressions early
696
+ 5. **Use watch mode** - During active skill development for instant feedback
697
+
698
+ ## Next Steps
699
+
700
+ After using these patterns:
701
+ 1. Customize tests for your specific skills
702
+ 2. Add skill-specific validation logic
703
+ 3. Integrate with your CI/CD pipeline
704
+ 4. Set up monitoring in production
705
+ 5. Collect feedback and iterate