@pennyfarthing/shared 7.5.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/browser.d.ts +6 -0
  2. package/dist/browser.d.ts.map +1 -0
  3. package/dist/browser.js +8 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/generate-skill-docs.d.ts +35 -0
  6. package/dist/generate-skill-docs.d.ts.map +1 -0
  7. package/dist/generate-skill-docs.js +442 -0
  8. package/dist/generate-skill-docs.js.map +1 -0
  9. package/dist/generate-skill-docs.test.d.ts +13 -0
  10. package/dist/generate-skill-docs.test.d.ts.map +1 -0
  11. package/dist/generate-skill-docs.test.js +519 -0
  12. package/dist/generate-skill-docs.test.js.map +1 -0
  13. package/dist/index.d.ts +11 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +13 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/marker/constants.d.ts +39 -0
  18. package/dist/marker/constants.d.ts.map +1 -0
  19. package/dist/marker/constants.js +46 -0
  20. package/dist/marker/constants.js.map +1 -0
  21. package/dist/marker/continue.test.d.ts +12 -0
  22. package/dist/marker/continue.test.d.ts.map +1 -0
  23. package/dist/marker/continue.test.js +76 -0
  24. package/dist/marker/continue.test.js.map +1 -0
  25. package/dist/marker/detect.d.ts +25 -0
  26. package/dist/marker/detect.d.ts.map +1 -0
  27. package/dist/marker/detect.js +53 -0
  28. package/dist/marker/detect.js.map +1 -0
  29. package/dist/marker/detect.test.d.ts +10 -0
  30. package/dist/marker/detect.test.d.ts.map +1 -0
  31. package/dist/marker/detect.test.js +418 -0
  32. package/dist/marker/detect.test.js.map +1 -0
  33. package/dist/marker/index.d.ts +14 -0
  34. package/dist/marker/index.d.ts.map +1 -0
  35. package/dist/marker/index.js +15 -0
  36. package/dist/marker/index.js.map +1 -0
  37. package/dist/marker/strip.d.ts +26 -0
  38. package/dist/marker/strip.d.ts.map +1 -0
  39. package/dist/marker/strip.js +39 -0
  40. package/dist/marker/strip.js.map +1 -0
  41. package/dist/marker/types.d.ts +23 -0
  42. package/dist/marker/types.d.ts.map +1 -0
  43. package/dist/marker/types.js +7 -0
  44. package/dist/marker/types.js.map +1 -0
  45. package/dist/migrate-theme-schema.test.d.ts +8 -0
  46. package/dist/migrate-theme-schema.test.d.ts.map +1 -0
  47. package/dist/migrate-theme-schema.test.js +100 -0
  48. package/dist/migrate-theme-schema.test.js.map +1 -0
  49. package/dist/portrait-resolver.d.ts +32 -0
  50. package/dist/portrait-resolver.d.ts.map +1 -0
  51. package/dist/portrait-resolver.js +225 -0
  52. package/dist/portrait-resolver.js.map +1 -0
  53. package/dist/portrait-resolver.test.d.ts +2 -0
  54. package/dist/portrait-resolver.test.d.ts.map +1 -0
  55. package/dist/portrait-resolver.test.js +156 -0
  56. package/dist/portrait-resolver.test.js.map +1 -0
  57. package/dist/skill-search.d.ts +36 -0
  58. package/dist/skill-search.d.ts.map +1 -0
  59. package/dist/skill-search.js +300 -0
  60. package/dist/skill-search.js.map +1 -0
  61. package/dist/skill-search.sh +41 -0
  62. package/dist/skill-search.test.d.ts +16 -0
  63. package/dist/skill-search.test.d.ts.map +1 -0
  64. package/dist/skill-search.test.js +177 -0
  65. package/dist/skill-search.test.js.map +1 -0
  66. package/dist/skill-suggest.d.ts +76 -0
  67. package/dist/skill-suggest.d.ts.map +1 -0
  68. package/dist/skill-suggest.js +256 -0
  69. package/dist/skill-suggest.js.map +1 -0
  70. package/dist/skill-suggest.test.d.ts +12 -0
  71. package/dist/skill-suggest.test.d.ts.map +1 -0
  72. package/dist/skill-suggest.test.js +257 -0
  73. package/dist/skill-suggest.test.js.map +1 -0
  74. package/dist/theme-loader.d.ts +81 -0
  75. package/dist/theme-loader.d.ts.map +1 -0
  76. package/dist/theme-loader.js +491 -0
  77. package/dist/theme-loader.js.map +1 -0
  78. package/dist/theme-loader.test.d.ts +8 -0
  79. package/dist/theme-loader.test.d.ts.map +1 -0
  80. package/dist/theme-loader.test.js +62 -0
  81. package/dist/theme-loader.test.js.map +1 -0
  82. package/package.json +14 -1
@@ -0,0 +1,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"}