@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,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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAgDjD,4DAA4D;AAC5D,MAAM,eAAe,GAA6B;IAChD,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,WAAW,EAAE,CAAC,cAAc,EAAE,SAAS,CAAC;IACxC,MAAM,EAAE,CAAC,aAAa,CAAC;IACvB,QAAQ,EAAE,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAChD,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC/B,CAAC;AAEF,4CAA4C;AAC5C,MAAM,cAAc,GAA6B;IAC/C,OAAO,EAAE;QACP,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ;QAC1E,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM;KAC5E;IACD,aAAa,EAAE;QACb,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ;KAChF;IACD,cAAc,EAAE;QACd,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK;KAC/E;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO;KACzE;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ;KAC/D;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS;KACxE;IACD,SAAS,EAAE;QACT,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ;KACjF;IACD,KAAK,EAAE;QACL,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO;KACnD;IACD,OAAO,EAAE;QACP,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS;KACrE;IACD,kBAAkB,EAAE;QAClB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU;KAC/D;IACD,gBAAgB,EAAE;QAChB,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,gBAAgB;KAC5D;CACF,CAAC;AAEF,8BAA8B;AAC9B,MAAM,aAAa,GAA6B;IAC9C,OAAO,EAAE,CAAC,SAAS,EAAE,aAAa,CAAC;IACnC,GAAG,EAAE,CAAC,SAAS,CAAC;IAChB,QAAQ,EAAE,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAChD,aAAa,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC;CACxC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAuB;IAEvB,mCAAmC;IACnC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,6BAA6B;IAC7B,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACzD,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACpB,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,MAAM,mBAAmB,CAAC,SAAS,CAAC;gBACjD,KAAK,EAAE,GAAG;gBACV,MAAM,EAAE,iBAAiB,OAAO,CAAC,KAAK,wBAAwB;gBAC9D,SAAS,EAAE,CAAC,SAAS,OAAO,CAAC,KAAK,EAAE,CAAC;aACtC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,8CAA8C;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAClE,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACnE,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,SAAS;QAElC,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QACnE,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACpB,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,MAAM,mBAAmB,CAAC,SAAS,CAAC;gBACjD,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,eAAe,CAAC,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC;gBACxD,MAAM,EAAE,gCAAgC,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAChF,SAAS,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;aACtD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,kBAAkB;IAClB,IAAI,OAAO,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,MAAM,mBAAmB,CAAC,MAAM,CAAC;YAC9C,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,6BAA6B,OAAO,CAAC,OAAO,EAAE;YACtD,SAAS,EAAE,CAAC,QAAQ,OAAO,CAAC,OAAO,EAAE,CAAC;SACvC,CAAC,CAAC;IACL,CAAC;IAED,2BAA2B;IAC3B,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAE9C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB;IAEjB,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1C,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,WAAW,GAAsB,EAAE,CAAC;IAE1C,wBAAwB;IACxB,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACnE,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAEvE,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,sDAAsD;YACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;YAE1D,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,MAAM,mBAAmB,CAAC,SAAS,CAAC;gBACjD,KAAK;gBACL,MAAM,EAAE,qBAAqB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACzD,SAAS,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;aACtD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9D,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,sCAAsC;gBACtC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;gBAC7D,IAAI,QAAQ,EAAE,CAAC;oBACb,uCAAuC;oBACvC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC;oBACtD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACN,WAAW,CAAC,IAAI,CAAC;wBACf,IAAI,EAAE,SAAS;wBACf,WAAW,EAAE,MAAM,mBAAmB,CAAC,SAAS,CAAC;wBACjD,KAAK,EAAE,GAAG;wBACV,MAAM,EAAE,gBAAgB,GAAG,EAAE;wBAC7B,SAAS,EAAE,CAAC,OAAO,GAAG,EAAE,CAAC;qBAC1B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,2BAA2B;IAC3B,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAE9C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,UAA0B,EAAE;IAE5B,MAAM,EACJ,mBAAmB,GAAG,GAAG,EACzB,UAAU,GAAG,CAAC,EACd,aAAa,GAAG,EAAE,EAClB,cAAc,EACd,YAAY,GACb,GAAG,OAAO,CAAC;IAEZ,qBAAqB;IACrB,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1C,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,qCAAqC;IACrC,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,kBAAkB,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAEhE,8CAA8C;IAC9C,IAAI,kBAAkB,GAAsB,EAAE,CAAC;IAC/C,IAAI,cAAc,EAAE,CAAC;QACnB,kBAAkB,GAAG,MAAM,kBAAkB,CAAC,cAAc,CAAC,CAAC;IAChE,CAAC;IAED,yDAAyD;IACzD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAElD,KAAK,MAAM,UAAU,IAAI,kBAAkB,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,kBAAkB,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,oEAAoE;YACpE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAClE,QAAQ,CAAC,SAAS,GAAG;gBACnB,GAAG,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;gBAC7B,GAAG,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC;aAChC,CAAC;YACF,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtE,QAAQ,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;YAC/D,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAE1C,8BAA8B;IAC9B,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACpE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,iCAAiC;IACjC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,mBAAmB,CAAC,CAAC;IAE9D,2BAA2B;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAE1C,gBAAgB;IAChB,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAEvC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IAClD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,WAAW,EAAE,CACtD,CAAC;QACF,OAAO,KAAK,EAAE,WAAW,IAAI,GAAG,SAAS,QAAQ,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,SAAS,QAAQ,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -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"}
@@ -0,0 +1,257 @@
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
+ import { describe, it } from 'node:test';
12
+ import assert from 'node:assert';
13
+ // Import functions to test - these don't exist yet, tests should fail (RED)
14
+ import { suggestSkills, suggestFromSession, suggestFromKeywords, } from './skill-suggest.js';
15
+ describe('Story 9-3: Skill Suggestions', () => {
16
+ describe('AC1: Session-aware suggestions', () => {
17
+ it('should suggest skills based on story acceptance criteria', async () => {
18
+ // AC1: Agents suggest skills when relevant (session-aware)
19
+ const sessionContext = {
20
+ storyId: '9-3',
21
+ acceptanceCriteria: [
22
+ 'Write failing tests for the feature',
23
+ 'Implement TDD workflow',
24
+ ],
25
+ phase: 'testing',
26
+ };
27
+ const suggestions = await suggestFromSession(sessionContext);
28
+ assert.ok(Array.isArray(suggestions), 'Should return array of suggestions');
29
+ assert.ok(suggestions.length > 0, 'Should suggest at least one skill');
30
+ const skillNames = suggestions.map(s => s.name);
31
+ assert.ok(skillNames.includes('testing'), 'Should suggest testing skill for TDD-related ACs');
32
+ });
33
+ it('should suggest skills based on current workflow phase', async () => {
34
+ // AC1: Phase-aware suggestions
35
+ const sessionContext = {
36
+ storyId: '9-3',
37
+ acceptanceCriteria: ['Deploy the feature'],
38
+ phase: 'review',
39
+ };
40
+ const suggestions = await suggestFromSession(sessionContext);
41
+ // Review phase should suggest code-review skill
42
+ const skillNames = suggestions.map(s => s.name);
43
+ assert.ok(skillNames.includes('code-review'), 'Should suggest code-review skill during review phase');
44
+ });
45
+ it('should include relevance reason for each suggestion', async () => {
46
+ // AC1: Suggestions should explain why they're relevant
47
+ const sessionContext = {
48
+ storyId: '9-3',
49
+ acceptanceCriteria: ['Add unit tests'],
50
+ phase: 'testing',
51
+ };
52
+ const suggestions = await suggestFromSession(sessionContext);
53
+ assert.ok(suggestions.length > 0, 'Should have suggestions');
54
+ for (const suggestion of suggestions) {
55
+ assert.ok(suggestion.reason, `Suggestion ${suggestion.name} should have a reason`);
56
+ assert.ok(suggestion.reason.length > 10, `Reason should be descriptive: ${suggestion.reason}`);
57
+ }
58
+ });
59
+ it('should return empty array when no story context available', async () => {
60
+ // AC1: Graceful handling of missing context
61
+ const emptyContext = {
62
+ storyId: '',
63
+ acceptanceCriteria: [],
64
+ phase: 'unknown',
65
+ };
66
+ const suggestions = await suggestFromSession(emptyContext);
67
+ assert.ok(Array.isArray(suggestions), 'Should return array');
68
+ assert.strictEqual(suggestions.length, 0, 'Should return empty array for no context');
69
+ });
70
+ it('should suggest jira skill when story has Jira reference', async () => {
71
+ // AC1: Detect Jira context
72
+ const sessionContext = {
73
+ storyId: '9-3',
74
+ jiraKey: 'MSSCI-11518',
75
+ acceptanceCriteria: ['Update Jira status'],
76
+ phase: 'development',
77
+ };
78
+ const suggestions = await suggestFromSession(sessionContext);
79
+ const skillNames = suggestions.map(s => s.name);
80
+ assert.ok(skillNames.includes('jira'), 'Should suggest jira skill when Jira key is present');
81
+ });
82
+ });
83
+ describe('AC2: Keyword-triggered suggestions', () => {
84
+ it('should extract keywords from user input and match skills', async () => {
85
+ // AC2: Suggestions based on task analysis (keyword extraction)
86
+ const userInput = 'I need to write some tests for this feature';
87
+ const suggestions = await suggestFromKeywords(userInput);
88
+ assert.ok(Array.isArray(suggestions), 'Should return array');
89
+ assert.ok(suggestions.length > 0, 'Should find matching skills');
90
+ const skillNames = suggestions.map(s => s.name);
91
+ assert.ok(skillNames.includes('testing'), 'Should suggest testing skill for "tests" keyword');
92
+ });
93
+ it('should match skills by their registered keywords', async () => {
94
+ // AC2: Registry matching
95
+ const userInput = 'How do I use vitest for unit testing?';
96
+ const suggestions = await suggestFromKeywords(userInput);
97
+ // vitest is a keyword for testing skill
98
+ const testingSuggestion = suggestions.find(s => s.name === 'testing');
99
+ assert.ok(testingSuggestion, 'Should find testing skill via vitest keyword');
100
+ });
101
+ it('should score suggestions by keyword match count', async () => {
102
+ // AC2: Scoring mechanism
103
+ const userInput = 'I need to run tests, write tests, and check test coverage';
104
+ const suggestions = await suggestFromKeywords(userInput);
105
+ assert.ok(suggestions.length > 0, 'Should have suggestions');
106
+ // First suggestion should have highest score
107
+ for (let i = 1; i < suggestions.length; i++) {
108
+ assert.ok(suggestions[i - 1].score >= suggestions[i].score, 'Suggestions should be sorted by score descending');
109
+ }
110
+ });
111
+ it('should include score in each suggestion', async () => {
112
+ // AC2: Each suggestion has a relevance score
113
+ const userInput = 'deploy the application to production';
114
+ const suggestions = await suggestFromKeywords(userInput);
115
+ for (const suggestion of suggestions) {
116
+ assert.ok(typeof suggestion.score === 'number', `Suggestion ${suggestion.name} should have numeric score`);
117
+ assert.ok(suggestion.score >= 0 && suggestion.score <= 1, `Score should be between 0 and 1: ${suggestion.score}`);
118
+ }
119
+ });
120
+ it('should return empty array for input with no skill keywords', async () => {
121
+ // AC2: Graceful handling of no matches
122
+ const userInput = 'hello world this is random text';
123
+ const suggestions = await suggestFromKeywords(userInput);
124
+ assert.ok(Array.isArray(suggestions), 'Should return array');
125
+ assert.strictEqual(suggestions.length, 0, 'Should return empty for no keyword matches');
126
+ });
127
+ it('should handle case-insensitive keyword matching', async () => {
128
+ // AC2: Case insensitivity
129
+ const lowerInput = 'run jest tests';
130
+ const upperInput = 'Run JEST Tests';
131
+ const lowerSuggestions = await suggestFromKeywords(lowerInput);
132
+ const upperSuggestions = await suggestFromKeywords(upperInput);
133
+ assert.deepStrictEqual(lowerSuggestions.map(s => s.name).sort(), upperSuggestions.map(s => s.name).sort(), 'Keyword matching should be case-insensitive');
134
+ });
135
+ it('should match skill tags in addition to keywords', async () => {
136
+ // AC2: Tag matching
137
+ const userInput = 'I need help with quality assurance';
138
+ const suggestions = await suggestFromKeywords(userInput);
139
+ // 'quality' is a tag, should match skills with that tag
140
+ const hasQualitySkill = suggestions.some(s => s.matchedOn?.includes('tag:quality'));
141
+ assert.ok(hasQualitySkill, 'Should match skills by tag');
142
+ });
143
+ });
144
+ describe('AC3: Non-intrusive presentation', () => {
145
+ it('should only return suggestions above confidence threshold', async () => {
146
+ // AC3: Contextual, not every message
147
+ const options = {
148
+ confidenceThreshold: 0.5,
149
+ };
150
+ const userInput = 'maybe something about testing I guess';
151
+ const suggestions = await suggestSkills(userInput, options);
152
+ for (const suggestion of suggestions) {
153
+ assert.ok(suggestion.score >= 0.5, `All suggestions should be above threshold: ${suggestion.name} has ${suggestion.score}`);
154
+ }
155
+ });
156
+ it('should limit number of suggestions returned', async () => {
157
+ // AC3: Non-intrusive - don't overwhelm with suggestions
158
+ const options = {
159
+ maxResults: 3,
160
+ };
161
+ const userInput = 'I need to test, deploy, review, document everything';
162
+ const suggestions = await suggestSkills(userInput, options);
163
+ assert.ok(suggestions.length <= 3, `Should return at most ${options.maxResults} suggestions`);
164
+ });
165
+ it('should exclude already-used skills from suggestions', async () => {
166
+ // AC3: Contextual - don't suggest what they're already using
167
+ const options = {
168
+ excludeSkills: ['testing'],
169
+ };
170
+ const userInput = 'I need to write more tests';
171
+ const suggestions = await suggestSkills(userInput, options);
172
+ const skillNames = suggestions.map(s => s.name);
173
+ assert.ok(!skillNames.includes('testing'), 'Should not suggest excluded skills');
174
+ });
175
+ it('should return empty array when all matches are below threshold', async () => {
176
+ // AC3: Don't suggest low-confidence matches
177
+ const options = {
178
+ confidenceThreshold: 0.99,
179
+ };
180
+ const userInput = 'something vaguely related to code';
181
+ const suggestions = await suggestSkills(userInput, options);
182
+ assert.ok(Array.isArray(suggestions), 'Should return array');
183
+ assert.strictEqual(suggestions.length, 0, 'Should return empty when below threshold');
184
+ });
185
+ it('should combine session context and keywords when both provided', async () => {
186
+ // AC3: Proactive - detect helpful moments
187
+ const sessionContext = {
188
+ storyId: '9-3',
189
+ acceptanceCriteria: ['Add documentation'],
190
+ phase: 'development',
191
+ };
192
+ const options = {
193
+ sessionContext,
194
+ };
195
+ const userInput = 'how do I write good docs?';
196
+ const suggestions = await suggestSkills(userInput, options);
197
+ // Should boost documentation-related skills
198
+ assert.ok(suggestions.length > 0, 'Should have suggestions');
199
+ // First suggestion should be docs-related given both context and input mention it
200
+ const topSuggestion = suggestions[0];
201
+ assert.ok(topSuggestion.name.includes('changelog') ||
202
+ topSuggestion.reason?.toLowerCase().includes('document'), 'Top suggestion should be documentation-related');
203
+ });
204
+ it('should respect default options when none provided', async () => {
205
+ // AC3: Sensible defaults
206
+ const userInput = 'run the tests';
207
+ const suggestions = await suggestSkills(userInput);
208
+ // Should work with defaults
209
+ assert.ok(Array.isArray(suggestions), 'Should return array with default options');
210
+ // Default threshold should filter out very low matches
211
+ for (const suggestion of suggestions) {
212
+ assert.ok(suggestion.score >= 0.3, 'Default threshold should be at least 0.3');
213
+ }
214
+ });
215
+ });
216
+ describe('SkillSuggestion interface', () => {
217
+ it('should include name, description, score, and reason', async () => {
218
+ // Verify the shape of returned suggestions
219
+ const userInput = 'write tests';
220
+ const suggestions = await suggestSkills(userInput);
221
+ assert.ok(suggestions.length > 0, 'Should have suggestions');
222
+ const suggestion = suggestions[0];
223
+ assert.ok('name' in suggestion, 'Should have name');
224
+ assert.ok('description' in suggestion, 'Should have description');
225
+ assert.ok('score' in suggestion, 'Should have score');
226
+ assert.ok('reason' in suggestion, 'Should have reason');
227
+ });
228
+ it('should optionally include matchedOn for debugging', async () => {
229
+ // Shows what triggered the suggestion
230
+ const userInput = 'run jest tests';
231
+ const suggestions = await suggestSkills(userInput);
232
+ const testingSuggestion = suggestions.find(s => s.name === 'testing');
233
+ if (testingSuggestion) {
234
+ assert.ok('matchedOn' in testingSuggestion, 'Should include matchedOn field');
235
+ assert.ok(Array.isArray(testingSuggestion.matchedOn), 'matchedOn should be array');
236
+ }
237
+ });
238
+ });
239
+ describe('Error handling', () => {
240
+ it('should handle invalid input gracefully', async () => {
241
+ // Empty input
242
+ const suggestions = await suggestSkills('');
243
+ assert.ok(Array.isArray(suggestions), 'Should return array for empty input');
244
+ assert.strictEqual(suggestions.length, 0, 'Should return empty for empty input');
245
+ });
246
+ it('should handle missing registry gracefully', async () => {
247
+ // If registry not found, should return empty (not crash)
248
+ const options = {
249
+ registryPath: '/nonexistent/path/registry.yaml',
250
+ };
251
+ const suggestions = await suggestSkills('test something', options);
252
+ // Should gracefully return empty, not throw
253
+ assert.ok(Array.isArray(suggestions), 'Should return array even with missing registry');
254
+ });
255
+ });
256
+ });
257
+ //# sourceMappingURL=skill-suggest.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-suggest.test.js","sourceRoot":"","sources":["../src/skill-suggest.test.ts"],"names":[],"mappings":"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"}