@pennyfarthing/shared 7.0.2

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 (46) hide show
  1. package/dist/generate-skill-docs.d.ts +35 -0
  2. package/dist/generate-skill-docs.d.ts.map +1 -0
  3. package/dist/generate-skill-docs.js +442 -0
  4. package/dist/generate-skill-docs.js.map +1 -0
  5. package/dist/generate-skill-docs.test.d.ts +13 -0
  6. package/dist/generate-skill-docs.test.d.ts.map +1 -0
  7. package/dist/generate-skill-docs.test.js +519 -0
  8. package/dist/generate-skill-docs.test.js.map +1 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +10 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/portrait-resolver.d.ts +32 -0
  14. package/dist/portrait-resolver.d.ts.map +1 -0
  15. package/dist/portrait-resolver.js +147 -0
  16. package/dist/portrait-resolver.js.map +1 -0
  17. package/dist/portrait-resolver.test.d.ts +2 -0
  18. package/dist/portrait-resolver.test.d.ts.map +1 -0
  19. package/dist/portrait-resolver.test.js +156 -0
  20. package/dist/portrait-resolver.test.js.map +1 -0
  21. package/dist/skill-search.d.ts +36 -0
  22. package/dist/skill-search.d.ts.map +1 -0
  23. package/dist/skill-search.js +300 -0
  24. package/dist/skill-search.js.map +1 -0
  25. package/dist/skill-search.sh +41 -0
  26. package/dist/skill-search.test.d.ts +16 -0
  27. package/dist/skill-search.test.d.ts.map +1 -0
  28. package/dist/skill-search.test.js +193 -0
  29. package/dist/skill-search.test.js.map +1 -0
  30. package/dist/skill-suggest.d.ts +76 -0
  31. package/dist/skill-suggest.d.ts.map +1 -0
  32. package/dist/skill-suggest.js +256 -0
  33. package/dist/skill-suggest.js.map +1 -0
  34. package/dist/skill-suggest.test.d.ts +12 -0
  35. package/dist/skill-suggest.test.d.ts.map +1 -0
  36. package/dist/skill-suggest.test.js +257 -0
  37. package/dist/skill-suggest.test.js.map +1 -0
  38. package/dist/theme-loader.d.ts +35 -0
  39. package/dist/theme-loader.d.ts.map +1 -0
  40. package/dist/theme-loader.js +170 -0
  41. package/dist/theme-loader.js.map +1 -0
  42. package/dist/theme-loader.test.d.ts +2 -0
  43. package/dist/theme-loader.test.d.ts.map +1 -0
  44. package/dist/theme-loader.test.js +72 -0
  45. package/dist/theme-loader.test.js.map +1 -0
  46. package/package.json +38 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Skill Search Utility - Story 9-2
