@pennyfarthing/shared 7.5.0 → 9.3.0

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 (82) hide show
  1. package/dist/browser.d.ts +6 -0
  2. package/dist/browser.d.ts.map +1 -0
  3. package/dist/browser.js +8 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/generate-skill-docs.d.ts +35 -0
  6. package/dist/generate-skill-docs.d.ts.map +1 -0
  7. package/dist/generate-skill-docs.js +442 -0
  8. package/dist/generate-skill-docs.js.map +1 -0
  9. package/dist/generate-skill-docs.test.d.ts +13 -0
  10. package/dist/generate-skill-docs.test.d.ts.map +1 -0
  11. package/dist/generate-skill-docs.test.js +519 -0
  12. package/dist/generate-skill-docs.test.js.map +1 -0
  13. package/dist/index.d.ts +11 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +13 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/marker/constants.d.ts +39 -0
  18. package/dist/marker/constants.d.ts.map +1 -0
  19. package/dist/marker/constants.js +46 -0
  20. package/dist/marker/constants.js.map +1 -0
  21. package/dist/marker/continue.test.d.ts +12 -0
  22. package/dist/marker/continue.test.d.ts.map +1 -0
  23. package/dist/marker/continue.test.js +76 -0
  24. package/dist/marker/continue.test.js.map +1 -0
  25. package/dist/marker/detect.d.ts +25 -0
  26. package/dist/marker/detect.d.ts.map +1 -0
  27. package/dist/marker/detect.js +53 -0
  28. package/dist/marker/detect.js.map +1 -0
  29. package/dist/marker/detect.test.d.ts +10 -0
  30. package/dist/marker/detect.test.d.ts.map +1 -0
  31. package/dist/marker/detect.test.js +418 -0
  32. package/dist/marker/detect.test.js.map +1 -0
  33. package/dist/marker/index.d.ts +14 -0
  34. package/dist/marker/index.d.ts.map +1 -0
  35. package/dist/marker/index.js +15 -0
  36. package/dist/marker/index.js.map +1 -0
  37. package/dist/marker/strip.d.ts +26 -0
  38. package/dist/marker/strip.d.ts.map +1 -0
  39. package/dist/marker/strip.js +39 -0
  40. package/dist/marker/strip.js.map +1 -0
  41. package/dist/marker/types.d.ts +23 -0
  42. package/dist/marker/types.d.ts.map +1 -0
  43. package/dist/marker/types.js +7 -0
  44. package/dist/marker/types.js.map +1 -0
  45. package/dist/migrate-theme-schema.test.d.ts +8 -0
  46. package/dist/migrate-theme-schema.test.d.ts.map +1 -0
  47. package/dist/migrate-theme-schema.test.js +100 -0
  48. package/dist/migrate-theme-schema.test.js.map +1 -0
  49. package/dist/portrait-resolver.d.ts +32 -0
  50. package/dist/portrait-resolver.d.ts.map +1 -0
  51. package/dist/portrait-resolver.js +225 -0
  52. package/dist/portrait-resolver.js.map +1 -0
  53. package/dist/portrait-resolver.test.d.ts +2 -0
  54. package/dist/portrait-resolver.test.d.ts.map +1 -0
  55. package/dist/portrait-resolver.test.js +156 -0
  56. package/dist/portrait-resolver.test.js.map +1 -0
  57. package/dist/skill-search.d.ts +36 -0
  58. package/dist/skill-search.d.ts.map +1 -0
  59. package/dist/skill-search.js +300 -0
  60. package/dist/skill-search.js.map +1 -0
  61. package/dist/skill-search.sh +41 -0
  62. package/dist/skill-search.test.d.ts +16 -0
  63. package/dist/skill-search.test.d.ts.map +1 -0
  64. package/dist/skill-search.test.js +177 -0
  65. package/dist/skill-search.test.js.map +1 -0
  66. package/dist/skill-suggest.d.ts +76 -0
  67. package/dist/skill-suggest.d.ts.map +1 -0
  68. package/dist/skill-suggest.js +256 -0
  69. package/dist/skill-suggest.js.map +1 -0
  70. package/dist/skill-suggest.test.d.ts +12 -0
  71. package/dist/skill-suggest.test.d.ts.map +1 -0
  72. package/dist/skill-suggest.test.js +257 -0
  73. package/dist/skill-suggest.test.js.map +1 -0
  74. package/dist/theme-loader.d.ts +81 -0
  75. package/dist/theme-loader.d.ts.map +1 -0
  76. package/dist/theme-loader.js +491 -0
  77. package/dist/theme-loader.js.map +1 -0
  78. package/dist/theme-loader.test.d.ts +8 -0
  79. package/dist/theme-loader.test.d.ts.map +1 -0
  80. package/dist/theme-loader.test.js +62 -0
  81. package/dist/theme-loader.test.js.map +1 -0
  82. package/package.json +14 -1
