@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.
- 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 +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.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 +147 -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 +193 -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 +35 -0
- package/dist/theme-loader.d.ts.map +1 -0
- package/dist/theme-loader.js +170 -0
- package/dist/theme-loader.js.map +1 -0
- package/dist/theme-loader.test.d.ts +2 -0
- package/dist/theme-loader.test.d.ts.map +1 -0
- package/dist/theme-loader.test.js +72 -0
- package/dist/theme-loader.test.js.map +1 -0
- 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":"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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"}
|
|
@@ -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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"}
|