3
+ *
4
+ * Searches the skill registry by tag, keyword, category, or description query.
5
+ * Returns matching skills with metadata for discovery and suggestions.
6
+ */
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { resolvePennyfarthingDist } from './portrait-resolver.js';
10
+ /** Valid categories from the registry */
11
+ const VALID_CATEGORIES = [
12
+ 'ai-llm',
13
+ 'documentation',
14
+ 'development',
15
+ 'tools',
16
+ 'benchmarking',
17
+ 'project-management',
18
+ 'theming'
19
+ ];
20
+ /**
21
+ * Simple YAML parser for skill registry
22
+ * Handles nested objects and arrays in the specific structure we need
23
+ */
24
+ function parseRegistryYaml(content) {
25
+ const result = { version: '', skills: {} };
26
+ const lines = content.split('\n');
27
+ let currentSkillKey = null;
28
+ let currentArrayField = null;
29
+ let insideExamples = false;
30
+ for (const line of lines) {
31
+ const trimmed = line.trimEnd();
32
+ // Skip empty lines and comments
33
+ if (!trimmed || trimmed.startsWith('#'))
34
+ continue;
35
+ const indent = line.length - line.trimStart().length;
36
+ const colonIndex = trimmed.indexOf(':');
37
+ const key = colonIndex >= 0 ? trimmed.substring(0, colonIndex).trim() : trimmed.trim();
38
+ const value = colonIndex >= 0 ? trimmed.substring(colonIndex + 1).trim() : '';
39
+ // Top-level version
40
+ if (indent === 0 && key === 'version') {
41
+ result.version = value.replace(/^["']|["']$/g, '');
42
+ continue;
43
+ }
44
+ // Top-level skills key
45
+ if (indent === 0 && key === 'skills') {
46
+ continue;
47
+ }
48
+ // Skill key (indent 2)
49
+ if (indent === 2 && !trimmed.startsWith('-')) {
50
+ currentSkillKey = key;
51
+ result.skills[currentSkillKey] = {
52
+ name: '',
53
+ description: '',
54
+ category: '',
55
+ tags: [],
56
+ version: '',
57
+ prerequisites: [],
58
+ examples: [],
59
+ anti_patterns: [],
60
+ related_skills: [],
61
+ keywords: []
62
+ };
63
+ currentArrayField = null;
64
+ insideExamples = false;
65
+ continue;
66
+ }
67
+ // Skill fields (indent 4)
68
+ if (indent === 4 && currentSkillKey) {
69
+ insideExamples = false;
70
+ currentArrayField = null;
71
+ if (key === 'name') {
72
+ result.skills[currentSkillKey].name = value.replace(/^["']|["']$/g, '');
73
+ }
74
+ else if (key === 'description') {
75
+ result.skills[currentSkillKey].description = value.replace(/^["']|["']$/g, '');
76
+ }
77
+ else if (key === 'category') {
78
+ result.skills[currentSkillKey].category = value.replace(/^["']|["']$/g, '');
79
+ }
80
+ else if (key === 'version') {
81
+ result.skills[currentSkillKey].version = value.replace(/^["']|["']$/g, '');
82
+ }
83
+ else if (key === 'tags') {
84
+ // Inline array: [tag1, tag2]
85
+ if (value.startsWith('[')) {
86
+ result.skills[currentSkillKey].tags = parseInlineArray(value);
87
+ }
88
+ else {
89
+ currentArrayField = 'tags';
90
+ }
91
+ }
92
+ else if (key === 'keywords') {
93
+ if (value.startsWith('[')) {
94
+ result.skills[currentSkillKey].keywords = parseInlineArray(value);
95
+ }
96
+ else {
97
+ currentArrayField = 'keywords';
98
+ }
99
+ }
100
+ else if (key === 'prerequisites') {
101
+ if (value.startsWith('[')) {
102
+ result.skills[currentSkillKey].prerequisites = parseInlineArray(value);
103
+ }
104
+ else {
105
+ currentArrayField = 'prerequisites';
106
+ }
107
+ }
108
+ else if (key === 'related_skills') {
109
+ if (value.startsWith('[')) {
110
+ result.skills[currentSkillKey].related_skills = parseInlineArray(value);
111
+ }
112
+ else {
113
+ currentArrayField = 'related_skills';
114
+ }
115
+ }
116
+ else if (key === 'anti_patterns') {
117
+ currentArrayField = 'anti_patterns';
118
+ }
119
+ else if (key === 'examples') {
120
+ insideExamples = true;
121
+ currentArrayField = null;
122
+ }
123
+ continue;
124
+ }
125
+ // Array items (indent 6 starting with -)
126
+ if (indent === 6 && currentSkillKey && trimmed.startsWith('-')) {
127
+ const itemValue = trimmed.substring(1).trim().replace(/^["']|["']$/g, '');
128
+ if (insideExamples) {
129
+ // Start of a new example object
130
+ result.skills[currentSkillKey].examples.push({ context: '', invocation: '' });
131
+ }
132
+ else if (currentArrayField && currentArrayField in result.skills[currentSkillKey]) {
133
+ const arr = result.skills[currentSkillKey][currentArrayField];
134
+ if (Array.isArray(arr) && typeof arr[0] !== 'object') {
135
+ arr.push(itemValue);
136
+ }
137
+ }
138
+ continue;
139
+ }
140
+ // Example sub-fields (indent 8)
141
+ if (indent === 8 && currentSkillKey && insideExamples) {
142
+ const examples = result.skills[currentSkillKey].examples;
143
+ if (examples.length > 0) {
144
+ const lastExample = examples[examples.length - 1];
145
+ if (key === 'context') {
146
+ lastExample.context = value.replace(/^["']|["']$/g, '');
147
+ }
148
+ else if (key === 'invocation') {
149
+ lastExample.invocation = value.replace(/^["']|["']$/g, '');
150
+ }
151
+ }
152
+ continue;
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+ /**
158
+ * Parse inline YAML array like [tag1, tag2, tag3]
159
+ */
160
+ function parseInlineArray(value) {
161
+ const match = value.match(/^\[(.*)\]$/);
162
+ if (!match)
163
+ return [];
164
+ return match[1]
165
+ .split(',')
166
+ .map(item => item.trim().replace(/^["']|["']$/g, ''))
167
+ .filter(Boolean);
168
+ }
169
+ /**
170
+ * Search skills in the registry based on provided options.
171
+ *
172
+ * @param options - Search options (tag, keyword, query, category)
173
+ * @returns Promise resolving to array of matching skills
174
+ * @throws Error if registry file not found or invalid category
175
+ */
176
+ export async function searchSkills(options) {
177
+ // Resolve registry path
178
+ let registryPath = options.registryPath;
179
+ if (!registryPath) {
180
+ const distPath = resolvePennyfarthingDist();
181
+ if (!distPath) {
182
+ throw new Error('Registry not found: Cannot resolve pennyfarthing-dist directory');
183
+ }
184
+ registryPath = join(distPath, 'skills', 'skill-registry.yaml');
185
+ }
186
+ // Validate registry exists
187
+ if (!existsSync(registryPath)) {
188
+ throw new Error(`Registry not found: ${registryPath}`);
189
+ }
190
+ // Validate category if provided
191
+ if (options.category && !VALID_CATEGORIES.includes(options.category)) {
192
+ throw new Error(`Invalid category: ${options.category}. Valid categories: ${VALID_CATEGORIES.join(', ')}`);
193
+ }
194
+ // Parse registry
195
+ const content = readFileSync(registryPath, 'utf-8');
196
+ const registry = parseRegistryYaml(content);
197
+ // Convert to SkillResult array
198
+ let results = Object.values(registry.skills).map(skill => ({
199
+ name: skill.name,
200
+ description: skill.description,
201
+ category: skill.category,
202
+ tags: skill.tags,
203
+ keywords: skill.keywords.length > 0 ? skill.keywords : undefined,
204
+ version: skill.version || undefined,
205
+ related_skills: skill.related_skills.length > 0 ? skill.related_skills : undefined
206
+ }));
207
+ // Apply filters (AND logic - all filters must match)
208
+ // Filter by category
209
+ if (options.category) {
210
+ results = results.filter(s => s.category === options.category);
211
+ }
212
+ // Filter by tag (case-insensitive)
213
+ if (options.tag) {
214
+ const searchTag = options.tag.toLowerCase();
215
+ results = results.filter(s => s.tags.some(t => t.toLowerCase() === searchTag));
216
+ }
217
+ // Filter by keyword (case-insensitive)
218
+ if (options.keyword) {
219
+ const searchKeyword = options.keyword.toLowerCase();
220
+ results = results.filter(s => s.keywords?.some(k => k.toLowerCase() === searchKeyword));
221
+ }
222
+ // Filter by query (searches description, case-insensitive)
223
+ if (options.query) {
224
+ const searchQuery = options.query.toLowerCase();
225
+ results = results.filter(s => s.description.toLowerCase().includes(searchQuery));
226
+ }
227
+ return results;
228
+ }
229
+ // CLI entry point when run directly
230
+ if (import.meta.url === `file://${process.argv[1]}`) {
231
+ const args = process.argv.slice(2);
232
+ const options = {};
233
+ let jsonOutput = false;
234
+ // Parse command-line arguments
235
+ for (let i = 0; i < args.length; i++) {
236
+ const arg = args[i];
237
+ if (arg === '--help' || arg === '-h') {
238
+ console.log(`Usage: skill-search [options]
239
+
240
+ Options:
241
+ --tag <tag> Filter by tag (e.g., "tdd", "quality")
242
+ --keyword <kw> Filter by keyword (e.g., "jest", "vitest")
243
+ --query <text> Search description text
244
+ --category <cat> Filter by category
245
+ --json Output as JSON (default: table)
246
+ --help, -h Show this help
247
+
248
+ Examples:
249
+ skill-search --tag tdd
250
+ skill-search --category development --json
251
+ skill-search --query "TDD workflow"
252
+ `);
253
+ process.exit(0);
254
+ }
255
+ else if (arg === '--json') {
256
+ jsonOutput = true;
257
+ }
258
+ else if (arg === '--tag' && args[i + 1]) {
259
+ options.tag = args[++i];
260
+ }
261
+ else if (arg === '--keyword' && args[i + 1]) {
262
+ options.keyword = args[++i];
263
+ }
264
+ else if (arg === '--query' && args[i + 1]) {
265
+ options.query = args[++i];
266
+ }
267
+ else if (arg === '--category' && args[i + 1]) {
268
+ options.category = args[++i];
269
+ }
270
+ }
271
+ searchSkills(options)
272
+ .then(results => {
273
+ if (jsonOutput) {
274
+ console.log(JSON.stringify(results, null, 2));
275
+ }
276
+ else {
277
+ // Table output
278
+ if (results.length === 0) {
279
+ console.log('No skills found matching criteria.');
280
+ }
281
+ else {
282
+ const maxName = Math.max(...results.map(s => s.name.length), 4);
283
+ const maxCat = Math.max(...results.map(s => s.category.length), 8);
284
+ console.log(`${'NAME'.padEnd(maxName)} ${'CATEGORY'.padEnd(maxCat)} DESCRIPTION`);
285
+ console.log(`${'-'.repeat(maxName)} ${'-'.repeat(maxCat)} ${'-'.repeat(40)}`);
286
+ for (const skill of results) {
287
+ const desc = skill.description.length > 50
288
+ ? skill.description.substring(0, 47) + '...'
289
+ : skill.description;
290
+ console.log(`${skill.name.padEnd(maxName)} ${skill.category.padEnd(maxCat)} ${desc}`);
291
+ }
292
+ }
293
+ }
294
+ })
295
+ .catch(err => {
296
+ console.error(`Error: ${err.message}`);
297
+ process.exit(1);
298
+ });
299
+ }
300
+ //# sourceMappingURL=skill-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-search.js","sourceRoot":"","sources":["../src/skill-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # Skill Search Shell Wrapper - Story 9-2
3
+ #
4
+ # Thin wrapper around the TypeScript skill-search utility.
5
+ # Provides CLI access to skill registry search functionality.
6
+ #
7
+ # Usage:
8
+ # skill-search.sh [options]
9
+ #
10
+ # Options:
11
+ # --tag <tag> Filter by tag (e.g., "tdd", "quality")
12
+ # --keyword <kw> Filter by keyword (e.g., "jest", "vitest")
13
+ # --query <text> Search description text
14
+ # --category <cat> Filter by category
15
+ # --json Output as JSON (default: table)
16
+ # --help, -h Show this help
17
+ #
18
+ # Examples:
19
+ # skill-search.sh --tag tdd
20
+ # skill-search.sh --category development --json
21
+ # skill-search.sh --query "TDD workflow"
22
+
23
+ set -euo pipefail
24
+
25
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+
27
+ # Find the compiled JavaScript file
28
+ JS_FILE="$SCRIPT_DIR/../dist/skill-search.js"
29
+
30
+ if [[ ! -f "$JS_FILE" ]]; then
31
+ # Try looking relative to where we are in the repo
32
+ JS_FILE="$(dirname "$SCRIPT_DIR")/dist/skill-search.js"
33
+ fi
34
+
35
+ if [[ ! -f "$JS_FILE" ]]; then
36
+ echo "Error: skill-search.js not found. Run 'npm run build' first." >&2
37
+ exit 1
38
+ fi
39
+
40
+ # Execute the Node.js script with all arguments
41
+ exec node "$JS_FILE" "$@"
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Tests for Story 9-2: Build Skill Search Utility
3
+ *
4
+ * These tests verify:
5
+ * - Tag filtering returns expected skills
6
+ * - Keyword search returns expected skills
7
+ * - Category filtering works
8
+ * - Combined filters use AND logic
9
+ * - JSON output is valid and includes required fields
10
+ * - Empty results return empty array (not error)
11
+ * - Error handling for missing registry
12
+ *
13
+ * Run with: npm test -- scripts/utils/skill-search.test.ts
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=skill-search.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-search.test.d.ts","sourceRoot":"","sources":["../src/skill-search.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Tests for Story 9-2: Build Skill Search Utility
3
+ *
4
+ * These tests verify:
5
+ * - Tag filtering returns expected skills
6
+ * - Keyword search returns expected skills
7
+ * - Category filtering works
8
+ * - Combined filters use AND logic
9
+ * - JSON output is valid and includes required fields
10
+ * - Empty results return empty array (not error)
11
+ * - Error handling for missing registry
12
+ *
13
+ * Run with: npm test -- scripts/utils/skill-search.test.ts
14
+ */
15
+ import { describe, it } from 'node:test';
16
+ import assert from 'node:assert';
17
+ import { execSync } from 'child_process';
18
+ import { existsSync } from 'fs';
19
+ import { join } from 'path';
20
+ // Import functions to test - these don't exist yet, tests should fail
21
+ import { searchSkills } from './skill-search.js';
22
+ // Path to the shell wrapper for integration tests
23
+ const WRAPPER_PATH = join(import.meta.dirname, 'skill-search.sh');
24
+ describe('Story 9-2: Skill Search Utility', () => {
25
+ describe('searchSkills() - Core Function', () => {
26
+ describe('AC1: Script is functional', () => {
27
+ it('should read skill-registry.yaml and return all skills when no filters', async () => {
28
+ // AC1: Reads skill-registry.yaml
29
+ const results = await searchSkills({});
30
+ assert.ok(Array.isArray(results), 'Should return an array');
31
+ assert.ok(results.length > 0, 'Should return skills from registry');
32
+ assert.ok(results.length === 19, 'Should return all 19 skills when no filters');
33
+ });
34
+ it('should return skills with required fields', async () => {
35
+ // AC1: Each skill should have core metadata
36
+ const results = await searchSkills({});
37
+ for (const skill of results) {
38
+ assert.ok(skill.name, `Skill should have name`);
39
+ assert.ok(skill.description, `Skill ${skill.name} should have description`);
40
+ assert.ok(skill.category, `Skill ${skill.name} should have category`);
41
+ assert.ok(Array.isArray(skill.tags), `Skill ${skill.name} should have tags array`);
42
+ }
43
+ });
44
+ it('should filter by tag with --tag option', async () => {
45
+ // AC1: Filters by tag with --tag <category>
46
+ const results = await searchSkills({ tag: 'tdd' });
47
+ assert.ok(results.length > 0, 'Should return at least one skill with tdd tag');
48
+ assert.ok(results.every(s => s.tags.includes('tdd')), 'All results should have tdd tag');
49
+ });
50
+ it('should filter by keyword with --keyword option', async () => {
51
+ // AC1: Filters by keyword with --keyword <term>
52
+ const results = await searchSkills({ keyword: 'jest' });
53
+ assert.ok(results.length > 0, 'Should return at least one skill with jest keyword');
54
+ assert.ok(results.every(s => s.keywords?.includes('jest')), 'All results should have jest in keywords');
55
+ });
56
+ it('should search descriptions with --query option', async () => {
57
+ // AC1: Searches descriptions with --query <text>
58
+ const results = await searchSkills({ query: 'TDD workflow' });
59
+ assert.ok(results.length > 0, 'Should return skills matching query');
60
+ assert.ok(results.some(s => s.description.toLowerCase().includes('tdd')), 'At least one result should mention TDD in description');
61
+ });
62
+ });
63
+ describe('AC2: Tag/keyword search returns expected results', () => {
64
+ it('should return testing skill when filtering by --tag tdd', async () => {
65
+ // AC2: --tag development returns skills with that tag
66
+ const results = await searchSkills({ tag: 'tdd' });
67
+ const skillNames = results.map(s => s.name);
68
+ assert.ok(skillNames.includes('testing'), 'Should include testing skill for tdd tag');
69
+ });
70
+ it('should return testing skill when filtering by --keyword vitest', async () => {
71
+ // AC2: --keyword jest returns skills mentioning jest in keywords
72
+ const results = await searchSkills({ keyword: 'vitest' });
73
+ const skillNames = results.map(s => s.name);
74
+ assert.ok(skillNames.includes('testing'), 'Should include testing skill for vitest keyword');
75
+ });
76
+ it('should return 3 skills when filtering by --category development', async () => {
77
+ // AC2: Category filter returns expected count
78
+ const results = await searchSkills({ category: 'development' });
79
+ assert.strictEqual(results.length, 3, 'Should return 3 development skills');
80
+ assert.ok(results.every(s => s.category === 'development'), 'All results should be development category');
81
+ });
82
+ it('should combine multiple filters with AND logic', async () => {
83
+ // AC2: Multiple filters can be combined (AND logic)
84
+ const results = await searchSkills({
85
+ tag: 'quality',
86
+ category: 'development'
87
+ });
88
+ assert.ok(results.length > 0, 'Should return at least one result');
89
+ assert.ok(results.length < 3, 'Should be narrower than just category filter');
90
+ assert.ok(results.every(s => s.category === 'development' && s.tags.includes('quality')), 'All results should match both filters');
91
+ });
92
+ it('should return empty array for non-existent tag (not error)', async () => {
93
+ // AC2: Empty results handled gracefully
94
+ const results = await searchSkills({ tag: 'nonexistent-tag-xyz' });
95
+ assert.ok(Array.isArray(results), 'Should return an array');
96
+ assert.strictEqual(results.length, 0, 'Should return empty array for no matches');
97
+ });
98
+ it('should return empty array for non-existent keyword', async () => {
99
+ // AC2: Empty results for non-existent keyword
100
+ const results = await searchSkills({ keyword: 'nonexistent-keyword-xyz' });
101
+ assert.ok(Array.isArray(results), 'Should return an array');
102
+ assert.strictEqual(results.length, 0, 'Should return empty array for no matches');
103
+ });
104
+ it('should handle case-insensitive tag matching', async () => {
105
+ // AC2: Tags should match case-insensitively
106
+ const upperResults = await searchSkills({ tag: 'TDD' });
107
+ const lowerResults = await searchSkills({ tag: 'tdd' });
108
+ assert.deepStrictEqual(upperResults.map(s => s.name).sort(), lowerResults.map(s => s.name).sort(), 'Tag matching should be case-insensitive');
109
+ });
110
+ });
111
+ describe('AC3: JSON output format', () => {
112
+ it('should return valid JSON-serializable results', async () => {
113
+ // AC3: Output should be valid JSON
114
+ const results = await searchSkills({});
115
+ const jsonString = JSON.stringify(results);
116
+ const parsed = JSON.parse(jsonString);
117
+ assert.ok(Array.isArray(parsed), 'Should produce valid JSON array');
118
+ });
119
+ it('should include name, description, category, tags in each result', async () => {
120
+ // AC3: Each skill object includes: name, description, category, tags
121
+ const results = await searchSkills({});
122
+ for (const skill of results) {
123
+ assert.ok('name' in skill, 'Should include name');
124
+ assert.ok('description' in skill, 'Should include description');
125
+ assert.ok('category' in skill, 'Should include category');
126
+ assert.ok('tags' in skill, 'Should include tags');
127
+ }
128
+ });
129
+ it('should include keywords in results when present', async () => {
130
+ // AC3: Keywords should be included for search relevance
131
+ const results = await searchSkills({ keyword: 'jest' });
132
+ assert.ok(results.length > 0, 'Should have results');
133
+ assert.ok(results.every(s => 'keywords' in s), 'All results should include keywords field');
134
+ });
135
+ });
136
+ describe('Error Handling', () => {
137
+ it('should throw helpful error when registry file is missing', async () => {
138
+ // Error handling: Missing registry file shows helpful error
139
+ await assert.rejects(async () => searchSkills({ registryPath: '/nonexistent/path/registry.yaml' }), {
140
+ message: /registry.*not found|cannot find|no such file/i
141
+ }, 'Should throw error with helpful message for missing registry');
142
+ });
143
+ it('should throw error for invalid category value', async () => {
144
+ // Error handling: Invalid category should be rejected
145
+ await assert.rejects(async () => searchSkills({ category: 'invalid-category' }), {
146
+ message: /invalid category|unknown category/i
147
+ }, 'Should throw error for invalid category');
148
+ });
149
+ });
150
+ });
151
+ describe('Shell Wrapper Integration', () => {
152
+ it('should execute via bash wrapper', () => {
153
+ // Integration test: Wrapper calls Node script correctly
154
+ // Skip if wrapper doesn't exist yet
155
+ if (!existsSync(WRAPPER_PATH)) {
156
+ assert.fail('skill-search.sh wrapper does not exist yet');
157
+ }
158
+ const result = execSync(`bash ${WRAPPER_PATH} --help`, { encoding: 'utf-8' });
159
+ assert.ok(result.includes('search') || result.includes('usage'), 'Should show help text');
160
+ });
161
+ it('should output JSON with --json flag via wrapper', () => {
162
+ // Integration test: JSON output via wrapper
163
+ if (!existsSync(WRAPPER_PATH)) {
164
+ assert.fail('skill-search.sh wrapper does not exist yet');
165
+ }
166
+ const result = execSync(`bash ${WRAPPER_PATH} --json`, { encoding: 'utf-8' });
167
+ const parsed = JSON.parse(result);
168
+ assert.ok(Array.isArray(parsed), 'Should output valid JSON array');
169
+ });
170
+ it('should filter by tag via wrapper', () => {
171
+ // Integration test: Tag filter via wrapper
172
+ if (!existsSync(WRAPPER_PATH)) {
173
+ assert.fail('skill-search.sh wrapper does not exist yet');
174
+ }
175
+ const result = execSync(`bash ${WRAPPER_PATH} --tag tdd --json`, { encoding: 'utf-8' });
176
+ const parsed = JSON.parse(result);
177
+ assert.ok(parsed.length > 0, 'Should return results for tdd tag');
178
+ assert.ok(parsed.every((s) => s.tags.includes('tdd')), 'All results should have tdd tag');
179
+ });
180
+ it('should show human-readable table without --json flag', () => {
181
+ // AC3: Non-JSON mode shows human-readable table
182
+ if (!existsSync(WRAPPER_PATH)) {
183
+ assert.fail('skill-search.sh wrapper does not exist yet');
184
+ }
185
+ const result = execSync(`bash ${WRAPPER_PATH} --tag tdd`, { encoding: 'utf-8' });
186
+ // Should not be valid JSON (it's a table)
187
+ assert.throws(() => JSON.parse(result), 'Non-JSON output should not be valid JSON');
188
+ // Should contain skill name and some formatting
189
+ assert.ok(result.includes('testing'), 'Should show testing skill name');
190
+ });
191
+ });
192
+ });
193
+ //# sourceMappingURL=skill-search.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-search.test.js","sourceRoot":"","sources":["../src/skill-search.test.ts"],"names":[],"mappings":"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"}