@skillsmith/mcp-server 0.1.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 (306) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/src/__tests__/get-skill.test.d.ts +6 -0
  3. package/dist/src/__tests__/get-skill.test.d.ts.map +1 -0
  4. package/dist/src/__tests__/get-skill.test.js +88 -0
  5. package/dist/src/__tests__/get-skill.test.js.map +1 -0
  6. package/dist/src/__tests__/middleware/errorFormatter.test.d.ts +7 -0
  7. package/dist/src/__tests__/middleware/errorFormatter.test.d.ts.map +1 -0
  8. package/dist/src/__tests__/middleware/errorFormatter.test.js +304 -0
  9. package/dist/src/__tests__/middleware/errorFormatter.test.js.map +1 -0
  10. package/dist/src/__tests__/middleware/license.test.d.ts +7 -0
  11. package/dist/src/__tests__/middleware/license.test.d.ts.map +1 -0
  12. package/dist/src/__tests__/middleware/license.test.js +500 -0
  13. package/dist/src/__tests__/middleware/license.test.js.map +1 -0
  14. package/dist/src/__tests__/search.test.d.ts +6 -0
  15. package/dist/src/__tests__/search.test.d.ts.map +1 -0
  16. package/dist/src/__tests__/search.test.js +86 -0
  17. package/dist/src/__tests__/search.test.js.map +1 -0
  18. package/dist/src/__tests__/test-utils.d.ts +19 -0
  19. package/dist/src/__tests__/test-utils.d.ts.map +1 -0
  20. package/dist/src/__tests__/test-utils.js +87 -0
  21. package/dist/src/__tests__/test-utils.js.map +1 -0
  22. package/dist/src/context/index.d.ts +19 -0
  23. package/dist/src/context/index.d.ts.map +1 -0
  24. package/dist/src/context/index.js +25 -0
  25. package/dist/src/context/index.js.map +1 -0
  26. package/dist/src/context/project-detector.d.ts +145 -0
  27. package/dist/src/context/project-detector.d.ts.map +1 -0
  28. package/dist/src/context/project-detector.js +321 -0
  29. package/dist/src/context/project-detector.js.map +1 -0
  30. package/dist/src/context.d.ts +100 -0
  31. package/dist/src/context.d.ts.map +1 -0
  32. package/dist/src/context.js +157 -0
  33. package/dist/src/context.js.map +1 -0
  34. package/dist/src/core-shim.d.ts +7 -0
  35. package/dist/src/core-shim.d.ts.map +1 -0
  36. package/dist/src/core-shim.js +9 -0
  37. package/dist/src/core-shim.js.map +1 -0
  38. package/dist/src/health/healthCheck.d.ts +88 -0
  39. package/dist/src/health/healthCheck.d.ts.map +1 -0
  40. package/dist/src/health/healthCheck.js +117 -0
  41. package/dist/src/health/healthCheck.js.map +1 -0
  42. package/dist/src/health/index.d.ts +21 -0
  43. package/dist/src/health/index.d.ts.map +1 -0
  44. package/dist/src/health/index.js +21 -0
  45. package/dist/src/health/index.js.map +1 -0
  46. package/dist/src/health/readinessCheck.d.ts +139 -0
  47. package/dist/src/health/readinessCheck.d.ts.map +1 -0
  48. package/dist/src/health/readinessCheck.js +266 -0
  49. package/dist/src/health/readinessCheck.js.map +1 -0
  50. package/dist/src/index.d.ts +8 -0
  51. package/dist/src/index.d.ts.map +1 -0
  52. package/dist/src/index.js +178 -0
  53. package/dist/src/index.js.map +1 -0
  54. package/dist/src/index.test.d.ts +2 -0
  55. package/dist/src/index.test.d.ts.map +1 -0
  56. package/dist/src/index.test.js +43 -0
  57. package/dist/src/index.test.js.map +1 -0
  58. package/dist/src/logger.d.ts +26 -0
  59. package/dist/src/logger.d.ts.map +1 -0
  60. package/dist/src/logger.js +179 -0
  61. package/dist/src/logger.js.map +1 -0
  62. package/dist/src/middleware/__tests__/csp.test.d.ts +2 -0
  63. package/dist/src/middleware/__tests__/csp.test.d.ts.map +1 -0
  64. package/dist/src/middleware/__tests__/csp.test.js +389 -0
  65. package/dist/src/middleware/__tests__/csp.test.js.map +1 -0
  66. package/dist/src/middleware/csp.d.ts +87 -0
  67. package/dist/src/middleware/csp.d.ts.map +1 -0
  68. package/dist/src/middleware/csp.js +273 -0
  69. package/dist/src/middleware/csp.js.map +1 -0
  70. package/dist/src/middleware/degradation.d.ts +99 -0
  71. package/dist/src/middleware/degradation.d.ts.map +1 -0
  72. package/dist/src/middleware/degradation.js +315 -0
  73. package/dist/src/middleware/degradation.js.map +1 -0
  74. package/dist/src/middleware/errorFormatter.d.ts +119 -0
  75. package/dist/src/middleware/errorFormatter.d.ts.map +1 -0
  76. package/dist/src/middleware/errorFormatter.js +294 -0
  77. package/dist/src/middleware/errorFormatter.js.map +1 -0
  78. package/dist/src/middleware/index.d.ts +10 -0
  79. package/dist/src/middleware/index.d.ts.map +1 -0
  80. package/dist/src/middleware/index.js +14 -0
  81. package/dist/src/middleware/index.js.map +1 -0
  82. package/dist/src/middleware/license.d.ts +161 -0
  83. package/dist/src/middleware/license.d.ts.map +1 -0
  84. package/dist/src/middleware/license.js +281 -0
  85. package/dist/src/middleware/license.js.map +1 -0
  86. package/dist/src/middleware/toolFeatureMapping.d.ts +36 -0
  87. package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -0
  88. package/dist/src/middleware/toolFeatureMapping.js +90 -0
  89. package/dist/src/middleware/toolFeatureMapping.js.map +1 -0
  90. package/dist/src/onboarding/first-run.d.ts +64 -0
  91. package/dist/src/onboarding/first-run.d.ts.map +1 -0
  92. package/dist/src/onboarding/first-run.js +77 -0
  93. package/dist/src/onboarding/first-run.js.map +1 -0
  94. package/dist/src/onboarding/index.d.ts +7 -0
  95. package/dist/src/onboarding/index.d.ts.map +1 -0
  96. package/dist/src/onboarding/index.js +7 -0
  97. package/dist/src/onboarding/index.js.map +1 -0
  98. package/dist/src/suggestions/index.d.ts +21 -0
  99. package/dist/src/suggestions/index.d.ts.map +1 -0
  100. package/dist/src/suggestions/index.js +20 -0
  101. package/dist/src/suggestions/index.js.map +1 -0
  102. package/dist/src/suggestions/suggestion-engine.d.ts +185 -0
  103. package/dist/src/suggestions/suggestion-engine.d.ts.map +1 -0
  104. package/dist/src/suggestions/suggestion-engine.js +352 -0
  105. package/dist/src/suggestions/suggestion-engine.js.map +1 -0
  106. package/dist/src/suggestions/types.d.ts +88 -0
  107. package/dist/src/suggestions/types.d.ts.map +1 -0
  108. package/dist/src/suggestions/types.js +21 -0
  109. package/dist/src/suggestions/types.js.map +1 -0
  110. package/dist/src/tools/analyze.d.ts +151 -0
  111. package/dist/src/tools/analyze.d.ts.map +1 -0
  112. package/dist/src/tools/analyze.js +205 -0
  113. package/dist/src/tools/analyze.js.map +1 -0
  114. package/dist/src/tools/compare.d.ts +149 -0
  115. package/dist/src/tools/compare.d.ts.map +1 -0
  116. package/dist/src/tools/compare.js +464 -0
  117. package/dist/src/tools/compare.js.map +1 -0
  118. package/dist/src/tools/get-skill.d.ts +116 -0
  119. package/dist/src/tools/get-skill.d.ts.map +1 -0
  120. package/dist/src/tools/get-skill.js +224 -0
  121. package/dist/src/tools/get-skill.js.map +1 -0
  122. package/dist/src/tools/index.d.ts +20 -0
  123. package/dist/src/tools/index.d.ts.map +1 -0
  124. package/dist/src/tools/index.js +20 -0
  125. package/dist/src/tools/index.js.map +1 -0
  126. package/dist/src/tools/install.d.ts +122 -0
  127. package/dist/src/tools/install.d.ts.map +1 -0
  128. package/dist/src/tools/install.js +314 -0
  129. package/dist/src/tools/install.js.map +1 -0
  130. package/dist/src/tools/recommend.d.ts +171 -0
  131. package/dist/src/tools/recommend.d.ts.map +1 -0
  132. package/dist/src/tools/recommend.js +325 -0
  133. package/dist/src/tools/recommend.js.map +1 -0
  134. package/dist/src/tools/search.d.ts +121 -0
  135. package/dist/src/tools/search.d.ts.map +1 -0
  136. package/dist/src/tools/search.js +249 -0
  137. package/dist/src/tools/search.js.map +1 -0
  138. package/dist/src/tools/suggest.d.ts +181 -0
  139. package/dist/src/tools/suggest.d.ts.map +1 -0
  140. package/dist/src/tools/suggest.js +342 -0
  141. package/dist/src/tools/suggest.js.map +1 -0
  142. package/dist/src/tools/uninstall.d.ts +123 -0
  143. package/dist/src/tools/uninstall.d.ts.map +1 -0
  144. package/dist/src/tools/uninstall.js +250 -0
  145. package/dist/src/tools/uninstall.js.map +1 -0
  146. package/dist/src/tools/validate.d.ts +122 -0
  147. package/dist/src/tools/validate.d.ts.map +1 -0
  148. package/dist/src/tools/validate.js +497 -0
  149. package/dist/src/tools/validate.js.map +1 -0
  150. package/dist/src/utils/installed-skills.d.ts +101 -0
  151. package/dist/src/utils/installed-skills.d.ts.map +1 -0
  152. package/dist/src/utils/installed-skills.js +220 -0
  153. package/dist/src/utils/installed-skills.js.map +1 -0
  154. package/dist/src/utils/validation.d.ts +76 -0
  155. package/dist/src/utils/validation.d.ts.map +1 -0
  156. package/dist/src/utils/validation.js +153 -0
  157. package/dist/src/utils/validation.js.map +1 -0
  158. package/dist/src/webhooks/index.d.ts +8 -0
  159. package/dist/src/webhooks/index.d.ts.map +1 -0
  160. package/dist/src/webhooks/index.js +9 -0
  161. package/dist/src/webhooks/index.js.map +1 -0
  162. package/dist/src/webhooks/webhook-endpoint.d.ts +149 -0
  163. package/dist/src/webhooks/webhook-endpoint.d.ts.map +1 -0
  164. package/dist/src/webhooks/webhook-endpoint.js +339 -0
  165. package/dist/src/webhooks/webhook-endpoint.js.map +1 -0
  166. package/dist/tests/compare.test.d.ts +6 -0
  167. package/dist/tests/compare.test.d.ts.map +1 -0
  168. package/dist/tests/compare.test.js +225 -0
  169. package/dist/tests/compare.test.js.map +1 -0
  170. package/dist/tests/context/project-detector.test.d.ts +6 -0
  171. package/dist/tests/context/project-detector.test.d.ts.map +1 -0
  172. package/dist/tests/context/project-detector.test.js +719 -0
  173. package/dist/tests/context/project-detector.test.js.map +1 -0
  174. package/dist/tests/e2e/compare.e2e.test.d.ts +10 -0
  175. package/dist/tests/e2e/compare.e2e.test.d.ts.map +1 -0
  176. package/dist/tests/e2e/compare.e2e.test.js +286 -0
  177. package/dist/tests/e2e/compare.e2e.test.js.map +1 -0
  178. package/dist/tests/e2e/install-flow.e2e.test.d.ts +10 -0
  179. package/dist/tests/e2e/install-flow.e2e.test.d.ts.map +1 -0
  180. package/dist/tests/e2e/install-flow.e2e.test.js +209 -0
  181. package/dist/tests/e2e/install-flow.e2e.test.js.map +1 -0
  182. package/dist/tests/e2e/recommend.e2e.test.d.ts +12 -0
  183. package/dist/tests/e2e/recommend.e2e.test.d.ts.map +1 -0
  184. package/dist/tests/e2e/recommend.e2e.test.js +347 -0
  185. package/dist/tests/e2e/recommend.e2e.test.js.map +1 -0
  186. package/dist/tests/e2e/skill-flow.e2e.test.d.ts +10 -0
  187. package/dist/tests/e2e/skill-flow.e2e.test.d.ts.map +1 -0
  188. package/dist/tests/e2e/skill-flow.e2e.test.js +280 -0
  189. package/dist/tests/e2e/skill-flow.e2e.test.js.map +1 -0
  190. package/dist/tests/e2e/suggest.e2e.test.d.ts +13 -0
  191. package/dist/tests/e2e/suggest.e2e.test.d.ts.map +1 -0
  192. package/dist/tests/e2e/suggest.e2e.test.js +347 -0
  193. package/dist/tests/e2e/suggest.e2e.test.js.map +1 -0
  194. package/dist/tests/e2e/utils/baseline-collector.d.ts +107 -0
  195. package/dist/tests/e2e/utils/baseline-collector.d.ts.map +1 -0
  196. package/dist/tests/e2e/utils/baseline-collector.js +211 -0
  197. package/dist/tests/e2e/utils/baseline-collector.js.map +1 -0
  198. package/dist/tests/e2e/utils/hardcoded-detector.d.ts +46 -0
  199. package/dist/tests/e2e/utils/hardcoded-detector.d.ts.map +1 -0
  200. package/dist/tests/e2e/utils/hardcoded-detector.js +255 -0
  201. package/dist/tests/e2e/utils/hardcoded-detector.js.map +1 -0
  202. package/dist/tests/e2e/utils/index.d.ts +7 -0
  203. package/dist/tests/e2e/utils/index.d.ts.map +1 -0
  204. package/dist/tests/e2e/utils/index.js +7 -0
  205. package/dist/tests/e2e/utils/index.js.map +1 -0
  206. package/dist/tests/e2e/utils/linear-reporter.d.ts +60 -0
  207. package/dist/tests/e2e/utils/linear-reporter.d.ts.map +1 -0
  208. package/dist/tests/e2e/utils/linear-reporter.js +232 -0
  209. package/dist/tests/e2e/utils/linear-reporter.js.map +1 -0
  210. package/dist/tests/health.test.d.ts +9 -0
  211. package/dist/tests/health.test.d.ts.map +1 -0
  212. package/dist/tests/health.test.js +308 -0
  213. package/dist/tests/health.test.js.map +1 -0
  214. package/dist/tests/integration/analyze.integration.test.d.ts +2 -0
  215. package/dist/tests/integration/analyze.integration.test.d.ts.map +1 -0
  216. package/dist/tests/integration/analyze.integration.test.js +244 -0
  217. package/dist/tests/integration/analyze.integration.test.js.map +1 -0
  218. package/dist/tests/integration/compare.integration.test.d.ts +2 -0
  219. package/dist/tests/integration/compare.integration.test.d.ts.map +1 -0
  220. package/dist/tests/integration/compare.integration.test.js +120 -0
  221. package/dist/tests/integration/compare.integration.test.js.map +1 -0
  222. package/dist/tests/integration/fixtures/test-skills.d.ts +62 -0
  223. package/dist/tests/integration/fixtures/test-skills.d.ts.map +1 -0
  224. package/dist/tests/integration/fixtures/test-skills.js +644 -0
  225. package/dist/tests/integration/fixtures/test-skills.js.map +1 -0
  226. package/dist/tests/integration/get-skill.integration.test.d.ts +6 -0
  227. package/dist/tests/integration/get-skill.integration.test.d.ts.map +1 -0
  228. package/dist/tests/integration/get-skill.integration.test.js +203 -0
  229. package/dist/tests/integration/get-skill.integration.test.js.map +1 -0
  230. package/dist/tests/integration/github-api.integration.test.d.ts +14 -0
  231. package/dist/tests/integration/github-api.integration.test.d.ts.map +1 -0
  232. package/dist/tests/integration/github-api.integration.test.js +190 -0
  233. package/dist/tests/integration/github-api.integration.test.js.map +1 -0
  234. package/dist/tests/integration/install.integration.test.d.ts +6 -0
  235. package/dist/tests/integration/install.integration.test.d.ts.map +1 -0
  236. package/dist/tests/integration/install.integration.test.js +282 -0
  237. package/dist/tests/integration/install.integration.test.js.map +1 -0
  238. package/dist/tests/integration/recommend.integration.test.d.ts +2 -0
  239. package/dist/tests/integration/recommend.integration.test.d.ts.map +1 -0
  240. package/dist/tests/integration/recommend.integration.test.js +215 -0
  241. package/dist/tests/integration/recommend.integration.test.js.map +1 -0
  242. package/dist/tests/integration/search.integration.test.d.ts +6 -0
  243. package/dist/tests/integration/search.integration.test.d.ts.map +1 -0
  244. package/dist/tests/integration/search.integration.test.js +229 -0
  245. package/dist/tests/integration/search.integration.test.js.map +1 -0
  246. package/dist/tests/integration/setup.d.ts +71 -0
  247. package/dist/tests/integration/setup.d.ts.map +1 -0
  248. package/dist/tests/integration/setup.js +124 -0
  249. package/dist/tests/integration/setup.js.map +1 -0
  250. package/dist/tests/integration/uninstall.integration.test.d.ts +6 -0
  251. package/dist/tests/integration/uninstall.integration.test.d.ts.map +1 -0
  252. package/dist/tests/integration/uninstall.integration.test.js +296 -0
  253. package/dist/tests/integration/uninstall.integration.test.js.map +1 -0
  254. package/dist/tests/integration/validate.integration.test.d.ts +2 -0
  255. package/dist/tests/integration/validate.integration.test.d.ts.map +1 -0
  256. package/dist/tests/integration/validate.integration.test.js +181 -0
  257. package/dist/tests/integration/validate.integration.test.js.map +1 -0
  258. package/dist/tests/onboarding/first-run.test.d.ts +7 -0
  259. package/dist/tests/onboarding/first-run.test.d.ts.map +1 -0
  260. package/dist/tests/onboarding/first-run.test.js +258 -0
  261. package/dist/tests/onboarding/first-run.test.js.map +1 -0
  262. package/dist/tests/performance/search-performance.test.d.ts +10 -0
  263. package/dist/tests/performance/search-performance.test.d.ts.map +1 -0
  264. package/dist/tests/performance/search-performance.test.js +218 -0
  265. package/dist/tests/performance/search-performance.test.js.map +1 -0
  266. package/dist/tests/recommend.test.d.ts +6 -0
  267. package/dist/tests/recommend.test.d.ts.map +1 -0
  268. package/dist/tests/recommend.test.js +208 -0
  269. package/dist/tests/recommend.test.js.map +1 -0
  270. package/dist/tests/suggestions/suggestion-engine.test.d.ts +6 -0
  271. package/dist/tests/suggestions/suggestion-engine.test.d.ts.map +1 -0
  272. package/dist/tests/suggestions/suggestion-engine.test.js +448 -0
  273. package/dist/tests/suggestions/suggestion-engine.test.js.map +1 -0
  274. package/dist/tests/test-utils.d.ts +74 -0
  275. package/dist/tests/test-utils.d.ts.map +1 -0
  276. package/dist/tests/test-utils.js +98 -0
  277. package/dist/tests/test-utils.js.map +1 -0
  278. package/dist/tests/tools.test.d.ts +5 -0
  279. package/dist/tests/tools.test.d.ts.map +1 -0
  280. package/dist/tests/tools.test.js +138 -0
  281. package/dist/tests/tools.test.js.map +1 -0
  282. package/dist/tests/unit/installed-skills.test.d.ts +6 -0
  283. package/dist/tests/unit/installed-skills.test.d.ts.map +1 -0
  284. package/dist/tests/unit/installed-skills.test.js +285 -0
  285. package/dist/tests/unit/installed-skills.test.js.map +1 -0
  286. package/dist/tests/unit/logger.test.d.ts +6 -0
  287. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  288. package/dist/tests/unit/logger.test.js +281 -0
  289. package/dist/tests/unit/logger.test.js.map +1 -0
  290. package/dist/tests/validate.test.d.ts +5 -0
  291. package/dist/tests/validate.test.d.ts.map +1 -0
  292. package/dist/tests/validate.test.js +303 -0
  293. package/dist/tests/validate.test.js.map +1 -0
  294. package/dist/tests/webhooks/proxy-trust.security.test.d.ts +8 -0
  295. package/dist/tests/webhooks/proxy-trust.security.test.d.ts.map +1 -0
  296. package/dist/tests/webhooks/proxy-trust.security.test.js +145 -0
  297. package/dist/tests/webhooks/proxy-trust.security.test.js.map +1 -0
  298. package/dist/tests/webhooks/rate-limiter.security.test.d.ts +8 -0
  299. package/dist/tests/webhooks/rate-limiter.security.test.d.ts.map +1 -0
  300. package/dist/tests/webhooks/rate-limiter.security.test.js +122 -0
  301. package/dist/tests/webhooks/rate-limiter.security.test.js.map +1 -0
  302. package/dist/vitest.config.d.ts +6 -0
  303. package/dist/vitest.config.d.ts.map +1 -0
  304. package/dist/vitest.config.js +13 -0
  305. package/dist/vitest.config.js.map +1 -0
  306. package/package.json +63 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * E2E Test: MCP skill_recommend tool
