@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.
- package/dist/browser.d.ts +6 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +8 -0
- package/dist/browser.js.map +1 -0
- package/dist/generate-skill-docs.d.ts +35 -0
- package/dist/generate-skill-docs.d.ts.map +1 -0
- package/dist/generate-skill-docs.js +442 -0
- package/dist/generate-skill-docs.js.map +1 -0
- package/dist/generate-skill-docs.test.d.ts +13 -0
- package/dist/generate-skill-docs.test.d.ts.map +1 -0
- package/dist/generate-skill-docs.test.js +519 -0
- package/dist/generate-skill-docs.test.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/marker/constants.d.ts +39 -0
- package/dist/marker/constants.d.ts.map +1 -0
- package/dist/marker/constants.js +46 -0
- package/dist/marker/constants.js.map +1 -0
- package/dist/marker/continue.test.d.ts +12 -0
- package/dist/marker/continue.test.d.ts.map +1 -0
- package/dist/marker/continue.test.js +76 -0
- package/dist/marker/continue.test.js.map +1 -0
- package/dist/marker/detect.d.ts +25 -0
- package/dist/marker/detect.d.ts.map +1 -0
- package/dist/marker/detect.js +53 -0
- package/dist/marker/detect.js.map +1 -0
- package/dist/marker/detect.test.d.ts +10 -0
- package/dist/marker/detect.test.d.ts.map +1 -0
- package/dist/marker/detect.test.js +418 -0
- package/dist/marker/detect.test.js.map +1 -0
- package/dist/marker/index.d.ts +14 -0
- package/dist/marker/index.d.ts.map +1 -0
- package/dist/marker/index.js +15 -0
- package/dist/marker/index.js.map +1 -0
- package/dist/marker/strip.d.ts +26 -0
- package/dist/marker/strip.d.ts.map +1 -0
- package/dist/marker/strip.js +39 -0
- package/dist/marker/strip.js.map +1 -0
- package/dist/marker/types.d.ts +23 -0
- package/dist/marker/types.d.ts.map +1 -0
- package/dist/marker/types.js +7 -0
- package/dist/marker/types.js.map +1 -0
- package/dist/migrate-theme-schema.test.d.ts +8 -0
- package/dist/migrate-theme-schema.test.d.ts.map +1 -0
- package/dist/migrate-theme-schema.test.js +100 -0
- package/dist/migrate-theme-schema.test.js.map +1 -0
- package/dist/portrait-resolver.d.ts +32 -0
- package/dist/portrait-resolver.d.ts.map +1 -0
- package/dist/portrait-resolver.js +225 -0
- package/dist/portrait-resolver.js.map +1 -0
- package/dist/portrait-resolver.test.d.ts +2 -0
- package/dist/portrait-resolver.test.d.ts.map +1 -0
- package/dist/portrait-resolver.test.js +156 -0
- package/dist/portrait-resolver.test.js.map +1 -0
- package/dist/skill-search.d.ts +36 -0
- package/dist/skill-search.d.ts.map +1 -0
- package/dist/skill-search.js +300 -0
- package/dist/skill-search.js.map +1 -0
- package/dist/skill-search.sh +41 -0
- package/dist/skill-search.test.d.ts +16 -0
- package/dist/skill-search.test.d.ts.map +1 -0
- package/dist/skill-search.test.js +177 -0
- package/dist/skill-search.test.js.map +1 -0
- package/dist/skill-suggest.d.ts +76 -0
- package/dist/skill-suggest.d.ts.map +1 -0
- package/dist/skill-suggest.js +256 -0
- package/dist/skill-suggest.js.map +1 -0
- package/dist/skill-suggest.test.d.ts +12 -0
- package/dist/skill-suggest.test.d.ts.map +1 -0
- package/dist/skill-suggest.test.js +257 -0
- package/dist/skill-suggest.test.js.map +1 -0
- package/dist/theme-loader.d.ts +81 -0
- package/dist/theme-loader.d.ts.map +1 -0
- package/dist/theme-loader.js +491 -0
- package/dist/theme-loader.js.map +1 -0
- package/dist/theme-loader.test.d.ts +8 -0
- package/dist/theme-loader.test.d.ts.map +1 -0
- package/dist/theme-loader.test.js +62 -0
- package/dist/theme-loader.test.js.map +1 -0
- package/package.json +14 -1
|
@@ -0,0 +1,177 @@
|
|
|
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 === 22, 'Should return all 22 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 4 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, 4, 'Should return 4 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', { skip: !existsSync(WRAPPER_PATH) }, () => {
|
|
152
|
+
// Skip all wrapper tests if skill-search.sh doesn't exist yet
|
|
153
|
+
it('should execute via bash wrapper', () => {
|
|
154
|
+
const result = execSync(`bash ${WRAPPER_PATH} --help`, { encoding: 'utf-8' });
|
|
155
|
+
assert.ok(result.includes('search') || result.includes('usage'), 'Should show help text');
|
|
156
|
+
});
|
|
157
|
+
it('should output JSON with --json flag via wrapper', () => {
|
|
158
|
+
const result = execSync(`bash ${WRAPPER_PATH} --json`, { encoding: 'utf-8' });
|
|
159
|
+
const parsed = JSON.parse(result);
|
|
160
|
+
assert.ok(Array.isArray(parsed), 'Should output valid JSON array');
|
|
161
|
+
});
|
|
162
|
+
it('should filter by tag via wrapper', () => {
|
|
163
|
+
const result = execSync(`bash ${WRAPPER_PATH} --tag tdd --json`, { encoding: 'utf-8' });
|
|
164
|
+
const parsed = JSON.parse(result);
|
|
165
|
+
assert.ok(parsed.length > 0, 'Should return results for tdd tag');
|
|
166
|
+
assert.ok(parsed.every((s) => s.tags.includes('tdd')), 'All results should have tdd tag');
|
|
167
|
+
});
|
|
168
|
+
it('should show human-readable table without --json flag', () => {
|
|
169
|
+
const result = execSync(`bash ${WRAPPER_PATH} --tag tdd`, { encoding: 'utf-8' });
|
|
170
|
+
// Should not be valid JSON (it's a table)
|
|
171
|
+
assert.throws(() => JSON.parse(result), 'Non-JSON output should not be valid JSON');
|
|
172
|
+
// Should contain skill name and some formatting
|
|
173
|
+
assert.ok(result.includes('testing'), 'Should show testing skill name');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
//# 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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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Suggestions Utility - Story 9-3
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent skill suggestions based on:
|
|
5
|
+
* - AC1: Session context (story, ACs, phase)
|
|
6
|
+
* - AC2: Keyword extraction and matching
|
|
7
|
+
* - AC3: Non-intrusive presentation (thresholds, limits)
|
|
8
|
+
*
|
|
9
|
+
* Reuses searchSkills() from skill-search.ts for registry access.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Session context for story-aware suggestions
|
|
13
|
+
*/
|
|
14
|
+
export interface SessionContext {
|
|
15
|
+
/** Story identifier (e.g., "9-3") */
|
|
16
|
+
storyId: string;
|
|
17
|
+
/** List of acceptance criteria */
|
|
18
|
+
acceptanceCriteria: string[];
|
|
19
|
+
/** Current workflow phase */
|
|
20
|
+
phase: string;
|
|
21
|
+
/** Optional Jira issue key */
|
|
22
|
+
jiraKey?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Options for skill suggestions
|
|
26
|
+
*/
|
|
27
|
+
export interface SuggestOptions {
|
|
28
|
+
/** Minimum confidence score (0-1) to include suggestion. Default: 0.3 */
|
|
29
|
+
confidenceThreshold?: number;
|
|
30
|
+
/** Maximum number of suggestions to return. Default: 5 */
|
|
31
|
+
maxResults?: number;
|
|
32
|
+
/** Skills to exclude from suggestions (already in use) */
|
|
33
|
+
excludeSkills?: string[];
|
|
34
|
+
/** Session context for combined suggestions */
|
|
35
|
+
sessionContext?: SessionContext;
|
|
36
|
+
/** Custom path to registry file (for testing) */
|
|
37
|
+
registryPath?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A skill suggestion with relevance scoring
|
|
41
|
+
*/
|
|
42
|
+
export interface SkillSuggestion {
|
|
43
|
+
/** Skill name */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Skill description */
|
|
46
|
+
description: string;
|
|
47
|
+
/** Relevance score (0-1) */
|
|
48
|
+
score: number;
|
|
49
|
+
/** Reason for suggesting this skill */
|
|
50
|
+
reason: string;
|
|
51
|
+
/** What triggered the match (keywords, tags, phase) */
|
|
52
|
+
matchedOn?: string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Suggest skills based on session context (story, ACs, phase)
|
|
56
|
+
*
|
|
57
|
+
* @param context - Session context with story details
|
|
58
|
+
* @returns Promise resolving to array of skill suggestions
|
|
59
|
+
*/
|
|
60
|
+
export declare function suggestFromSession(context: SessionContext): Promise<SkillSuggestion[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Suggest skills based on keywords in user input
|
|
63
|
+
*
|
|
64
|
+
* @param userInput - User's message or query
|
|
65
|
+
* @returns Promise resolving to array of skill suggestions
|
|
66
|
+
*/
|
|
67
|
+
export declare function suggestFromKeywords(userInput: string): Promise<SkillSuggestion[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Main suggestion function combining session context and keywords
|
|
70
|
+
*
|
|
71
|
+
* @param userInput - User's message or query
|
|
72
|
+
* @param options - Suggestion options (threshold, limits, exclusions)
|
|
73
|
+
* @returns Promise resolving to filtered, limited skill suggestions
|
|
74
|
+
*/
|
|
75
|
+
export declare function suggestSkills(userInput: string, options?: SuggestOptions): Promise<SkillSuggestion[]>;
|
|
76
|
+
//# sourceMappingURL=skill-suggest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-suggest.d.ts","sourceRoot":"","sources":["../src/skill-suggest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAyDD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CA0D5B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,EAAE,CAAC,CAqD5B;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,eAAe,EAAE,CAAC,CA2E5B"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Suggestions Utility - Story 9-3
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent skill suggestions based on:
|
|
5
|
+
* - AC1: Session context (story, ACs, phase)
|
|
6
|
+
* - AC2: Keyword extraction and matching
|
|
7
|
+
* - AC3: Non-intrusive presentation (thresholds, limits)
|
|
8
|
+
*
|
|
9
|
+
* Reuses searchSkills() from skill-search.ts for registry access.
|
|
10
|
+
*/
|
|
11
|
+
import { searchSkills } from './skill-search.js';
|
|
12
|
+
/** Phase to skill mappings for session-aware suggestions */
|
|
13
|
+
const PHASE_SKILL_MAP = {
|
|
14
|
+
testing: ['testing'],
|
|
15
|
+
development: ['dev-patterns', 'testing'],
|
|
16
|
+
review: ['code-review'],
|
|
17
|
+
planning: ['story-management', 'sprint-context'],
|
|
18
|
+
deployment: ['just', 'run-ci'],
|
|
19
|
+
};
|
|
20
|
+
/** Keywords that suggest specific skills */
|
|
21
|
+
const SKILL_KEYWORDS = {
|
|
22
|
+
testing: [
|
|
23
|
+
'test', 'tests', 'testing', 'tdd', 'unit', 'integration', 'jest', 'vitest',
|
|
24
|
+
'mocha', 'pytest', 'coverage', 'spec', 'specs', 'assertion', 'mock', 'stub'
|
|
25
|
+
],
|
|
26
|
+
'code-review': [
|
|
27
|
+
'review', 'reviewing', 'pr', 'pull request', 'code review', 'approve', 'reject'
|
|
28
|
+
],
|
|
29
|
+
'dev-patterns': [
|
|
30
|
+
'pattern', 'patterns', 'implement', 'implementation', 'refactor', 'fix', 'bug'
|
|
31
|
+
],
|
|
32
|
+
jira: [
|
|
33
|
+
'jira', 'ticket', 'issue', 'sprint', 'backlog', 'story', 'epic', 'mssci'
|
|
34
|
+
],
|
|
35
|
+
just: [
|
|
36
|
+
'just', 'justfile', 'recipe', 'task', 'run', 'build', 'script'
|
|
37
|
+
],
|
|
38
|
+
'run-ci': [
|
|
39
|
+
'ci', 'cd', 'pipeline', 'deploy', 'deployment', 'production', 'staging'
|
|
40
|
+
],
|
|
41
|
+
changelog: [
|
|
42
|
+
'changelog', 'release', 'version', 'document', 'documentation', 'docs', 'readme'
|
|
43
|
+
],
|
|
44
|
+
theme: [
|
|
45
|
+
'theme', 'themes', 'persona', 'character', 'style'
|
|
46
|
+
],
|
|
47
|
+
mermaid: [
|
|
48
|
+
'diagram', 'flowchart', 'sequence', 'architecture', 'uml', 'mermaid'
|
|
49
|
+
],
|
|
50
|
+
'story-management': [
|
|
51
|
+
'story', 'stories', 'sizing', 'estimate', 'points', 'planning'
|
|
52
|
+
],
|
|
53
|
+
'sprint-context': [
|
|
54
|
+
'sprint', 'backlog', 'velocity', 'status', 'current sprint'
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
/** Tags that map to skills */
|
|
58
|
+
const TAG_SKILL_MAP = {
|
|
59
|
+
quality: ['testing', 'code-review'],
|
|
60
|
+
tdd: ['testing'],
|
|
61
|
+
workflow: ['story-management', 'sprint-context'],
|
|
62
|
+
documentation: ['changelog', 'mermaid'],
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Suggest skills based on session context (story, ACs, phase)
|
|
66
|
+
*
|
|
67
|
+
* @param context - Session context with story details
|
|
68
|
+
* @returns Promise resolving to array of skill suggestions
|
|
69
|
+
*/
|
|
70
|
+
export async function suggestFromSession(context) {
|
|
71
|
+
// Return empty for missing context
|
|
72
|
+
if (!context.storyId && context.acceptanceCriteria.length === 0) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const suggestions = [];
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
// 1. Phase-based suggestions
|
|
78
|
+
const phaseSkills = PHASE_SKILL_MAP[context.phase] || [];
|
|
79
|
+
for (const skillName of phaseSkills) {
|
|
80
|
+
if (!seen.has(skillName)) {
|
|
81
|
+
seen.add(skillName);
|
|
82
|
+
suggestions.push({
|
|
83
|
+
name: skillName,
|
|
84
|
+
description: await getSkillDescription(skillName),
|
|
85
|
+
score: 0.8,
|
|
86
|
+
reason: `Suggested for ${context.phase} phase of the workflow`,
|
|
87
|
+
matchedOn: [`phase:${context.phase}`],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 2. AC-based suggestions - scan for keywords
|
|
92
|
+
const acText = context.acceptanceCriteria.join(' ').toLowerCase();
|
|
93
|
+
for (const [skillName, keywords] of Object.entries(SKILL_KEYWORDS)) {
|
|
94
|
+
if (seen.has(skillName))
|
|
95
|
+
continue;
|
|
96
|
+
const matchedKeywords = keywords.filter(kw => acText.includes(kw));
|
|
97
|
+
if (matchedKeywords.length > 0) {
|
|
98
|
+
seen.add(skillName);
|
|
99
|
+
suggestions.push({
|
|
100
|
+
name: skillName,
|
|
101
|
+
description: await getSkillDescription(skillName),
|
|
102
|
+
score: Math.min(0.5 + matchedKeywords.length * 0.1, 1.0),
|
|
103
|
+
reason: `Acceptance criteria mention: ${matchedKeywords.slice(0, 3).join(', ')}`,
|
|
104
|
+
matchedOn: matchedKeywords.map(kw => `keyword:${kw}`),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 3. Jira context
|
|
109
|
+
if (context.jiraKey && !seen.has('jira')) {
|
|
110
|
+
seen.add('jira');
|
|
111
|
+
suggestions.push({
|
|
112
|
+
name: 'jira',
|
|
113
|
+
description: await getSkillDescription('jira'),
|
|
114
|
+
score: 0.7,
|
|
115
|
+
reason: `Story has Jira reference: ${context.jiraKey}`,
|
|
116
|
+
matchedOn: [`jira:${context.jiraKey}`],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Sort by score descending
|
|
120
|
+
suggestions.sort((a, b) => b.score - a.score);
|
|
121
|
+
return suggestions;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Suggest skills based on keywords in user input
|
|
125
|
+
*
|
|
126
|
+
* @param userInput - User's message or query
|
|
127
|
+
* @returns Promise resolving to array of skill suggestions
|
|
128
|
+
*/
|
|
129
|
+
export async function suggestFromKeywords(userInput) {
|
|
130
|
+
if (!userInput || userInput.trim() === '') {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const inputLower = userInput.toLowerCase();
|
|
134
|
+
const suggestions = [];
|
|
135
|
+
// Check keyword matches
|
|
136
|
+
for (const [skillName, keywords] of Object.entries(SKILL_KEYWORDS)) {
|
|
137
|
+
const matchedKeywords = keywords.filter(kw => inputLower.includes(kw));
|
|
138
|
+
if (matchedKeywords.length > 0) {
|
|
139
|
+
// Score based on number of matches, normalized to 0-1
|
|
140
|
+
const score = Math.min(matchedKeywords.length * 0.2, 1.0);
|
|
141
|
+
suggestions.push({
|
|
142
|
+
name: skillName,
|
|
143
|
+
description: await getSkillDescription(skillName),
|
|
144
|
+
score,
|
|
145
|
+
reason: `Matched keywords: ${matchedKeywords.join(', ')}`,
|
|
146
|
+
matchedOn: matchedKeywords.map(kw => `keyword:${kw}`),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Check tag matches
|
|
151
|
+
for (const [tag, skillNames] of Object.entries(TAG_SKILL_MAP)) {
|
|
152
|
+
if (inputLower.includes(tag)) {
|
|
153
|
+
for (const skillName of skillNames) {
|
|
154
|
+
// Check if we already have this skill
|
|
155
|
+
const existing = suggestions.find(s => s.name === skillName);
|
|
156
|
+
if (existing) {
|
|
157
|
+
// Boost score and add tag to matchedOn
|
|
158
|
+
existing.score = Math.min(existing.score + 0.15, 1.0);
|
|
159
|
+
existing.matchedOn?.push(`tag:${tag}`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
suggestions.push({
|
|
163
|
+
name: skillName,
|
|
164
|
+
description: await getSkillDescription(skillName),
|
|
165
|
+
score: 0.4,
|
|
166
|
+
reason: `Matched tag: ${tag}`,
|
|
167
|
+
matchedOn: [`tag:${tag}`],
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Sort by score descending
|
|
174
|
+
suggestions.sort((a, b) => b.score - a.score);
|
|
175
|
+
return suggestions;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Main suggestion function combining session context and keywords
|
|
179
|
+
*
|
|
180
|
+
* @param userInput - User's message or query
|
|
181
|
+
* @param options - Suggestion options (threshold, limits, exclusions)
|
|
182
|
+
* @returns Promise resolving to filtered, limited skill suggestions
|
|
183
|
+
*/
|
|
184
|
+
export async function suggestSkills(userInput, options = {}) {
|
|
185
|
+
const { confidenceThreshold = 0.3, maxResults = 5, excludeSkills = [], sessionContext, registryPath, } = options;
|
|
186
|
+
// Handle empty input
|
|
187
|
+
if (!userInput || userInput.trim() === '') {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
// Handle missing registry gracefully
|
|
191
|
+
if (registryPath) {
|
|
192
|
+
try {
|
|
193
|
+
await searchSkills({ registryPath });
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Get keyword suggestions
|
|
200
|
+
const keywordSuggestions = await suggestFromKeywords(userInput);
|
|
201
|
+
// Get session suggestions if context provided
|
|
202
|
+
let sessionSuggestions = [];
|
|
203
|
+
if (sessionContext) {
|
|
204
|
+
sessionSuggestions = await suggestFromSession(sessionContext);
|
|
205
|
+
}
|
|
206
|
+
// Merge suggestions, boosting skills that appear in both
|
|
207
|
+
const merged = new Map();
|
|
208
|
+
for (const suggestion of keywordSuggestions) {
|
|
209
|
+
merged.set(suggestion.name, suggestion);
|
|
210
|
+
}
|
|
211
|
+
for (const suggestion of sessionSuggestions) {
|
|
212
|
+
const existing = merged.get(suggestion.name);
|
|
213
|
+
if (existing) {
|
|
214
|
+
// Strong boost when found in both sources - these are high priority
|
|
215
|
+
existing.score = Math.min(existing.score + suggestion.score, 1.0);
|
|
216
|
+
existing.matchedOn = [
|
|
217
|
+
...(existing.matchedOn || []),
|
|
218
|
+
...(suggestion.matchedOn || []),
|
|
219
|
+
];
|
|
220
|
+
if (suggestion.reason && !existing.reason.includes(suggestion.reason)) {
|
|
221
|
+
existing.reason = `${existing.reason}; ${suggestion.reason}`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
merged.set(suggestion.name, suggestion);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Convert to array and filter
|
|
229
|
+
let results = Array.from(merged.values());
|
|
230
|
+
// Exclude already-used skills
|
|
231
|
+
if (excludeSkills.length > 0) {
|
|
232
|
+
const excludeSet = new Set(excludeSkills.map(s => s.toLowerCase()));
|
|
233
|
+
results = results.filter(s => !excludeSet.has(s.name.toLowerCase()));
|
|
234
|
+
}
|
|
235
|
+
// Filter by confidence threshold
|
|
236
|
+
results = results.filter(s => s.score >= confidenceThreshold);
|
|
237
|
+
// Sort by score descending
|
|
238
|
+
results.sort((a, b) => b.score - a.score);
|
|
239
|
+
// Limit results
|
|
240
|
+
results = results.slice(0, maxResults);
|
|
241
|
+
return results;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get skill description from registry or use fallback
|
|
245
|
+
*/
|
|
246
|
+
async function getSkillDescription(skillName) {
|
|
247
|
+
try {
|
|
248
|
+
const results = await searchSkills({});
|
|
249
|
+
const skill = results.find(s => s.name.toLowerCase() === skillName.toLowerCase());
|
|
250
|
+
return skill?.description || `${skillName} skill`;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return `${skillName} skill`;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=skill-suggest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-suggest.js","sourceRoot":"","sources":["../src/skill-suggest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Story 9-3: Add Skill Suggestions to Agents
|
|
3
|
+
*
|
|
4
|
+
* These tests verify:
|
|
5
|
+
* - AC1: Session-aware suggestions based on story context
|
|
6
|
+
* - AC2: Keyword-triggered suggestions with scoring
|
|
7
|
+
* - AC3: Non-intrusive presentation (confidence threshold, limits)
|
|
8
|
+
*
|
|
9
|
+
* Run with: npm test -- packages/shared/src/skill-suggest.test.ts
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=skill-suggest.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-suggest.test.d.ts","sourceRoot":"","sources":["../src/skill-suggest.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|