@skillsmith/mcp-server 0.4.2 → 0.4.4
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/README.md +11 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/__tests__/LocalIndexer.test.js +158 -0
- package/dist/src/__tests__/LocalIndexer.test.js.map +1 -1
- package/dist/src/__tests__/compare.test.d.ts +8 -0
- package/dist/src/__tests__/compare.test.d.ts.map +1 -0
- package/dist/src/__tests__/compare.test.js +162 -0
- package/dist/src/__tests__/compare.test.js.map +1 -0
- package/dist/src/__tests__/context.async.test.d.ts +8 -0
- package/dist/src/__tests__/context.async.test.d.ts.map +1 -0
- package/dist/src/__tests__/context.async.test.js +223 -0
- package/dist/src/__tests__/context.async.test.js.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts +10 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.js +93 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.js.map +1 -0
- package/dist/src/__tests__/middleware/license-renewal.test.d.ts +10 -0
- package/dist/src/__tests__/middleware/license-renewal.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/license-renewal.test.js +152 -0
- package/dist/src/__tests__/middleware/license-renewal.test.js.map +1 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.d.ts +9 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.js +105 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.js.map +1 -0
- package/dist/src/__tests__/middleware/quota.test.d.ts +12 -0
- package/dist/src/__tests__/middleware/quota.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/quota.test.js +189 -0
- package/dist/src/__tests__/middleware/quota.test.js.map +1 -0
- package/dist/src/__tests__/recommend-online-path.test.d.ts +10 -0
- package/dist/src/__tests__/recommend-online-path.test.d.ts.map +1 -0
- package/dist/src/__tests__/recommend-online-path.test.js +225 -0
- package/dist/src/__tests__/recommend-online-path.test.js.map +1 -0
- package/dist/src/__tests__/recommend.test.d.ts +2 -0
- package/dist/src/__tests__/recommend.test.d.ts.map +1 -1
- package/dist/src/__tests__/recommend.test.js +14 -2
- package/dist/src/__tests__/recommend.test.js.map +1 -1
- package/dist/src/__tests__/search-online-path.test.d.ts +10 -0
- package/dist/src/__tests__/search-online-path.test.d.ts.map +1 -0
- package/dist/src/__tests__/search-online-path.test.js +140 -0
- package/dist/src/__tests__/search-online-path.test.js.map +1 -0
- package/dist/src/__tests__/search.test.js +153 -5
- package/dist/src/__tests__/search.test.js.map +1 -1
- package/dist/src/context/project-detector.d.ts.map +1 -1
- package/dist/src/context/project-detector.js +1 -0
- package/dist/src/context/project-detector.js.map +1 -1
- package/dist/src/context.async.d.ts +48 -0
- package/dist/src/context.async.d.ts.map +1 -0
- package/dist/src/context.async.js +217 -0
- package/dist/src/context.async.js.map +1 -0
- package/dist/src/context.d.ts +5 -145
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.helpers.d.ts +25 -0
- package/dist/src/context.helpers.d.ts.map +1 -0
- package/dist/src/context.helpers.js +49 -0
- package/dist/src/context.helpers.js.map +1 -0
- package/dist/src/context.js +13 -228
- package/dist/src/context.js.map +1 -1
- package/dist/src/context.types.d.ts +112 -0
- package/dist/src/context.types.d.ts.map +1 -0
- package/dist/src/context.types.js +10 -0
- package/dist/src/context.types.js.map +1 -0
- package/dist/src/health/readinessCheck.d.ts +1 -1
- package/dist/src/health/readinessCheck.d.ts.map +1 -1
- package/dist/src/index.js +25 -152
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/FrontmatterParser.d.ts +6 -0
- package/dist/src/indexer/FrontmatterParser.d.ts.map +1 -1
- package/dist/src/indexer/FrontmatterParser.js +15 -0
- package/dist/src/indexer/FrontmatterParser.js.map +1 -1
- package/dist/src/indexer/LocalIndexer.d.ts +4 -0
- package/dist/src/indexer/LocalIndexer.d.ts.map +1 -1
- package/dist/src/indexer/LocalIndexer.js +3 -0
- package/dist/src/indexer/LocalIndexer.js.map +1 -1
- package/dist/src/middleware/degradation.d.ts.map +1 -1
- package/dist/src/middleware/degradation.js +8 -0
- package/dist/src/middleware/degradation.js.map +1 -1
- package/dist/src/middleware/errorFormatter.builders.d.ts +49 -0
- package/dist/src/middleware/errorFormatter.builders.d.ts.map +1 -0
- package/dist/src/middleware/errorFormatter.builders.js +237 -0
- package/dist/src/middleware/errorFormatter.builders.js.map +1 -0
- package/dist/src/middleware/errorFormatter.d.ts +5 -100
- package/dist/src/middleware/errorFormatter.d.ts.map +1 -1
- package/dist/src/middleware/errorFormatter.js +16 -238
- package/dist/src/middleware/errorFormatter.js.map +1 -1
- package/dist/src/middleware/errorFormatter.types.d.ts +81 -0
- package/dist/src/middleware/errorFormatter.types.d.ts.map +1 -0
- package/dist/src/middleware/errorFormatter.types.js +34 -0
- package/dist/src/middleware/errorFormatter.types.js.map +1 -0
- package/dist/src/middleware/toolFeatureMapping.d.ts +1 -1
- package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -1
- package/dist/src/middleware/toolFeatureMapping.js +10 -0
- package/dist/src/middleware/toolFeatureMapping.js.map +1 -1
- package/dist/src/tool-dispatch.d.ts +27 -0
- package/dist/src/tool-dispatch.d.ts.map +1 -0
- package/dist/src/tool-dispatch.js +142 -0
- package/dist/src/tool-dispatch.js.map +1 -0
- package/dist/src/tools/LocalSkillSearch.d.ts.map +1 -1
- package/dist/src/tools/LocalSkillSearch.js +4 -0
- package/dist/src/tools/LocalSkillSearch.js.map +1 -1
- package/dist/src/tools/compare.types.d.ts +5 -1
- package/dist/src/tools/compare.types.d.ts.map +1 -1
- package/dist/src/tools/compare.types.js.map +1 -1
- package/dist/src/tools/get-skill.d.ts.map +1 -1
- package/dist/src/tools/get-skill.js +30 -0
- package/dist/src/tools/get-skill.js.map +1 -1
- package/dist/src/tools/index.d.ts +8 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +8 -0
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/install.d.ts +7 -58
- package/dist/src/tools/install.d.ts.map +1 -1
- package/dist/src/tools/install.dep-helpers.d.ts +41 -0
- package/dist/src/tools/install.dep-helpers.d.ts.map +1 -0
- package/dist/src/tools/install.dep-helpers.js +68 -0
- package/dist/src/tools/install.dep-helpers.js.map +1 -0
- package/dist/src/tools/install.dep-helpers.test.d.ts +6 -0
- package/dist/src/tools/install.dep-helpers.test.d.ts.map +1 -0
- package/dist/src/tools/install.dep-helpers.test.js +109 -0
- package/dist/src/tools/install.dep-helpers.test.js.map +1 -0
- package/dist/src/tools/install.js +49 -109
- package/dist/src/tools/install.js.map +1 -1
- package/dist/src/tools/install.optimize.d.ts +46 -0
- package/dist/src/tools/install.optimize.d.ts.map +1 -0
- package/dist/src/tools/install.optimize.js +67 -0
- package/dist/src/tools/install.optimize.js.map +1 -0
- package/dist/src/tools/install.tool.d.ts +44 -0
- package/dist/src/tools/install.tool.d.ts.map +1 -0
- package/dist/src/tools/install.tool.js +44 -0
- package/dist/src/tools/install.tool.js.map +1 -0
- package/dist/src/tools/install.types.d.ts +14 -1
- package/dist/src/tools/install.types.d.ts.map +1 -1
- package/dist/src/tools/install.types.js.map +1 -1
- package/dist/src/tools/outdated.d.ts +101 -0
- package/dist/src/tools/outdated.d.ts.map +1 -0
- package/dist/src/tools/outdated.js +214 -0
- package/dist/src/tools/outdated.js.map +1 -0
- package/dist/src/tools/outdated.test.d.ts +6 -0
- package/dist/src/tools/outdated.test.d.ts.map +1 -0
- package/dist/src/tools/outdated.test.js +287 -0
- package/dist/src/tools/outdated.test.js.map +1 -0
- package/dist/src/tools/recommend.d.ts +2 -4
- package/dist/src/tools/recommend.d.ts.map +1 -1
- package/dist/src/tools/recommend.format.d.ts +28 -0
- package/dist/src/tools/recommend.format.d.ts.map +1 -0
- package/dist/src/tools/recommend.format.js +111 -0
- package/dist/src/tools/recommend.format.js.map +1 -0
- package/dist/src/tools/recommend.js +6 -97
- package/dist/src/tools/recommend.js.map +1 -1
- package/dist/src/tools/recommend.types.d.ts +2 -2
- package/dist/src/tools/search.d.ts +24 -21
- package/dist/src/tools/search.d.ts.map +1 -1
- package/dist/src/tools/search.formatter.d.ts +30 -0
- package/dist/src/tools/search.formatter.d.ts.map +1 -0
- package/dist/src/tools/search.formatter.js +64 -0
- package/dist/src/tools/search.formatter.js.map +1 -0
- package/dist/src/tools/search.js +55 -54
- package/dist/src/tools/search.js.map +1 -1
- package/dist/src/tools/skill-audit.d.ts +98 -0
- package/dist/src/tools/skill-audit.d.ts.map +1 -0
- package/dist/src/tools/skill-audit.js +105 -0
- package/dist/src/tools/skill-audit.js.map +1 -0
- package/dist/src/tools/skill-audit.test.d.ts +6 -0
- package/dist/src/tools/skill-audit.test.d.ts.map +1 -0
- package/dist/src/tools/skill-audit.test.js +121 -0
- package/dist/src/tools/skill-audit.test.js.map +1 -0
- package/dist/src/tools/skill-diff.d.ts +107 -0
- package/dist/src/tools/skill-diff.d.ts.map +1 -0
- package/dist/src/tools/skill-diff.js +268 -0
- package/dist/src/tools/skill-diff.js.map +1 -0
- package/dist/src/tools/skill-diff.test.d.ts +6 -0
- package/dist/src/tools/skill-diff.test.d.ts.map +1 -0
- package/dist/src/tools/skill-diff.test.js +260 -0
- package/dist/src/tools/skill-diff.test.js.map +1 -0
- package/dist/src/tools/skill-pack-audit.d.ts +96 -0
- package/dist/src/tools/skill-pack-audit.d.ts.map +1 -0
- package/dist/src/tools/skill-pack-audit.js +183 -0
- package/dist/src/tools/skill-pack-audit.js.map +1 -0
- package/dist/src/tools/skill-updates.d.ts +1 -1
- package/dist/src/tools/skill-updates.d.ts.map +1 -1
- package/dist/src/tools/uninstall.d.ts.map +1 -1
- package/dist/src/tools/uninstall.js +9 -0
- package/dist/src/tools/uninstall.js.map +1 -1
- package/dist/src/tools/validate.d.ts.map +1 -1
- package/dist/src/tools/validate.dep.test.d.ts +6 -0
- package/dist/src/tools/validate.dep.test.d.ts.map +1 -0
- package/dist/src/tools/validate.dep.test.js +77 -0
- package/dist/src/tools/validate.dep.test.js.map +1 -0
- package/dist/src/tools/validate.helpers.d.ts +12 -0
- package/dist/src/tools/validate.helpers.d.ts.map +1 -1
- package/dist/src/tools/validate.helpers.js +90 -19
- package/dist/src/tools/validate.helpers.js.map +1 -1
- package/dist/src/tools/validate.js +3 -1
- package/dist/src/tools/validate.js.map +1 -1
- package/dist/src/utils/validation.d.ts +13 -0
- package/dist/src/utils/validation.d.ts.map +1 -1
- package/dist/src/utils/validation.js +27 -0
- package/dist/src/utils/validation.js.map +1 -1
- package/dist/tests/health.test.js +4 -4
- package/dist/tests/health.test.js.map +1 -1
- package/dist/tests/integration/recommend.integration.test.js +3 -0
- package/dist/tests/integration/recommend.integration.test.js.map +1 -1
- package/dist/tests/integration/setup.d.ts +4 -1
- package/dist/tests/integration/setup.d.ts.map +1 -1
- package/dist/tests/integration/setup.js +6 -1
- package/dist/tests/integration/setup.js.map +1 -1
- package/dist/tests/integration/validate.integration.test.js +4 -2
- package/dist/tests/integration/validate.integration.test.js.map +1 -1
- package/dist/tests/recommend.test.js +3 -0
- package/dist/tests/recommend.test.js.map +1 -1
- package/dist/tests/unit/skill-pack-audit.test.d.ts +8 -0
- package/dist/tests/unit/skill-pack-audit.test.d.ts.map +1 -0
- package/dist/tests/unit/skill-pack-audit.test.js +342 -0
- package/dist/tests/unit/skill-pack-audit.test.js.map +1 -0
- package/dist/tests/unit/validate-helpers.test.js +99 -2
- package/dist/tests/unit/validate-helpers.test.js.map +1 -1
- package/dist/tests/validate.test.js +4 -0
- package/dist/tests/validate.test.js.map +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for skill_outdated MCP tool
|
|
3
|
+
* @see SMI-3138: Wave 5 — Dependency intelligence outdated tool
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
import { SkillVersionRepository, SkillDependencyRepository } from '@skillsmith/core';
|
|
7
|
+
import { createTestDatabase, closeDatabase } from '../../../core/tests/helpers/database.js';
|
|
8
|
+
import { executeOutdated } from './outdated.js';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Mocks
|
|
11
|
+
// ============================================================================
|
|
12
|
+
vi.mock('./install.helpers.js', () => ({
|
|
13
|
+
loadManifest: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
vi.mock('./install.conflict-helpers.js', () => ({
|
|
16
|
+
hashContent: vi.fn((content) => {
|
|
17
|
+
// Simple deterministic mock hash
|
|
18
|
+
if (content === 'latest-content')
|
|
19
|
+
return 'aabbccdd11223344';
|
|
20
|
+
if (content === 'old-content')
|
|
21
|
+
return '11223344aabbccdd';
|
|
22
|
+
return '0000000000000000';
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
vi.mock('fs', async () => {
|
|
26
|
+
const actual = await vi.importActual('fs');
|
|
27
|
+
return {
|
|
28
|
+
...actual,
|
|
29
|
+
promises: {
|
|
30
|
+
...actual.promises,
|
|
31
|
+
readFile: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
import { loadManifest } from './install.helpers.js';
|
|
36
|
+
import { promises as fs } from 'fs';
|
|
37
|
+
const mockedLoadManifest = vi.mocked(loadManifest);
|
|
38
|
+
const mockedReadFile = vi.mocked(fs.readFile);
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Helpers
|
|
41
|
+
// ============================================================================
|
|
42
|
+
function makeContext(db) {
|
|
43
|
+
return {
|
|
44
|
+
db,
|
|
45
|
+
skillDependencyRepository: new SkillDependencyRepository(db),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function emptyManifest() {
|
|
49
|
+
return { version: '1', installedSkills: {} };
|
|
50
|
+
}
|
|
51
|
+
function manifestWithSkills(skills) {
|
|
52
|
+
const installedSkills = {};
|
|
53
|
+
for (const s of skills) {
|
|
54
|
+
installedSkills[s.name] = {
|
|
55
|
+
id: s.id,
|
|
56
|
+
name: s.name,
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
source: 'registry',
|
|
59
|
+
installPath: s.installPath,
|
|
60
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
61
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { version: '1', installedSkills };
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Tests
|
|
68
|
+
// ============================================================================
|
|
69
|
+
describe('executeOutdated', () => {
|
|
70
|
+
let db;
|
|
71
|
+
let versionRepo;
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
db = createTestDatabase();
|
|
74
|
+
versionRepo = new SkillVersionRepository(db);
|
|
75
|
+
vi.clearAllMocks();
|
|
76
|
+
});
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
closeDatabase(db);
|
|
79
|
+
});
|
|
80
|
+
it('returns empty result when manifest has no installed skills', async () => {
|
|
81
|
+
mockedLoadManifest.mockResolvedValue(emptyManifest());
|
|
82
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
83
|
+
expect(result.skills).toHaveLength(0);
|
|
84
|
+
expect(result.summary.total_installed).toBe(0);
|
|
85
|
+
expect(result.summary.outdated).toBe(0);
|
|
86
|
+
expect(result.summary.up_to_date).toBe(0);
|
|
87
|
+
expect(result.summary.unknown).toBe(0);
|
|
88
|
+
expect(result.summary.missing_deps).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
it('reports all skills as current when hashes match', async () => {
|
|
91
|
+
const skillId = 'community/test-skill';
|
|
92
|
+
mockedLoadManifest.mockResolvedValue(manifestWithSkills([
|
|
93
|
+
{ id: skillId, name: 'test-skill', installPath: '/tmp/skills/test-skill' },
|
|
94
|
+
]));
|
|
95
|
+
mockedReadFile.mockResolvedValue('latest-content');
|
|
96
|
+
// Insert a version record with the same hash as hashContent('latest-content')
|
|
97
|
+
await versionRepo.recordVersion(skillId, 'aabbccdd11223344', '1.2.0');
|
|
98
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
99
|
+
expect(result.skills).toHaveLength(1);
|
|
100
|
+
expect(result.skills[0].status).toBe('current');
|
|
101
|
+
expect(result.skills[0].installed_hash).toBe('aabbccdd');
|
|
102
|
+
expect(result.skills[0].latest_hash).toBe('aabbccdd');
|
|
103
|
+
expect(result.skills[0].semver).toBe('1.2.0');
|
|
104
|
+
expect(result.summary.up_to_date).toBe(1);
|
|
105
|
+
expect(result.summary.outdated).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
it('reports skill as outdated when hashes differ', async () => {
|
|
108
|
+
const skillId = 'community/outdated-skill';
|
|
109
|
+
mockedLoadManifest.mockResolvedValue(manifestWithSkills([
|
|
110
|
+
{ id: skillId, name: 'outdated-skill', installPath: '/tmp/skills/outdated-skill' },
|
|
111
|
+
]));
|
|
112
|
+
// Local content is old → hashContent returns '11223344aabbccdd'
|
|
113
|
+
mockedReadFile.mockResolvedValue('old-content');
|
|
114
|
+
// Registry has a different hash
|
|
115
|
+
await versionRepo.recordVersion(skillId, 'aabbccdd11223344', '2.0.0');
|
|
116
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
117
|
+
expect(result.skills).toHaveLength(1);
|
|
118
|
+
expect(result.skills[0].status).toBe('outdated');
|
|
119
|
+
expect(result.skills[0].installed_hash).toBe('11223344');
|
|
120
|
+
expect(result.skills[0].latest_hash).toBe('aabbccdd');
|
|
121
|
+
expect(result.skills[0].semver).toBe('2.0.0');
|
|
122
|
+
expect(result.summary.outdated).toBe(1);
|
|
123
|
+
expect(result.summary.up_to_date).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
it('reports unknown when no version history exists', async () => {
|
|
126
|
+
const skillId = 'community/new-skill';
|
|
127
|
+
mockedLoadManifest.mockResolvedValue(manifestWithSkills([{ id: skillId, name: 'new-skill', installPath: '/tmp/skills/new-skill' }]));
|
|
128
|
+
mockedReadFile.mockResolvedValue('latest-content');
|
|
129
|
+
// No version records in DB for this skill
|
|
130
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
131
|
+
expect(result.skills).toHaveLength(1);
|
|
132
|
+
expect(result.skills[0].status).toBe('unknown');
|
|
133
|
+
expect(result.summary.unknown).toBe(1);
|
|
134
|
+
});
|
|
135
|
+
it('includes dependency status when include_deps is true', async () => {
|
|
136
|
+
const skillId = 'community/dep-skill';
|
|
137
|
+
const depSkillId = 'community/required-skill';
|
|
138
|
+
mockedLoadManifest.mockResolvedValue(manifestWithSkills([
|
|
139
|
+
{ id: skillId, name: 'dep-skill', installPath: '/tmp/skills/dep-skill' },
|
|
140
|
+
{ id: depSkillId, name: 'required-skill', installPath: '/tmp/skills/required-skill' },
|
|
141
|
+
]));
|
|
142
|
+
mockedReadFile.mockResolvedValue('latest-content');
|
|
143
|
+
await versionRepo.recordVersion(skillId, 'aabbccdd11223344', '1.0.0');
|
|
144
|
+
await versionRepo.recordVersion(depSkillId, 'aabbccdd11223344', '1.0.0');
|
|
145
|
+
// Add a dependency: dep-skill depends on required-skill (which IS installed)
|
|
146
|
+
const depRepo = new SkillDependencyRepository(db);
|
|
147
|
+
depRepo.setDependencies(skillId, [
|
|
148
|
+
{
|
|
149
|
+
skill_id: skillId,
|
|
150
|
+
dep_type: 'skill_hard',
|
|
151
|
+
dep_target: depSkillId,
|
|
152
|
+
dep_version: '*',
|
|
153
|
+
dep_source: 'declared',
|
|
154
|
+
confidence: 1.0,
|
|
155
|
+
metadata: null,
|
|
156
|
+
},
|
|
157
|
+
], 'declared');
|
|
158
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
159
|
+
const depSkill = result.skills.find((s) => s.id === skillId);
|
|
160
|
+
expect(depSkill?.dependencies).toBeDefined();
|
|
161
|
+
expect(depSkill.dependencies.total).toBe(1);
|
|
162
|
+
expect(depSkill.dependencies.satisfied).toHaveLength(1);
|
|
163
|
+
expect(depSkill.dependencies.missing).toHaveLength(0);
|
|
164
|
+
expect(result.summary.missing_deps).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
it('omits dependency status when include_deps is false', async () => {
|
|
167
|
+
const skillId = 'community/no-dep-check';
|
|
168
|
+
mockedLoadManifest.mockResolvedValue(manifestWithSkills([
|
|
169
|
+
{ id: skillId, name: 'no-dep-check', installPath: '/tmp/skills/no-dep-check' },
|
|
170
|
+
]));
|
|
171
|
+
mockedReadFile.mockResolvedValue('latest-content');
|
|
172
|
+
await versionRepo.recordVersion(skillId, 'aabbccdd11223344', '1.0.0');
|
|
173
|
+
const result = await executeOutdated({ include_deps: false }, makeContext(db));
|
|
174
|
+
expect(result.skills).toHaveLength(1);
|
|
175
|
+
expect(result.skills[0].dependencies).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
it('counts missing deps in summary when a skill dep is not installed', async () => {
|
|
178
|
+
const skillId = 'community/lonely-skill';
|
|
179
|
+
mockedLoadManifest.mockResolvedValue(manifestWithSkills([
|
|
180
|
+
{ id: skillId, name: 'lonely-skill', installPath: '/tmp/skills/lonely-skill' },
|
|
181
|
+
]));
|
|
182
|
+
mockedReadFile.mockResolvedValue('latest-content');
|
|
183
|
+
await versionRepo.recordVersion(skillId, 'aabbccdd11223344', '1.0.0');
|
|
184
|
+
// Add a dependency on a skill that is NOT installed
|
|
185
|
+
const depRepo = new SkillDependencyRepository(db);
|
|
186
|
+
depRepo.setDependencies(skillId, [
|
|
187
|
+
{
|
|
188
|
+
skill_id: skillId,
|
|
189
|
+
dep_type: 'skill_hard',
|
|
190
|
+
dep_target: 'community/missing-skill',
|
|
191
|
+
dep_version: '*',
|
|
192
|
+
dep_source: 'declared',
|
|
193
|
+
confidence: 1.0,
|
|
194
|
+
metadata: null,
|
|
195
|
+
},
|
|
196
|
+
], 'declared');
|
|
197
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
198
|
+
const skill = result.skills[0];
|
|
199
|
+
expect(skill.dependencies.missing).toHaveLength(1);
|
|
200
|
+
expect(skill.dependencies.missing[0]).toContain('missing-skill');
|
|
201
|
+
expect(result.summary.missing_deps).toBe(1);
|
|
202
|
+
});
|
|
203
|
+
// ===========================================================================
|
|
204
|
+
// SMI-3177: Corrupt manifest entries (missing installPath)
|
|
205
|
+
// ===========================================================================
|
|
206
|
+
it('handles manifest entry with missing installPath gracefully', async () => {
|
|
207
|
+
// Simulate corrupt manifest entry (runtime JSON, not type-checked)
|
|
208
|
+
const corruptManifest = {
|
|
209
|
+
version: '1',
|
|
210
|
+
installedSkills: {
|
|
211
|
+
'test-skill': {
|
|
212
|
+
id: 'test/test-skill',
|
|
213
|
+
name: 'test-skill',
|
|
214
|
+
version: '1.0.0',
|
|
215
|
+
source: 'registry',
|
|
216
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
217
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
218
|
+
}, // Cast to bypass TS required field
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
mockedLoadManifest.mockResolvedValue(corruptManifest);
|
|
222
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
223
|
+
expect(result.skills).toHaveLength(1);
|
|
224
|
+
expect(result.skills[0].status).toBe('unknown');
|
|
225
|
+
expect(result.skills[0].installed_hash).toBe('--------');
|
|
226
|
+
expect(result.skills[0].id).toBe('test/test-skill');
|
|
227
|
+
expect(result.skills[0].dependencies).toEqual({ total: 0, satisfied: [], missing: [] });
|
|
228
|
+
expect(result.summary.unknown).toBe(1);
|
|
229
|
+
expect(result.summary.total_installed).toBe(1);
|
|
230
|
+
});
|
|
231
|
+
it('processes valid entries alongside corrupt entries', async () => {
|
|
232
|
+
const manifest = {
|
|
233
|
+
version: '1',
|
|
234
|
+
installedSkills: {
|
|
235
|
+
'good-skill': {
|
|
236
|
+
id: 'community/good-skill',
|
|
237
|
+
name: 'good-skill',
|
|
238
|
+
version: '1.0.0',
|
|
239
|
+
source: 'registry',
|
|
240
|
+
installPath: '/tmp/skills/good-skill',
|
|
241
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
242
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
243
|
+
},
|
|
244
|
+
'bad-skill': {
|
|
245
|
+
id: 'test/bad-skill',
|
|
246
|
+
name: 'bad-skill',
|
|
247
|
+
version: '1.0.0',
|
|
248
|
+
source: 'registry',
|
|
249
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
250
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
mockedLoadManifest.mockResolvedValue(manifest);
|
|
255
|
+
mockedReadFile.mockResolvedValue('latest-content');
|
|
256
|
+
await versionRepo.recordVersion('community/good-skill', 'aabbccdd11223344', '1.0.0');
|
|
257
|
+
const result = await executeOutdated({ include_deps: true }, makeContext(db));
|
|
258
|
+
expect(result.skills).toHaveLength(2);
|
|
259
|
+
expect(result.summary.total_installed).toBe(2);
|
|
260
|
+
const good = result.skills.find((s) => s.id === 'community/good-skill');
|
|
261
|
+
const bad = result.skills.find((s) => s.id === 'test/bad-skill');
|
|
262
|
+
expect(good?.status).toBe('current');
|
|
263
|
+
expect(bad?.status).toBe('unknown');
|
|
264
|
+
expect(bad?.installed_hash).toBe('--------');
|
|
265
|
+
});
|
|
266
|
+
it('handles corrupt entry with include_deps false', async () => {
|
|
267
|
+
const corruptManifest = {
|
|
268
|
+
version: '1',
|
|
269
|
+
installedSkills: {
|
|
270
|
+
broken: {
|
|
271
|
+
id: 'test/broken',
|
|
272
|
+
name: 'broken',
|
|
273
|
+
version: '1.0.0',
|
|
274
|
+
source: 'registry',
|
|
275
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
276
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
mockedLoadManifest.mockResolvedValue(corruptManifest);
|
|
281
|
+
const result = await executeOutdated({ include_deps: false }, makeContext(db));
|
|
282
|
+
expect(result.skills).toHaveLength(1);
|
|
283
|
+
expect(result.skills[0].status).toBe('unknown');
|
|
284
|
+
expect(result.skills[0].dependencies).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
//# sourceMappingURL=outdated.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outdated.test.js","sourceRoot":"","sources":["../../../src/tools/outdated.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* @see SMI-602: Integrate semantic matching with EmbeddingService
|
|
6
6
|
* @see SMI-604: Add trigger phrase overlap detection
|
|
7
7
|
* @see SMI-1837: Include local skills in recommendations (parallel search)
|
|
8
|
+
* @see SMI-2741: Split to meet 500-line standard
|
|
8
9
|
*/
|
|
9
10
|
import type { ToolContext } from '../context.js';
|
|
10
11
|
import { type RecommendInput, type RecommendResponse } from './recommend.types.js';
|
|
11
12
|
export { recommendInputSchema, recommendToolSchema, type RecommendInput, type RecommendResponse, type SkillRecommendation, } from './recommend.types.js';
|
|
13
|
+
export { formatRecommendations, mergeAndDeduplicateRecommendations } from './recommend.format.js';
|
|
12
14
|
/**
|
|
13
15
|
* Execute skill recommendation based on installed skills and context.
|
|
14
16
|
*
|
|
@@ -17,8 +19,4 @@ export { recommendInputSchema, recommendToolSchema, type RecommendInput, type Re
|
|
|
17
19
|
* - Falls back to local semantic matching if API is offline or fails
|
|
18
20
|
*/
|
|
19
21
|
export declare function executeRecommend(input: RecommendInput, context: ToolContext): Promise<RecommendResponse>;
|
|
20
|
-
/**
|
|
21
|
-
* Format recommendations for terminal display
|
|
22
|
-
*/
|
|
23
|
-
export declare function formatRecommendations(response: RecommendResponse): string;
|
|
24
22
|
//# sourceMappingURL=recommend.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recommend.d.ts","sourceRoot":"","sources":["../../../src/tools/recommend.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"recommend.d.ts","sourceRoot":"","sources":["../../../src/tools/recommend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAKhD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAGvB,MAAM,sBAAsB,CAAA;AAiB7B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EAAE,qBAAqB,EAAE,kCAAkC,EAAE,MAAM,uBAAuB,CAAA;AA0DjG;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CA0T5B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recommendation Formatting and Deduplication Utilities
|
|
3
|
+
* @module @skillsmith/mcp-server/tools/recommend.format
|
|
4
|
+
* @see SMI-2741: Split from recommend.ts to meet 500-line standard
|
|
5
|
+
*
|
|
6
|
+
* Standalone utilities extracted from executeRecommend:
|
|
7
|
+
* - mergeAndDeduplicateRecommendations: Merge API and local results, removing duplicates
|
|
8
|
+
* - formatRecommendations: Format recommendation response for terminal display
|
|
9
|
+
*/
|
|
10
|
+
import type { SkillRecommendation, RecommendResponse } from './recommend.types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Merge and deduplicate API and local skill recommendations.
|
|
13
|
+
* API results take priority over local results with the same name.
|
|
14
|
+
*
|
|
15
|
+
* @param apiResults - Results from API
|
|
16
|
+
* @param localResults - Results from local skill search
|
|
17
|
+
* @param limit - Maximum combined results
|
|
18
|
+
* @returns Merged and deduplicated recommendations
|
|
19
|
+
*/
|
|
20
|
+
export declare function mergeAndDeduplicateRecommendations(apiResults: SkillRecommendation[], localResults: SkillRecommendation[], limit: number): SkillRecommendation[];
|
|
21
|
+
/**
|
|
22
|
+
* Format recommendations for terminal display
|
|
23
|
+
*
|
|
24
|
+
* @param response - Recommendation response to format
|
|
25
|
+
* @returns Formatted string for terminal output
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatRecommendations(response: RecommendResponse): string;
|
|
28
|
+
//# sourceMappingURL=recommend.format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommend.format.d.ts","sourceRoot":"","sources":["../../../src/tools/recommend.format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAElF;;;;;;;;GAQG;AACH,wBAAgB,kCAAkC,CAChD,UAAU,EAAE,mBAAmB,EAAE,EACjC,YAAY,EAAE,mBAAmB,EAAE,EACnC,KAAK,EAAE,MAAM,GACZ,mBAAmB,EAAE,CA8CvB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAyDzE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recommendation Formatting and Deduplication Utilities
|
|
3
|
+
* @module @skillsmith/mcp-server/tools/recommend.format
|
|
4
|
+
* @see SMI-2741: Split from recommend.ts to meet 500-line standard
|
|
5
|
+
*
|
|
6
|
+
* Standalone utilities extracted from executeRecommend:
|
|
7
|
+
* - mergeAndDeduplicateRecommendations: Merge API and local results, removing duplicates
|
|
8
|
+
* - formatRecommendations: Format recommendation response for terminal display
|
|
9
|
+
*/
|
|
10
|
+
import { getTrustBadge } from '../utils/validation.js';
|
|
11
|
+
/**
|
|
12
|
+
* Merge and deduplicate API and local skill recommendations.
|
|
13
|
+
* API results take priority over local results with the same name.
|
|
14
|
+
*
|
|
15
|
+
* @param apiResults - Results from API
|
|
16
|
+
* @param localResults - Results from local skill search
|
|
17
|
+
* @param limit - Maximum combined results
|
|
18
|
+
* @returns Merged and deduplicated recommendations
|
|
19
|
+
*/
|
|
20
|
+
export function mergeAndDeduplicateRecommendations(apiResults, localResults, limit) {
|
|
21
|
+
// Build a Set of names from API results for deduplication
|
|
22
|
+
const apiSkillNames = new Set(apiResults.map((r) => r.name.toLowerCase()));
|
|
23
|
+
// Also track skill IDs (without the author prefix)
|
|
24
|
+
const apiSkillIdNames = new Set(apiResults.map((r) => r.skill_id.split('/').pop()?.toLowerCase() || ''));
|
|
25
|
+
// Filter local results to exclude duplicates
|
|
26
|
+
const uniqueLocalResults = localResults.filter((local) => {
|
|
27
|
+
const localName = local.name.toLowerCase();
|
|
28
|
+
const localIdName = local.skill_id.split('/').pop()?.toLowerCase() || '';
|
|
29
|
+
// Exclude if name matches an API result
|
|
30
|
+
if (apiSkillNames.has(localName)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
// Exclude if ID name matches an API result
|
|
34
|
+
if (apiSkillIdNames.has(localIdName)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Exclude if local name is contained in or contains an API skill name
|
|
38
|
+
for (const apiName of apiSkillNames) {
|
|
39
|
+
if (localName.includes(apiName) || apiName.includes(localName)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
// Combine API results first (higher priority), then unique local results
|
|
46
|
+
const combined = [...apiResults, ...uniqueLocalResults];
|
|
47
|
+
// Sort by quality score descending, then by similarity score
|
|
48
|
+
combined.sort((a, b) => {
|
|
49
|
+
if (b.quality_score !== a.quality_score) {
|
|
50
|
+
return b.quality_score - a.quality_score;
|
|
51
|
+
}
|
|
52
|
+
return b.similarity_score - a.similarity_score;
|
|
53
|
+
});
|
|
54
|
+
return combined.slice(0, limit);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Format recommendations for terminal display
|
|
58
|
+
*
|
|
59
|
+
* @param response - Recommendation response to format
|
|
60
|
+
* @returns Formatted string for terminal output
|
|
61
|
+
*/
|
|
62
|
+
export function formatRecommendations(response) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push('\n=== Skill Recommendations ===\n');
|
|
65
|
+
if (response.recommendations.length === 0) {
|
|
66
|
+
lines.push('No recommendations found.');
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push('Suggestions:');
|
|
69
|
+
lines.push(' - Try adding more installed skills for better matching');
|
|
70
|
+
lines.push(' - Provide a project context for more relevant results');
|
|
71
|
+
// SMI-1631: Suggest removing role filter if one was applied
|
|
72
|
+
if (response.context.role_filter) {
|
|
73
|
+
lines.push(` - Try removing the role filter (currently: ${response.context.role_filter})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
lines.push(`Found ${response.recommendations.length} recommendation(s):\n`);
|
|
78
|
+
response.recommendations.forEach((rec, index) => {
|
|
79
|
+
const trustBadge = getTrustBadge(rec.trust_tier);
|
|
80
|
+
// SMI-1631: Show roles if present
|
|
81
|
+
const rolesDisplay = rec.roles?.length ? ` [${rec.roles.join(', ')}]` : '';
|
|
82
|
+
lines.push(`${index + 1}. ${rec.name} ${trustBadge}${rolesDisplay}`);
|
|
83
|
+
lines.push(` Score: ${rec.quality_score}/100 | Relevance: ${Math.round(rec.similarity_score * 100)}%`);
|
|
84
|
+
lines.push(` ${rec.reason}`);
|
|
85
|
+
lines.push(` ID: ${rec.skill_id}`);
|
|
86
|
+
lines.push('');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
lines.push('---');
|
|
90
|
+
lines.push(`Candidates considered: ${response.candidates_considered}`);
|
|
91
|
+
if (response.overlap_filtered > 0) {
|
|
92
|
+
lines.push(`Filtered for overlap: ${response.overlap_filtered}`);
|
|
93
|
+
}
|
|
94
|
+
// SMI-1631: Show role filter stats
|
|
95
|
+
if (response.role_filtered > 0) {
|
|
96
|
+
lines.push(`Filtered for role: ${response.role_filtered}`);
|
|
97
|
+
}
|
|
98
|
+
if (response.context.role_filter) {
|
|
99
|
+
lines.push(`Role filter: ${response.context.role_filter}`);
|
|
100
|
+
}
|
|
101
|
+
if (response.context.auto_detected) {
|
|
102
|
+
lines.push(`Installed skills: ${response.context.installed_count} (auto-detected from ~/.claude/skills/)`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
lines.push(`Installed skills: ${response.context.installed_count}`);
|
|
106
|
+
}
|
|
107
|
+
lines.push(`Semantic matching: ${response.context.using_semantic_matching ? 'enabled' : 'disabled'}`);
|
|
108
|
+
lines.push(`Completed in ${response.timing.totalMs}ms`);
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=recommend.format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommend.format.js","sourceRoot":"","sources":["../../../src/tools/recommend.format.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,18 +5,23 @@
|
|
|
5
5
|
* @see SMI-602: Integrate semantic matching with EmbeddingService
|
|
6
6
|
* @see SMI-604: Add trigger phrase overlap detection
|
|
7
7
|
* @see SMI-1837: Include local skills in recommendations (parallel search)
|
|
8
|
+
* @see SMI-2741: Split to meet 500-line standard
|
|
8
9
|
*/
|
|
9
10
|
import { SkillMatcher, OverlapDetector, trackEvent } from '@skillsmith/core';
|
|
10
11
|
import { getInstalledSkills } from '../utils/installed-skills.js';
|
|
11
|
-
import { mapTrustTierFromDb
|
|
12
|
+
import { mapTrustTierFromDb } from '../utils/validation.js';
|
|
12
13
|
// Import types
|
|
13
14
|
import { recommendInputSchema, } from './recommend.types.js';
|
|
14
15
|
// Import helpers
|
|
15
16
|
import { inferRolesFromTags, loadSkillsFromDatabase, isSkillCollection, } from './recommend.helpers.js';
|
|
17
|
+
// SMI-2741: Formatting and deduplication extracted to companion file
|
|
18
|
+
import { mergeAndDeduplicateRecommendations } from './recommend.format.js';
|
|
16
19
|
// SMI-1837: Import local skill search for parallel querying
|
|
17
20
|
import { getLocalIndexer } from './LocalSkillSearch.js';
|
|
18
21
|
// Re-export only public API types (SMI-1718: trimmed internal exports)
|
|
19
22
|
export { recommendInputSchema, recommendToolSchema, } from './recommend.types.js';
|
|
23
|
+
// Re-export formatting utilities (SMI-2741)
|
|
24
|
+
export { formatRecommendations, mergeAndDeduplicateRecommendations } from './recommend.format.js';
|
|
20
25
|
/**
|
|
21
26
|
* SMI-1837: Convert a LocalSkill to SkillRecommendation format
|
|
22
27
|
* @param skill - The local skill to convert
|
|
@@ -61,50 +66,6 @@ async function searchLocalSkillsForRecommend(query, limit) {
|
|
|
61
66
|
return [];
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
|
-
/**
|
|
65
|
-
* SMI-1837: Merge and deduplicate API and local skill recommendations
|
|
66
|
-
* API results take priority over local results with the same name
|
|
67
|
-
* @param apiResults - Results from API
|
|
68
|
-
* @param localResults - Results from local skill search
|
|
69
|
-
* @param limit - Maximum combined results
|
|
70
|
-
* @returns Merged and deduplicated recommendations
|
|
71
|
-
*/
|
|
72
|
-
function mergeAndDeduplicateRecommendations(apiResults, localResults, limit) {
|
|
73
|
-
// Build a Set of names from API results for deduplication
|
|
74
|
-
const apiSkillNames = new Set(apiResults.map((r) => r.name.toLowerCase()));
|
|
75
|
-
// Also track skill IDs (without the author prefix)
|
|
76
|
-
const apiSkillIdNames = new Set(apiResults.map((r) => r.skill_id.split('/').pop()?.toLowerCase() || ''));
|
|
77
|
-
// Filter local results to exclude duplicates
|
|
78
|
-
const uniqueLocalResults = localResults.filter((local) => {
|
|
79
|
-
const localName = local.name.toLowerCase();
|
|
80
|
-
const localIdName = local.skill_id.split('/').pop()?.toLowerCase() || '';
|
|
81
|
-
// Exclude if name matches an API result
|
|
82
|
-
if (apiSkillNames.has(localName)) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
// Exclude if ID name matches an API result
|
|
86
|
-
if (apiSkillIdNames.has(localIdName)) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
// Exclude if local name is contained in or contains an API skill name
|
|
90
|
-
for (const apiName of apiSkillNames) {
|
|
91
|
-
if (localName.includes(apiName) || apiName.includes(localName)) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return true;
|
|
96
|
-
});
|
|
97
|
-
// Combine API results first (higher priority), then unique local results
|
|
98
|
-
const combined = [...apiResults, ...uniqueLocalResults];
|
|
99
|
-
// Sort by quality score descending, then by similarity score
|
|
100
|
-
combined.sort((a, b) => {
|
|
101
|
-
if (b.quality_score !== a.quality_score) {
|
|
102
|
-
return b.quality_score - a.quality_score;
|
|
103
|
-
}
|
|
104
|
-
return b.similarity_score - a.similarity_score;
|
|
105
|
-
});
|
|
106
|
-
return combined.slice(0, limit);
|
|
107
|
-
}
|
|
108
69
|
/**
|
|
109
70
|
* Execute skill recommendation based on installed skills and context.
|
|
110
71
|
*
|
|
@@ -364,56 +325,4 @@ export async function executeRecommend(input, context) {
|
|
|
364
325
|
}
|
|
365
326
|
return response;
|
|
366
327
|
}
|
|
367
|
-
/**
|
|
368
|
-
* Format recommendations for terminal display
|
|
369
|
-
*/
|
|
370
|
-
export function formatRecommendations(response) {
|
|
371
|
-
const lines = [];
|
|
372
|
-
lines.push('\n=== Skill Recommendations ===\n');
|
|
373
|
-
if (response.recommendations.length === 0) {
|
|
374
|
-
lines.push('No recommendations found.');
|
|
375
|
-
lines.push('');
|
|
376
|
-
lines.push('Suggestions:');
|
|
377
|
-
lines.push(' - Try adding more installed skills for better matching');
|
|
378
|
-
lines.push(' - Provide a project context for more relevant results');
|
|
379
|
-
// SMI-1631: Suggest removing role filter if one was applied
|
|
380
|
-
if (response.context.role_filter) {
|
|
381
|
-
lines.push(` - Try removing the role filter (currently: ${response.context.role_filter})`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
lines.push(`Found ${response.recommendations.length} recommendation(s):\n`);
|
|
386
|
-
response.recommendations.forEach((rec, index) => {
|
|
387
|
-
const trustBadge = getTrustBadge(rec.trust_tier);
|
|
388
|
-
// SMI-1631: Show roles if present
|
|
389
|
-
const rolesDisplay = rec.roles?.length ? ` [${rec.roles.join(', ')}]` : '';
|
|
390
|
-
lines.push(`${index + 1}. ${rec.name} ${trustBadge}${rolesDisplay}`);
|
|
391
|
-
lines.push(` Score: ${rec.quality_score}/100 | Relevance: ${Math.round(rec.similarity_score * 100)}%`);
|
|
392
|
-
lines.push(` ${rec.reason}`);
|
|
393
|
-
lines.push(` ID: ${rec.skill_id}`);
|
|
394
|
-
lines.push('');
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
lines.push('---');
|
|
398
|
-
lines.push(`Candidates considered: ${response.candidates_considered}`);
|
|
399
|
-
if (response.overlap_filtered > 0) {
|
|
400
|
-
lines.push(`Filtered for overlap: ${response.overlap_filtered}`);
|
|
401
|
-
}
|
|
402
|
-
// SMI-1631: Show role filter stats
|
|
403
|
-
if (response.role_filtered > 0) {
|
|
404
|
-
lines.push(`Filtered for role: ${response.role_filtered}`);
|
|
405
|
-
}
|
|
406
|
-
if (response.context.role_filter) {
|
|
407
|
-
lines.push(`Role filter: ${response.context.role_filter}`);
|
|
408
|
-
}
|
|
409
|
-
if (response.context.auto_detected) {
|
|
410
|
-
lines.push(`Installed skills: ${response.context.installed_count} (auto-detected from ~/.claude/skills/)`);
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
lines.push(`Installed skills: ${response.context.installed_count}`);
|
|
414
|
-
}
|
|
415
|
-
lines.push(`Semantic matching: ${response.context.using_semantic_matching ? 'enabled' : 'disabled'}`);
|
|
416
|
-
lines.push(`Completed in ${response.timing.totalMs}ms`);
|
|
417
|
-
return lines.join('\n');
|
|
418
|
-
}
|
|
419
328
|
//# sourceMappingURL=recommend.js.map
|