@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.
- package/.claude/commands/tool-builder.md +37 -0
- package/.claude/commands/ws-pull.md +44 -0
- package/.claude/settings.json +8 -0
- package/.claude/settings.local.json +49 -0
- package/.claude/skills/activity-api/SKILL.md +96 -0
- package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
- package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
- package/.claude/skills/agent-building/SKILL.md +243 -0
- package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
- package/.claude/skills/agent-building/references/code-examples.md +587 -0
- package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
- package/.claude/skills/app-api/SKILL.md +219 -0
- package/.claude/skills/app-api/references/app-endpoints.md +759 -0
- package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
- package/.claude/skills/create-app-skill/SKILL.md +1101 -0
- package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
- package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
- package/.claude/skills/hailer-api/SKILL.md +283 -0
- package/.claude/skills/hailer-api/references/activities.md +620 -0
- package/.claude/skills/hailer-api/references/authentication.md +216 -0
- package/.claude/skills/hailer-api/references/datasets.md +437 -0
- package/.claude/skills/hailer-api/references/files.md +301 -0
- package/.claude/skills/hailer-api/references/insights.md +469 -0
- package/.claude/skills/hailer-api/references/workflows.md +720 -0
- package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
- package/.claude/skills/insight-api/SKILL.md +185 -0
- package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
- package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
- package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
- package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
- package/.claude/skills/local-first-skill/SKILL.md +570 -0
- package/.claude/skills/mcp-tools/SKILL.md +419 -0
- package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
- package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
- package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
- package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
- package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
- package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
- package/.claude/skills/remove-app-skill/SKILL.md +985 -0
- package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
- package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
- package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
- package/.claude/skills/skill-testing/README.md +137 -0
- package/.claude/skills/skill-testing/SKILL.md +348 -0
- package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
- package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
- package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
- package/.claude/skills/tool-builder/SKILL.md +328 -0
- package/.claude/skills/update-app-skill/SKILL.md +970 -0
- package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
- package/.env.example +81 -0
- package/.mcp.json +13 -0
- package/README.md +297 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +74 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +5 -0
- package/dist/client/adaptive-documentation-bot.d.ts +108 -0
- package/dist/client/adaptive-documentation-bot.js +475 -0
- package/dist/client/adaptive-documentation-types.d.ts +66 -0
- package/dist/client/adaptive-documentation-types.js +9 -0
- package/dist/client/agent-activity-bot.d.ts +51 -0
- package/dist/client/agent-activity-bot.js +166 -0
- package/dist/client/agent-tracker.d.ts +499 -0
- package/dist/client/agent-tracker.js +659 -0
- package/dist/client/description-updater.d.ts +56 -0
- package/dist/client/description-updater.js +259 -0
- package/dist/client/log-parser.d.ts +72 -0
- package/dist/client/log-parser.js +387 -0
- package/dist/client/mcp-client.d.ts +50 -0
- package/dist/client/mcp-client.js +532 -0
- package/dist/client/message-processor.d.ts +35 -0
- package/dist/client/message-processor.js +352 -0
- package/dist/client/multi-bot-manager.d.ts +24 -0
- package/dist/client/multi-bot-manager.js +74 -0
- package/dist/client/providers/anthropic-provider.d.ts +19 -0
- package/dist/client/providers/anthropic-provider.js +631 -0
- package/dist/client/providers/llm-provider.d.ts +47 -0
- package/dist/client/providers/llm-provider.js +367 -0
- package/dist/client/providers/openai-provider.d.ts +23 -0
- package/dist/client/providers/openai-provider.js +621 -0
- package/dist/client/simple-llm-caller.d.ts +19 -0
- package/dist/client/simple-llm-caller.js +100 -0
- package/dist/client/skill-generator.d.ts +81 -0
- package/dist/client/skill-generator.js +386 -0
- package/dist/client/test-adaptive-bot.d.ts +9 -0
- package/dist/client/test-adaptive-bot.js +82 -0
- package/dist/client/token-pricing.d.ts +38 -0
- package/dist/client/token-pricing.js +127 -0
- package/dist/client/token-tracker.d.ts +232 -0
- package/dist/client/token-tracker.js +457 -0
- package/dist/client/token-usage-bot.d.ts +53 -0
- package/dist/client/token-usage-bot.js +153 -0
- package/dist/client/tool-executor.d.ts +69 -0
- package/dist/client/tool-executor.js +159 -0
- package/dist/client/tool-schema-loader.d.ts +60 -0
- package/dist/client/tool-schema-loader.js +178 -0
- package/dist/client/types.d.ts +69 -0
- package/dist/client/types.js +7 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +296 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.js +147 -0
- package/dist/lib/context-manager.d.ts +111 -0
- package/dist/lib/context-manager.js +431 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +277 -0
- package/dist/lib/materialize.d.ts +3 -0
- package/dist/lib/materialize.js +101 -0
- package/dist/lib/normalizedName.d.ts +7 -0
- package/dist/lib/normalizedName.js +48 -0
- package/dist/lib/prompt-length-manager.d.ts +81 -0
- package/dist/lib/prompt-length-manager.js +457 -0
- package/dist/lib/terminal-prompt.d.ts +9 -0
- package/dist/lib/terminal-prompt.js +108 -0
- package/dist/mcp/UserContextCache.d.ts +56 -0
- package/dist/mcp/UserContextCache.js +163 -0
- package/dist/mcp/auth.d.ts +2 -0
- package/dist/mcp/auth.js +29 -0
- package/dist/mcp/hailer-clients.d.ts +42 -0
- package/dist/mcp/hailer-clients.js +246 -0
- package/dist/mcp/signal-handler.d.ts +45 -0
- package/dist/mcp/signal-handler.js +317 -0
- package/dist/mcp/tool-registry.d.ts +100 -0
- package/dist/mcp/tool-registry.js +306 -0
- package/dist/mcp/tools/activity.d.ts +15 -0
- package/dist/mcp/tools/activity.js +955 -0
- package/dist/mcp/tools/app.d.ts +20 -0
- package/dist/mcp/tools/app.js +1488 -0
- package/dist/mcp/tools/discussion.d.ts +19 -0
- package/dist/mcp/tools/discussion.js +950 -0
- package/dist/mcp/tools/file.d.ts +15 -0
- package/dist/mcp/tools/file.js +119 -0
- package/dist/mcp/tools/insight.d.ts +17 -0
- package/dist/mcp/tools/insight.js +806 -0
- package/dist/mcp/tools/skill.d.ts +10 -0
- package/dist/mcp/tools/skill.js +279 -0
- package/dist/mcp/tools/user.d.ts +10 -0
- package/dist/mcp/tools/user.js +108 -0
- package/dist/mcp/tools/workflow-template.d.ts +19 -0
- package/dist/mcp/tools/workflow-template.js +822 -0
- package/dist/mcp/tools/workflow.d.ts +18 -0
- package/dist/mcp/tools/workflow.js +1362 -0
- package/dist/mcp/utils/api-errors.d.ts +45 -0
- package/dist/mcp/utils/api-errors.js +160 -0
- package/dist/mcp/utils/data-transformers.d.ts +102 -0
- package/dist/mcp/utils/data-transformers.js +194 -0
- package/dist/mcp/utils/file-upload.d.ts +33 -0
- package/dist/mcp/utils/file-upload.js +148 -0
- package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
- package/dist/mcp/utils/hailer-api-client.js +323 -0
- package/dist/mcp/utils/index.d.ts +13 -0
- package/dist/mcp/utils/index.js +39 -0
- package/dist/mcp/utils/logger.d.ts +42 -0
- package/dist/mcp/utils/logger.js +103 -0
- package/dist/mcp/utils/types.d.ts +286 -0
- package/dist/mcp/utils/types.js +7 -0
- package/dist/mcp/workspace-cache.d.ts +42 -0
- package/dist/mcp/workspace-cache.js +97 -0
- package/dist/mcp-server.d.ts +42 -0
- package/dist/mcp-server.js +280 -0
- package/package.json +56 -0
- 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
|