3
+ *
4
+ * Tests the recommend tool against real skill databases
5
+ * in a clean Codespace environment.
6
+ *
7
+ * FOCUS: Detect hardcoded values that caused SMI-902/904 issues
8
+ *
9
+ * User Journey: Get skill recommendations based on context
10
+ */
11
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
12
+ import { existsSync, rmSync, mkdirSync, writeFileSync } from 'fs';
13
+ import { join } from 'path';
14
+ import { tmpdir, homedir } from 'os';
15
+ import { createDatabase, initializeSchema, SkillRepository, } from '@skillsmith/core';
16
+ import { createToolContext } from '../../src/context.js';
17
+ import { executeRecommend } from '../../src/tools/recommend.js';
18
+ import { scanForHardcoded } from './utils/hardcoded-detector.js';
19
+ import { recordTiming, measureAsync } from './utils/baseline-collector.js';
20
+ // Test configuration
21
+ const TEST_DIR = join(tmpdir(), 'skillsmith-e2e-recommend');
22
+ const TEST_DB_PATH = join(TEST_DIR, 'recommend-test.db');
23
+ const TEST_SKILLS_DIR = join(TEST_DIR, '.claude', 'skills');
24
+ // Diverse seed data for testing recommendations
25
+ const SEED_SKILLS = [
26
+ {
27
+ id: 'anthropic/commit',
28
+ name: 'commit',
29
+ description: 'Generate semantic commit messages following conventional commits',
30
+ author: 'anthropic',
31
+ repoUrl: 'https://github.com/anthropics/claude-code/tree/main/skills/commit',
32
+ qualityScore: 0.95,
33
+ trustTier: 'verified',
34
+ tags: ['development', 'git', 'commit', 'conventional-commits'],
35
+ },
36
+ {
37
+ id: 'community/jest-helper',
38
+ name: 'jest-helper',
39
+ description: 'Generate Jest test cases for React components',
40
+ author: 'community',
41
+ repoUrl: 'https://github.com/skillsmith-community/jest-helper',
42
+ qualityScore: 0.87,
43
+ trustTier: 'community',
44
+ tags: ['testing', 'jest', 'react', 'unit-tests'],
45
+ },
46
+ {
47
+ id: 'community/vitest-helper',
48
+ name: 'vitest-helper',
49
+ description: 'Generate Vitest test cases with modern ESM support',
50
+ author: 'community',
51
+ repoUrl: 'https://github.com/skillsmith-community/vitest-helper',
52
+ qualityScore: 0.85,
53
+ trustTier: 'community',
54
+ tags: ['testing', 'vitest', 'esm', 'typescript'],
55
+ },
56
+ {
57
+ id: 'community/docker-compose',
58
+ name: 'docker-compose',
59
+ description: 'Generate and manage Docker Compose configurations',
60
+ author: 'community',
61
+ repoUrl: 'https://github.com/skillsmith-community/docker-compose',
62
+ qualityScore: 0.84,
63
+ trustTier: 'community',
64
+ tags: ['devops', 'docker', 'containers'],
65
+ },
66
+ {
67
+ id: 'community/eslint-config',
68
+ name: 'eslint-config',
69
+ description: 'Generate and configure ESLint rules for JavaScript projects',
70
+ author: 'community',
71
+ repoUrl: 'https://github.com/skillsmith-community/eslint-config',
72
+ qualityScore: 0.82,
73
+ trustTier: 'community',
74
+ tags: ['linting', 'eslint', 'code-quality'],
75
+ },
76
+ {
77
+ id: 'experimental/ai-debug',
78
+ name: 'ai-debug',
79
+ description: 'AI-powered debugging assistant',
80
+ author: 'experimental',
81
+ repoUrl: 'https://github.com/skillsmith-community/ai-debug',
82
+ qualityScore: 0.65,
83
+ trustTier: 'experimental',
84
+ tags: ['debugging', 'ai', 'experimental'],
85
+ },
86
+ ];
87
+ /**
88
+ * Create mock installed skill for auto-detection tests
89
+ */
90
+ function createMockInstalledSkill(skillName) {
91
+ const skillDir = join(TEST_SKILLS_DIR, skillName);
92
+ mkdirSync(skillDir, { recursive: true });
93
+ writeFileSync(join(skillDir, 'SKILL.md'), `---
94
+ name: ${skillName}
95
+ version: 1.0.0
96
+ description: Mock skill for testing
97
+ ---
98
+ # ${skillName}
99
+ Mock skill content.
100
+ `);
101
+ }
102
+ /**
103
+ * Scan response for hardcoded values
104
+ */
105
+ function scanResponseForHardcoded(response, command) {
106
+ const responseStr = JSON.stringify(response, null, 2);
107
+ return scanForHardcoded(responseStr, command, 'database', 'recommend response');
108
+ }
109
+ describe('E2E: skill_recommend tool', () => {
110
+ let db;
111
+ let context;
112
+ beforeAll(() => {
113
+ // Create test environment
114
+ if (existsSync(TEST_DIR)) {
115
+ rmSync(TEST_DIR, { recursive: true, force: true });
116
+ }
117
+ mkdirSync(TEST_DIR, { recursive: true });
118
+ mkdirSync(TEST_SKILLS_DIR, { recursive: true });
119
+ // Initialize database with seed data
120
+ db = createDatabase(TEST_DB_PATH);
121
+ initializeSchema(db);
122
+ const skillRepo = new SkillRepository(db);
123
+ for (const skill of SEED_SKILLS) {
124
+ skillRepo.create(skill);
125
+ }
126
+ // Create context pointing to test database
127
+ context = createToolContext({ dbPath: TEST_DB_PATH });
128
+ });
129
+ afterAll(() => {
130
+ db?.close();
131
+ if (existsSync(TEST_DIR)) {
132
+ rmSync(TEST_DIR, { recursive: true, force: true });
133
+ }
134
+ });
135
+ describe('Basic Recommendations', () => {
136
+ it('should return recommendations without installed skills', async () => {
137
+ const input = {
138
+ installed_skills: [],
139
+ limit: 5,
140
+ };
141
+ const { result } = await measureAsync('recommend:empty', 'skill_recommend (no installed)', () => executeRecommend(input, context));
142
+ expect(result.recommendations.length).toBeGreaterThan(0);
143
+ expect(result.timing.totalMs).toBeGreaterThanOrEqual(0);
144
+ // Check for hardcoded values in response
145
+ const issues = scanResponseForHardcoded(result, 'skill_recommend');
146
+ expect(issues.filter((i) => i.severity === 'error')).toHaveLength(0);
147
+ });
148
+ it('should return recommendations with installed skills', async () => {
149
+ const input = {
150
+ installed_skills: ['anthropic/commit'],
151
+ limit: 5,
152
+ };
153
+ const result = await executeRecommend(input, context);
154
+ recordTiming('recommend:with_installed', 'skill_recommend (installed)', result.timing.totalMs);
155
+ // May have 0 recommendations if seed data overlaps with installed skill
156
+ expect(result.recommendations.length).toBeGreaterThanOrEqual(0);
157
+ expect(result.context.installed_count).toBe(1);
158
+ // If there are recommendations, should not include already installed skill
159
+ const recommendedIds = result.recommendations.map((r) => r.skill_id);
160
+ expect(recommendedIds).not.toContain('anthropic/commit');
161
+ // Check for hardcoded values
162
+ const issues = scanResponseForHardcoded(result, 'skill_recommend');
163
+ expect(issues.filter((i) => i.severity === 'error')).toHaveLength(0);
164
+ });
165
+ it('should use project context for recommendations', async () => {
166
+ const input = {
167
+ installed_skills: [],
168
+ project_context: 'React TypeScript frontend with Jest testing',
169
+ limit: 5,
170
+ };
171
+ const result = await executeRecommend(input, context);
172
+ recordTiming('recommend:context', 'skill_recommend (context)', result.timing.totalMs);
173
+ expect(result.context.has_project_context).toBe(true);
174
+ // Check for hardcoded values
175
+ const issues = scanResponseForHardcoded(result, 'skill_recommend');
176
+ expect(issues.filter((i) => i.severity === 'error')).toHaveLength(0);
177
+ });
178
+ });
179
+ describe('Auto-Detection of Installed Skills', () => {
180
+ it('should auto-detect installed skills from ~/.claude/skills/', async () => {
181
+ // Create mock installed skill
182
+ createMockInstalledSkill('test-installed-skill');
183
+ // Override HOME for this test
184
+ const originalHome = process.env.HOME;
185
+ process.env.HOME = TEST_DIR;
186
+ try {
187
+ const input = {
188
+ installed_skills: [], // Empty to trigger auto-detection
189
+ limit: 5,
190
+ };
191
+ const result = await executeRecommend(input, context);
192
+ // Should have auto-detected
193
+ expect(result.context.auto_detected).toBe(true);
194
+ // Check for hardcoded values
195
+ const issues = scanResponseForHardcoded(result, 'skill_recommend (auto-detect)');
196
+ expect(issues.filter((i) => i.severity === 'error')).toHaveLength(0);
197
+ }
198
+ finally {
199
+ process.env.HOME = originalHome;
200
+ }
201
+ });
202
+ });
203
+ describe('Overlap Detection', () => {
204
+ it('should filter overlapping skills when enabled', async () => {
205
+ const input = {
206
+ installed_skills: ['community/jest-helper'],
207
+ detect_overlap: true,
208
+ limit: 10,
209
+ };
210
+ const result = await executeRecommend(input, context);
211
+ recordTiming('recommend:overlap', 'skill_recommend (overlap)', result.timing.totalMs);
212
+ // vitest-helper might be filtered as similar to jest-helper
213
+ // Just verify overlap_filtered is tracked
214
+ expect(typeof result.overlap_filtered).toBe('number');
215
+ // Check for hardcoded values
216
+ const issues = scanResponseForHardcoded(result, 'skill_recommend');
217
+ expect(issues.filter((i) => i.severity === 'error')).toHaveLength(0);
218
+ });
219
+ it('should not filter when detect_overlap is false', async () => {
220
+ const input = {
221
+ installed_skills: ['community/jest-helper'],
222
+ detect_overlap: false,
223
+ limit: 10,
224
+ };
225
+ const result = await executeRecommend(input, context);
226
+ // Should have more candidates when overlap not filtered
227
+ expect(result.candidates_considered).toBeGreaterThan(0);
228
+ });
229
+ });
230
+ describe('Response Data Quality', () => {
231
+ it('should return valid recommendation structure', async () => {
232
+ const input = {
233
+ installed_skills: [],
234
+ limit: 3,
235
+ };
236
+ const result = await executeRecommend(input, context);
237
+ for (const rec of result.recommendations) {
238
+ // Validate structure
239
+ expect(rec.skill_id).toBeDefined();
240
+ expect(rec.name).toBeDefined();
241
+ expect(rec.reason).toBeDefined();
242
+ expect(rec.similarity_score).toBeGreaterThanOrEqual(0);
243
+ expect(rec.similarity_score).toBeLessThanOrEqual(1);
244
+ expect(['verified', 'community', 'standard', 'unverified']).toContain(rec.trust_tier);
245
+ expect(rec.quality_score).toBeGreaterThanOrEqual(0);
246
+ expect(rec.quality_score).toBeLessThanOrEqual(100);
247
+ }
248
+ });
249
+ it('should not contain hardcoded skill IDs in recommendations', async () => {
250
+ const input = {
251
+ installed_skills: [],
252
+ limit: 10,
253
+ };
254
+ const result = await executeRecommend(input, context);
255
+ // All skill IDs should come from database, not hardcoded
256
+ const dbSkillIds = SEED_SKILLS.map((s) => s.id);
257
+ for (const rec of result.recommendations) {
258
+ expect(dbSkillIds).toContain(rec.skill_id);
259
+ }
260
+ });
261
+ });
262
+ describe('Hardcoded Value Detection (SMI-902/904 Regression)', () => {
263
+ it('should not expose user paths in response', async () => {
264
+ const input = {
265
+ installed_skills: ['anthropic/commit'],
266
+ project_context: 'Testing for hardcoded paths',
267
+ limit: 5,
268
+ };
269
+ const result = await executeRecommend(input, context);
270
+ const responseStr = JSON.stringify(result);
271
+ // Should not contain user-specific paths
272
+ expect(responseStr).not.toMatch(/\/Users\/[a-zA-Z0-9_-]+\//);
273
+ expect(responseStr).not.toMatch(/\/home\/[a-zA-Z0-9_-]+\//);
274
+ expect(responseStr).not.toContain(homedir());
275
+ });
276
+ it('should not expose localhost URLs in response', async () => {
277
+ const input = {
278
+ installed_skills: [],
279
+ limit: 5,
280
+ };
281
+ const result = await executeRecommend(input, context);
282
+ const responseStr = JSON.stringify(result);
283
+ // Should not contain localhost
284
+ expect(responseStr).not.toMatch(/localhost:\d+/);
285
+ expect(responseStr).not.toMatch(/127\.0\.0\.1:\d+/);
286
+ });
287
+ it('should not expose API keys in response', async () => {
288
+ const input = {
289
+ installed_skills: [],
290
+ project_context: 'Should not leak credentials',
291
+ limit: 5,
292
+ };
293
+ const result = await executeRecommend(input, context);
294
+ const responseStr = JSON.stringify(result);
295
+ // Should not contain API keys
296
+ expect(responseStr).not.toMatch(/ghp_[a-zA-Z0-9]{36}/);
297
+ expect(responseStr).not.toMatch(/sk-[a-zA-Z0-9]{32,}/);
298
+ expect(responseStr).not.toMatch(/lin_api_[a-zA-Z0-9]+/);
299
+ });
300
+ it('should have dynamic recommendations not hardcoded static list', async () => {
301
+ // Run twice with different contexts
302
+ const result1 = await executeRecommend({ installed_skills: [], project_context: 'Docker DevOps', limit: 5 }, context);
303
+ const result2 = await executeRecommend({ installed_skills: [], project_context: 'React testing', limit: 5 }, context);
304
+ // Results should potentially differ based on context
305
+ // (This test helps detect if recommendations are completely static)
306
+ const ids1 = result1.recommendations.map((r) => r.skill_id).sort();
307
+ const ids2 = result2.recommendations.map((r) => r.skill_id).sort();
308
+ // If results are EXACTLY the same regardless of context, may indicate hardcoding
309
+ // Note: This is a soft check - might be same due to small dataset
310
+ if (ids1.join(',') === ids2.join(',')) {
311
+ console.warn('Warning: Recommendations identical for different contexts - may indicate hardcoding');
312
+ }
313
+ });
314
+ });
315
+ describe('Performance Baselines', () => {
316
+ it('should complete recommendation in reasonable time', async () => {
317
+ const { durationMs } = await measureAsync('recommend:baseline', 'skill_recommend baseline', () => executeRecommend({
318
+ installed_skills: ['anthropic/commit'],
319
+ project_context: 'Full-stack TypeScript project',
320
+ limit: 10,
321
+ }, context));
322
+ // Baseline: should complete within 2 seconds for seed data
323
+ expect(durationMs).toBeLessThan(2000);
324
+ console.log(`Recommend baseline: ${durationMs}ms`);
325
+ });
326
+ it('should handle multiple rapid requests', async () => {
327
+ const requests = Array(5)
328
+ .fill(null)
329
+ .map((_, i) => executeRecommend({ installed_skills: [], project_context: `Context ${i}`, limit: 3 }, context));
330
+ const results = await Promise.all(requests);
331
+ expect(results).toHaveLength(5);
332
+ for (const result of results) {
333
+ expect(result.recommendations.length).toBeGreaterThanOrEqual(0);
334
+ }
335
+ });
336
+ });
337
+ describe('Error Handling', () => {
338
+ it('should handle invalid limit gracefully', async () => {
339
+ // Zod should enforce limits
340
+ await expect(executeRecommend({ installed_skills: [], limit: 100 }, context)).rejects.toThrow();
341
+ });
342
+ it('should handle invalid min_similarity gracefully', async () => {
343
+ await expect(executeRecommend({ installed_skills: [], min_similarity: 2.0 }, context)).rejects.toThrow();
344
+ });
345
+ });
346
+ });
347
+ //# sourceMappingURL=recommend.e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend.e2e.test.js","sourceRoot":"","sources":["../../../tests/e2e/recommend.e2e.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * E2E Test: Search → Get → Install Flow
3
+ *
4
+ * Tests the complete user journey from discovering a skill to installing it.
5
+ * Uses seed data to ensure consistent, reproducible tests.
6
+ *
7
+ * @see SMI-796: E2E test: Search → Get → Install flow
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=skill-flow.e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-flow.e2e.test.d.ts","sourceRoot":"","sources":["../../../tests/e2e/skill-flow.e2e.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,280 @@
1
+ /**
2
+ * E2E Test: Search → Get → Install Flow
3
+ *
4
+ * Tests the complete user journey from discovering a skill to installing it.
5
+ * Uses seed data to ensure consistent, reproducible tests.
6
+ *
7
+ * @see SMI-796: E2E test: Search → Get → Install flow
8
+ */
9
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
10
+ import { existsSync, rmSync, mkdirSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { tmpdir } from 'os';
13
+ import { createDatabase, initializeSchema, SkillRepository, } from '@skillsmith/core';
14
+ import { createToolContext } from '../../src/context.js';
15
+ import { executeSearch } from '../../src/tools/search.js';
16
+ import { executeGetSkill } from '../../src/tools/get-skill.js';
17
+ const SEED_SKILLS = [
18
+ {
19
+ id: 'anthropic/commit',
20
+ name: 'commit',
21
+ description: 'Generate semantic commit messages following conventional commits',
22
+ author: 'anthropic',
23
+ repoUrl: 'https://github.com/anthropics/claude-code/tree/main/skills/commit',
24
+ qualityScore: 0.95,
25
+ trustTier: 'verified',
26
+ tags: ['development', 'git', 'commit', 'conventional-commits'],
27
+ },
28
+ {
29
+ id: 'community/jest-helper',
30
+ name: 'jest-helper',
31
+ description: 'Generate Jest test cases for React components',
32
+ author: 'community',
33
+ repoUrl: 'https://github.com/skillsmith-community/jest-helper',
34
+ qualityScore: 0.87,
35
+ trustTier: 'community',
36
+ tags: ['testing', 'jest', 'react', 'unit-tests'],
37
+ },
38
+ {
39
+ id: 'community/vitest-helper',
40
+ name: 'vitest-helper',
41
+ description: 'Generate Vitest test cases with modern ESM support',
42
+ author: 'community',
43
+ repoUrl: 'https://github.com/skillsmith-community/vitest-helper',
44
+ qualityScore: 0.85,
45
+ trustTier: 'community',
46
+ tags: ['testing', 'vitest', 'esm', 'typescript'],
47
+ },
48
+ {
49
+ id: 'community/docker-compose',
50
+ name: 'docker-compose',
51
+ description: 'Generate and manage Docker Compose configurations',
52
+ author: 'community',
53
+ repoUrl: 'https://github.com/skillsmith-community/docker-compose',
54
+ qualityScore: 0.84,
55
+ trustTier: 'community',
56
+ tags: ['devops', 'docker', 'containers'],
57
+ },
58
+ ];
59
+ describe('E2E: Skill Discovery Flow', () => {
60
+ let db;
61
+ let context;
62
+ let testDbPath;
63
+ beforeAll(() => {
64
+ // Create isolated test database
65
+ const testDir = join(tmpdir(), 'skillsmith-e2e-test');
66
+ if (!existsSync(testDir)) {
67
+ mkdirSync(testDir, { recursive: true });
68
+ }
69
+ testDbPath = join(testDir, `e2e-test-${Date.now()}.db`);
70
+ // Initialize database with seed data
71
+ db = createDatabase(testDbPath);
72
+ initializeSchema(db);
73
+ const skillRepository = new SkillRepository(db);
74
+ for (const skill of SEED_SKILLS) {
75
+ skillRepository.create(skill);
76
+ }
77
+ // Create context
78
+ context = createToolContext({ dbPath: testDbPath });
79
+ });
80
+ afterAll(() => {
81
+ db?.close();
82
+ if (testDbPath && existsSync(testDbPath)) {
83
+ rmSync(testDbPath, { force: true });
84
+ }
85
+ });
86
+ describe('Step 1: Search for skills', () => {
87
+ it('should find commit skill when searching for "commit"', async () => {
88
+ const input = { query: 'commit' };
89
+ const result = await executeSearch(input, context);
90
+ expect(result.results.length).toBeGreaterThan(0);
91
+ expect(result.results.some((s) => s.id === 'anthropic/commit')).toBe(true);
92
+ });
93
+ it('should find testing skills when searching for "test"', async () => {
94
+ const input = { query: 'test' };
95
+ const result = await executeSearch(input, context);
96
+ expect(result.results.length).toBeGreaterThanOrEqual(2);
97
+ expect(result.results.some((s) => s.id === 'community/jest-helper')).toBe(true);
98
+ expect(result.results.some((s) => s.id === 'community/vitest-helper')).toBe(true);
99
+ });
100
+ it('should filter by category', async () => {
101
+ // Note: Category filtering may not reduce results if skills already match the category
102
+ const input = { query: 'docker' };
103
+ const result = await executeSearch(input, context);
104
+ expect(result.results.length).toBeGreaterThan(0);
105
+ expect(result.results.some((s) => s.id === 'community/docker-compose')).toBe(true);
106
+ // Verify docker-compose has expected category from extractCategoryFromTags
107
+ const dockerSkill = result.results.find((s) => s.id === 'community/docker-compose');
108
+ expect(dockerSkill?.category).toBe('devops');
109
+ });
110
+ it('should filter by trust tier', async () => {
111
+ const input = { query: 'commit', trust_tier: 'verified' };
112
+ const result = await executeSearch(input, context);
113
+ expect(result.results.every((s) => s.trustTier === 'verified')).toBe(true);
114
+ });
115
+ it('should filter by minimum score', async () => {
116
+ const input = { query: 'test', min_score: 85 };
117
+ const result = await executeSearch(input, context);
118
+ expect(result.results.every((s) => s.score >= 85)).toBe(true);
119
+ });
120
+ });
121
+ describe('Step 2: Get skill details', () => {
122
+ it('should retrieve full details for a skill', async () => {
123
+ const input = { id: 'anthropic/commit' };
124
+ const result = await executeGetSkill(input, context);
125
+ expect(result.skill.id).toBe('anthropic/commit');
126
+ expect(result.skill.name).toBe('commit');
127
+ expect(result.skill.author).toBe('anthropic');
128
+ expect(result.skill.trustTier).toBe('verified');
129
+ expect(result.skill.score).toBe(95);
130
+ expect(result.installCommand).toContain('claude skill add anthropic/commit');
131
+ });
132
+ it('should include timing information', async () => {
133
+ const input = { id: 'community/jest-helper' };
134
+ const result = await executeGetSkill(input, context);
135
+ expect(result.timing).toBeDefined();
136
+ expect(result.timing.totalMs).toBeGreaterThanOrEqual(0);
137
+ });
138
+ it('should throw for non-existent skill', async () => {
139
+ const input = { id: 'nonexistent/skill' };
140
+ await expect(executeGetSkill(input, context)).rejects.toThrow();
141
+ });
142
+ });
143
+ describe('Step 3: Search → Get flow integration', () => {
144
+ it('should allow getting details of any search result', async () => {
145
+ // Step 1: Search
146
+ const searchInput = { query: 'jest' };
147
+ const searchResult = await executeSearch(searchInput, context);
148
+ expect(searchResult.results.length).toBeGreaterThan(0);
149
+ // Step 2: Get details of first result
150
+ const firstResult = searchResult.results[0];
151
+ const getInput = { id: firstResult.id };
152
+ const skillDetails = await executeGetSkill(getInput, context);
153
+ // Verify consistency
154
+ expect(skillDetails.skill.id).toBe(firstResult.id);
155
+ expect(skillDetails.skill.name).toBe(firstResult.name);
156
+ expect(skillDetails.skill.trustTier).toBe(firstResult.trustTier);
157
+ });
158
+ it('should provide install command for all skills', async () => {
159
+ // Search for all testing skills
160
+ const searchInput = { query: 'test' };
161
+ const searchResult = await executeSearch(searchInput, context);
162
+ // Get details and verify install commands
163
+ for (const result of searchResult.results) {
164
+ const details = await executeGetSkill({ id: result.id }, context);
165
+ expect(details.installCommand).toMatch(/^claude skill add [a-z0-9-]+\/[a-z0-9-]+$/i);
166
+ }
167
+ });
168
+ });
169
+ describe('Quality and Performance', () => {
170
+ it('should complete search in under 100ms', async () => {
171
+ const input = { query: 'commit' };
172
+ const result = await executeSearch(input, context);
173
+ expect(result.timing.totalMs).toBeLessThan(100);
174
+ });
175
+ it('should complete get-skill in under 50ms', async () => {
176
+ const input = { id: 'anthropic/commit' };
177
+ const result = await executeGetSkill(input, context);
178
+ expect(result.timing.totalMs).toBeLessThan(50);
179
+ });
180
+ it('should handle rapid successive searches', async () => {
181
+ const queries = ['commit', 'test', 'docker', 'jest', 'vitest'];
182
+ const results = await Promise.all(queries.map((query) => executeSearch({ query }, context)));
183
+ expect(results.every((r) => r.results.length >= 0)).toBe(true);
184
+ });
185
+ });
186
+ describe('Error Handling', () => {
187
+ it('should reject empty search queries', async () => {
188
+ const input = { query: '' };
189
+ await expect(executeSearch(input, context)).rejects.toThrow();
190
+ });
191
+ it('should reject single-character search queries', async () => {
192
+ const input = { query: 'a' };
193
+ await expect(executeSearch(input, context)).rejects.toThrow();
194
+ });
195
+ it('should reject invalid skill ID formats', async () => {
196
+ const input = { id: 'invalid-format' };
197
+ await expect(executeGetSkill(input, context)).rejects.toThrow();
198
+ });
199
+ it('should reject out-of-range min_score', async () => {
200
+ const input = { query: 'test', min_score: 150 };
201
+ await expect(executeSearch(input, context)).rejects.toThrow();
202
+ });
203
+ });
204
+ });
205
+ describe('E2E: Data Quality Validation', () => {
206
+ let db;
207
+ let context;
208
+ let testDbPath;
209
+ beforeAll(() => {
210
+ const testDir = join(tmpdir(), 'skillsmith-e2e-quality');
211
+ if (!existsSync(testDir)) {
212
+ mkdirSync(testDir, { recursive: true });
213
+ }
214
+ testDbPath = join(testDir, `quality-test-${Date.now()}.db`);
215
+ db = createDatabase(testDbPath);
216
+ initializeSchema(db);
217
+ const skillRepository = new SkillRepository(db);
218
+ for (const skill of SEED_SKILLS) {
219
+ skillRepository.create(skill);
220
+ }
221
+ context = createToolContext({ dbPath: testDbPath });
222
+ });
223
+ afterAll(() => {
224
+ db?.close();
225
+ if (testDbPath && existsSync(testDbPath)) {
226
+ rmSync(testDbPath, { force: true });
227
+ }
228
+ });
229
+ describe('SMI-795: Import Quality Validation', () => {
230
+ it('should have valid trust tiers for all skills', async () => {
231
+ // Since empty/short queries throw, get skills directly
232
+ for (const skill of SEED_SKILLS) {
233
+ const details = await executeGetSkill({ id: skill.id }, context);
234
+ expect(['verified', 'community', 'standard', 'unverified', 'experimental']).toContain(details.skill.trustTier);
235
+ }
236
+ });
237
+ it('should have quality scores between 0 and 100', async () => {
238
+ for (const skill of SEED_SKILLS) {
239
+ const details = await executeGetSkill({ id: skill.id }, context);
240
+ const score = details.skill.score ?? 0;
241
+ expect(score).toBeGreaterThanOrEqual(0);
242
+ expect(score).toBeLessThanOrEqual(100);
243
+ }
244
+ });
245
+ it('should have non-empty descriptions', async () => {
246
+ for (const skill of SEED_SKILLS) {
247
+ const details = await executeGetSkill({ id: skill.id }, context);
248
+ const description = details.skill.description ?? '';
249
+ expect(description.length).toBeGreaterThan(0);
250
+ }
251
+ });
252
+ it('should have valid category mappings', async () => {
253
+ for (const skill of SEED_SKILLS) {
254
+ const details = await executeGetSkill({ id: skill.id }, context);
255
+ const category = details.skill.category ?? 'other';
256
+ expect([
257
+ 'development',
258
+ 'testing',
259
+ 'documentation',
260
+ 'devops',
261
+ 'database',
262
+ 'security',
263
+ 'productivity',
264
+ 'integration',
265
+ 'ai-ml',
266
+ 'other',
267
+ ]).toContain(category);
268
+ }
269
+ });
270
+ it('should have valid author names', async () => {
271
+ for (const skill of SEED_SKILLS) {
272
+ const details = await executeGetSkill({ id: skill.id }, context);
273
+ const author = details.skill.author ?? '';
274
+ expect(author).not.toBe('unknown');
275
+ expect(author.length).toBeGreaterThan(0);
276
+ }
277
+ });
278
+ });
279
+ });
280
+ //# sourceMappingURL=skill-flow.e2e.test.js.map