@@ -0,0 +1,156 @@
1
+ import { describe, it, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import path from 'node:path';
4
+ import { resolvePennyfarthingDist, resolvePortraitPath, getPortraitPaths, } from './portrait-resolver.js';
5
+ describe('portrait-resolver', () => {
6
+ describe('resolvePennyfarthingDist', () => {
7
+ const originalEnv = process.env.PENNYFARTHING_DIST;
8
+ afterEach(() => {
9
+ // Restore original env
10
+ if (originalEnv !== undefined) {
11
+ process.env.PENNYFARTHING_DIST = originalEnv;
12
+ }
13
+ else {
14
+ delete process.env.PENNYFARTHING_DIST;
15
+ }
16
+ });
17
+ it('should return PENNYFARTHING_DIST env var when set and path exists', () => {
18
+ // Scenario 1: Explicit env var override with existing path
19
+ // Use the actual pennyfarthing-dist path that exists
20
+ const actualDistPath = resolvePennyfarthingDist();
21
+ assert.ok(actualDistPath !== null, 'Should have a valid dist path');
22
+ process.env.PENNYFARTHING_DIST = actualDistPath;
23
+ const result = resolvePennyfarthingDist();
24
+ // When env var is set to valid existing path, should return it
25
+ assert.strictEqual(result, actualDistPath);
26
+ });
27
+ it('should fall through when PENNYFARTHING_DIST is set but path does not exist', () => {
28
+ // Scenario 1b: Env var set but invalid - should fall through to other checks
29
+ process.env.PENNYFARTHING_DIST = '/nonexistent/path/pennyfarthing-dist';
30
+ const result = resolvePennyfarthingDist();
31
+ // Should fall through and find monorepo path (since we're in monorepo)
32
+ // The key behavior is it doesn't return the invalid env var path
33
+ assert.ok(result === null || !result.includes('/nonexistent/'));
34
+ });
35
+ it('should find monorepo root pennyfarthing-dist directory', () => {
36
+ // Scenario 2: Monorepo root detection (dogfooding)
37
+ // When running from within pennyfarthing repo, find pennyfarthing-dist/ at root
38
+ delete process.env.PENNYFARTHING_DIST;
39
+ const result = resolvePennyfarthingDist();
40
+ // Should find the monorepo root path
41
+ assert.ok(result !== null, 'Should find monorepo root');
42
+ assert.ok(result.endsWith('pennyfarthing-dist'), 'Path should end with pennyfarthing-dist');
43
+ });
44
+ it('should find sibling pennyfarthing-dist directory', () => {
45
+ // Scenario 3: Sibling directory for dev scenarios
46
+ // e.g., project/pennyfarthing-dist when called from project/packages/shared
47
+ delete process.env.PENNYFARTHING_DIST;
48
+ const result = resolvePennyfarthingDist();
49
+ // Implementation should check ../pennyfarthing-dist, ../../pennyfarthing-dist, etc.
50
+ assert.ok(result === null || result.includes('pennyfarthing-dist'));
51
+ });
52
+ it('should find scoped npm package path', () => {
53
+ // Scenario 4: Scoped npm install
54
+ // node_modules/@pennyfarthing/core/pennyfarthing-dist/
55
+ delete process.env.PENNYFARTHING_DIST;
56
+ const result = resolvePennyfarthingDist();
57
+ // When installed via @pennyfarthing/core, should find that path
58
+ assert.ok(result === null || result.includes('pennyfarthing-dist'));
59
+ });
60
+ it('should find legacy npm package path', () => {
61
+ // Scenario 5: Legacy npm install
62
+ // node_modules/pennyfarthing/pennyfarthing-dist/
63
+ delete process.env.PENNYFARTHING_DIST;
64
+ const result = resolvePennyfarthingDist();
65
+ // When installed via pennyfarthing, should find that path
66
+ assert.ok(result === null || result.includes('pennyfarthing-dist'));
67
+ });
68
+ it('should return a valid path or null', () => {
69
+ // This tests that the function returns a valid result
70
+ delete process.env.PENNYFARTHING_DIST;
71
+ const result = resolvePennyfarthingDist();
72
+ // Result should be either null or a valid pennyfarthing-dist path
73
+ if (result !== null) {
74
+ assert.ok(result.includes('pennyfarthing-dist'));
75
+ }
76
+ });
77
+ it('should check paths in priority order - env var takes precedence', () => {
78
+ // When multiple paths exist, env var should return first if it exists
79
+ const actualDistPath = resolvePennyfarthingDist();
80
+ assert.ok(actualDistPath !== null, 'Should have a valid dist path');
81
+ // Set env var to actual path
82
+ process.env.PENNYFARTHING_DIST = actualDistPath;
83
+ const result = resolvePennyfarthingDist();
84
+ // Env var should take precedence (returns same path since it exists)
85
+ assert.strictEqual(result, actualDistPath);
86
+ });
87
+ });
88
+ describe('resolvePortraitPath', () => {
89
+ it('should resolve portrait path for valid theme and agent', () => {
90
+ // Use a-team which has actual portraits (shakespeare has only .gitkeep)
91
+ const result = resolvePortraitPath('a-team', 'sm');
92
+ assert.ok(result !== null, 'Should find portrait');
93
+ assert.ok(result.includes('a-team'), 'Path should include theme');
94
+ // Portrait files use character names (faceman) not agent names (sm)
95
+ assert.ok(result.includes('face'), 'Path should include character name');
96
+ assert.ok(result.endsWith('.png') || result.endsWith('.jpg'), 'Should be image file');
97
+ });
98
+ it('should return null for invalid theme', () => {
99
+ const result = resolvePortraitPath('nonexistent-theme', 'sm');
100
+ assert.strictEqual(result, null);
101
+ });
102
+ it('should return null for invalid agent', () => {
103
+ const result = resolvePortraitPath('a-team', 'nonexistent-agent');
104
+ assert.strictEqual(result, null);
105
+ });
106
+ it('should handle theme with special characters in name', () => {
107
+ // Themes like 'star-trek-tos' have hyphens
108
+ const result = resolvePortraitPath('star-trek-tos', 'sm');
109
+ assert.ok(result === null || result.includes('star-trek-tos'));
110
+ });
111
+ it('should handle portrait resolution when dist is found', () => {
112
+ // When resolvePennyfarthingDist returns a valid path, we should be able
113
+ // to resolve portraits for known themes
114
+ delete process.env.PENNYFARTHING_DIST;
115
+ const result = resolvePortraitPath('a-team', 'dev');
116
+ // If dist is found (which it is in monorepo), should find portrait
117
+ // dev maps to ba in a-team theme
118
+ if (result !== null) {
119
+ assert.ok(result.includes('a-team'));
120
+ assert.ok(result.includes('ba'));
121
+ }
122
+ });
123
+ });
124
+ describe('getPortraitPaths', () => {
125
+ it('should return correct paths structure', () => {
126
+ const distPath = '/test/pennyfarthing-dist';
127
+ const result = getPortraitPaths(distPath);
128
+ assert.ok('portraitsDir' in result, 'Should have portraitsDir');
129
+ assert.ok('themesDir' in result, 'Should have themesDir');
130
+ assert.ok('agentsDir' in result, 'Should have agentsDir');
131
+ });
132
+ it('should build portraitsDir correctly', () => {
133
+ const distPath = '/test/pennyfarthing-dist';
134
+ const result = getPortraitPaths(distPath);
135
+ // Actual structure: pennyfarthing-dist/personas/portraits/
136
+ assert.strictEqual(result.portraitsDir, path.join(distPath, 'personas', 'portraits'), 'portraitsDir should be distPath/personas/portraits');
137
+ });
138
+ it('should build themesDir correctly', () => {
139
+ const distPath = '/test/pennyfarthing-dist';
140
+ const result = getPortraitPaths(distPath);
141
+ assert.strictEqual(result.themesDir, path.join(distPath, 'personas'), 'themesDir should be distPath/personas');
142
+ });
143
+ it('should build agentsDir correctly', () => {
144
+ const distPath = '/test/pennyfarthing-dist';
145
+ const result = getPortraitPaths(distPath);
146
+ assert.strictEqual(result.agentsDir, path.join(distPath, 'agents'), 'agentsDir should be distPath/agents');
147
+ });
148
+ it('should handle paths with trailing slashes', () => {
149
+ const distPath = '/test/pennyfarthing-dist/';
150
+ const result = getPortraitPaths(distPath);
151
+ // Should normalize paths correctly
152
+ assert.ok(!result.portraitsDir.includes('//'), 'Should not have double slashes');
153
+ });
154
+ });
155
+ });
156
+ //# sourceMappingURL=portrait-resolver.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portrait-resolver.test.js","sourceRoot":"","sources":["../src/portrait-resolver.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
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
+ export interface SearchOptions {
8
+ /** Filter by tag (e.g., "tdd", "quality") */
9
+ tag?: string;
10
+ /** Filter by keyword (e.g., "jest", "vitest") */
11
+ keyword?: string;
12
+ /** Search description text */
13
+ query?: string;
14
+ /** Filter by category (e.g., "development", "tools") */
15
+ category?: string;
16
+ /** Custom path to registry file (for testing) */
17
+ registryPath?: string;
18
+ }
19
+ export interface SkillResult {
20
+ name: string;
21
+ description: string;
22
+ category: string;
23
+ tags: string[];
24
+ keywords?: string[];
25
+ version?: string;
26
+ related_skills?: string[];
27
+ }
28
+ /**
29
+ * Search skills in the registry based on provided options.
30
+ *
31
+ * @param options - Search options (tag, keyword, query, category)
32
+ * @returns Promise resolving to array of matching skills
33
+ * @throws Error if registry file not found or invalid category
34
+ */
35
+ export declare function searchSkills(options: SearchOptions): Promise<SkillResult[]>;
36
+ //# sourceMappingURL=skill-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-search.d.ts","sourceRoot":"","sources":["../src/skill-search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAmLD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAoEjF"}
@@ -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"}