@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,185 @@
1
+ /**
2
+ * @fileoverview Contextual Skill Suggestion Engine
3
+ * @module @skillsmith/mcp-server/suggestions/suggestion-engine
4
+ * @see SMI-913: Contextual skill suggestions after first success
5
+ *
6
+ * Provides intelligent skill recommendations based on project context detection.
7
+ * Implements rate limiting, opt-out functionality, and persistent state management.
8
+ *
9
+ * @example
10
+ * import { SuggestionEngine } from './suggestion-engine.js'
11
+ * import { detectProjectContext } from '../context/project-detector.js'
12
+ *
13
+ * const engine = new SuggestionEngine()
14
+ * const context = detectProjectContext()
15
+ * const suggestions = engine.getSuggestions(context, ['installed/skill1'])
16
+ *
17
+ * if (suggestions.length > 0) {
18
+ * console.log(`Suggestion: ${suggestions[0].skillName} - ${suggestions[0].reason}`)
19
+ * engine.recordSuggestionShown()
20
+ * }
21
+ */
22
+ import type { ProjectContext } from '../context/project-detector.js';
23
+ import type { SkillSuggestion, SuggestionConfig, SuggestionState } from './types.js';
24
+ /**
25
+ * Engine for generating contextual skill suggestions
26
+ *
27
+ * Manages suggestion state, rate limiting, and skill recommendations
28
+ * based on detected project context.
29
+ *
30
+ * @example
31
+ * const engine = new SuggestionEngine({ cooldownMs: 10 * 60 * 1000 })
32
+ * const context = detectProjectContext('/path/to/project')
33
+ * const suggestions = engine.getSuggestions(context, ['installed/skill'])
34
+ */
35
+ export declare class SuggestionEngine {
36
+ private config;
37
+ private state;
38
+ private stateDir;
39
+ private stateFile;
40
+ /**
41
+ * Create a new SuggestionEngine instance
42
+ *
43
+ * @param config - Partial configuration to override defaults
44
+ *
45
+ * @example
46
+ * // Use defaults
47
+ * const engine = new SuggestionEngine()
48
+ *
49
+ * // Override cooldown
50
+ * const engine = new SuggestionEngine({ cooldownMs: 10 * 60 * 1000 })
51
+ *
52
+ * // Custom state directory (for testing)
53
+ * const engine = new SuggestionEngine({ stateDir: '/tmp/test-state' })
54
+ */
55
+ constructor(config?: Partial<SuggestionConfig>);
56
+ /**
57
+ * Load suggestion state from disk
58
+ *
59
+ * Resets daily count if it's a new day.
60
+ *
61
+ * @returns Loaded or default suggestion state
62
+ */
63
+ private loadState;
64
+ /**
65
+ * Get default suggestion state
66
+ *
67
+ * @returns Fresh default state object
68
+ */
69
+ private getDefaultState;
70
+ /**
71
+ * Save suggestion state to disk
72
+ *
73
+ * Creates the state directory if it doesn't exist.
74
+ */
75
+ private saveState;
76
+ /**
77
+ * Check if suggestions can be shown based on rate limits
78
+ *
79
+ * Checks:
80
+ * - User has not opted out
81
+ * - Daily limit not reached
82
+ * - Cooldown period has passed
83
+ *
84
+ * @returns True if suggestions are allowed
85
+ *
86
+ * @example
87
+ * if (engine.canSuggest()) {
88
+ * const suggestions = engine.getSuggestions(context)
89
+ * // Show suggestion to user
90
+ * }
91
+ */
92
+ canSuggest(): boolean;
93
+ /**
94
+ * Get skill suggestions based on project context
95
+ *
96
+ * Returns empty array if rate limited or opted out.
97
+ * Filters out already installed and dismissed skills.
98
+ * Returns at most one suggestion (the highest priority match).
99
+ *
100
+ * @param context - Detected project context from project-detector
101
+ * @param installedSkills - Array of currently installed skill IDs
102
+ * @returns Array of skill suggestions (at most one)
103
+ *
104
+ * @example
105
+ * const context = detectProjectContext()
106
+ * const suggestions = engine.getSuggestions(context, ['user/docker'])
107
+ *
108
+ * if (suggestions.length > 0) {
109
+ * console.log(`Try: ${suggestions[0].skillName}`)
110
+ * }
111
+ */
112
+ getSuggestions(context: ProjectContext, installedSkills?: string[]): SkillSuggestion[];
113
+ /**
114
+ * Get list of context attributes that are true
115
+ *
116
+ * @param context - Project context to analyze
117
+ * @returns Array of context match strings
118
+ */
119
+ private getContextMatches;
120
+ /**
121
+ * Record that a suggestion was shown to the user
122
+ *
123
+ * Updates the last suggestion time and increments daily counter.
124
+ * Should be called after displaying a suggestion.
125
+ *
126
+ * @example
127
+ * const suggestions = engine.getSuggestions(context)
128
+ * if (suggestions.length > 0) {
129
+ * displaySuggestion(suggestions[0])
130
+ * engine.recordSuggestionShown()
131
+ * }
132
+ */
133
+ recordSuggestionShown(): void;
134
+ /**
135
+ * Dismiss a skill so it won't be suggested again
136
+ *
137
+ * User can dismiss skills they're not interested in.
138
+ *
139
+ * @param skillId - Full skill identifier to dismiss
140
+ *
141
+ * @example
142
+ * // User clicks "Don't show again" on docker suggestion
143
+ * engine.dismissSkill('community/docker')
144
+ */
145
+ dismissSkill(skillId: string): void;
146
+ /**
147
+ * Permanently opt out of all suggestions
148
+ *
149
+ * User can disable all suggestions. Use optIn() to reverse.
150
+ *
151
+ * @example
152
+ * // User clicks "Never show suggestions"
153
+ * engine.optOut()
154
+ */
155
+ optOut(): void;
156
+ /**
157
+ * Opt back in to suggestions after opting out
158
+ *
159
+ * @example
160
+ * // User re-enables suggestions in settings
161
+ * engine.optIn()
162
+ */
163
+ optIn(): void;
164
+ /**
165
+ * Reset all suggestion state to defaults
166
+ *
167
+ * Clears dismissed skills, resets counters, and re-enables suggestions.
168
+ *
169
+ * @example
170
+ * // User clicks "Reset suggestions"
171
+ * engine.resetState()
172
+ */
173
+ resetState(): void;
174
+ /**
175
+ * Get a deep copy of the current suggestion state
176
+ *
177
+ * @returns Deep copy of current state (modifications don't affect engine)
178
+ *
179
+ * @example
180
+ * const state = engine.getState()
181
+ * console.log(`Suggestions today: ${state.suggestionsToday}`)
182
+ */
183
+ getState(): SuggestionState;
184
+ }
185
+ //# sourceMappingURL=suggestion-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestion-engine.d.ts","sourceRoot":"","sources":["../../../src/suggestions/suggestion-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAqFpF;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,SAAS,CAAQ;IAEzB;;;;;;;;;;;;;;OAcG;gBACS,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM;IAOlD;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAuBjB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAOjB;;;;;;;;;;;;;;;OAeG;IACH,UAAU,IAAI,OAAO;IAQrB;;;;;;;;;;;;;;;;;;OAkBG;IACH,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,GAAE,MAAM,EAAO,GAAG,eAAe,EAAE;IA4B1F;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;;;;;;;OAYG;IACH,qBAAqB,IAAI,IAAI;IAM7B;;;;;;;;;;OAUG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOnC;;;;;;;;OAQG;IACH,MAAM,IAAI,IAAI;IAKd;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;IAKb;;;;;;;;OAQG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;;;;OAQG;IACH,QAAQ,IAAI,eAAe;CAM5B"}
@@ -0,0 +1,352 @@
1
+ /**
2
+ * @fileoverview Contextual Skill Suggestion Engine
3
+ * @module @skillsmith/mcp-server/suggestions/suggestion-engine
4
+ * @see SMI-913: Contextual skill suggestions after first success
5
+ *
6
+ * Provides intelligent skill recommendations based on project context detection.
7
+ * Implements rate limiting, opt-out functionality, and persistent state management.
8
+ *
9
+ * @example
10
+ * import { SuggestionEngine } from './suggestion-engine.js'
11
+ * import { detectProjectContext } from '../context/project-detector.js'
12
+ *
13
+ * const engine = new SuggestionEngine()
14
+ * const context = detectProjectContext()
15
+ * const suggestions = engine.getSuggestions(context, ['installed/skill1'])
16
+ *
17
+ * if (suggestions.length > 0) {
18
+ * console.log(`Suggestion: ${suggestions[0].skillName} - ${suggestions[0].reason}`)
19
+ * engine.recordSuggestionShown()
20
+ * }
21
+ */
22
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
23
+ import { join } from 'path';
24
+ import { homedir } from 'os';
25
+ /** Cooldown period between suggestions in milliseconds (5 minutes) */
26
+ const SUGGESTION_COOLDOWN_MS = 5 * 60 * 1000;
27
+ /** Maximum suggestions per day per user */
28
+ const MAX_SUGGESTIONS_PER_DAY = 3;
29
+ /** Default directory for storing Skillsmith configuration and state */
30
+ const DEFAULT_SUGGESTIONS_DIR = join(homedir(), '.skillsmith');
31
+ /** Default configuration values */
32
+ const DEFAULT_CONFIG = {
33
+ cooldownMs: SUGGESTION_COOLDOWN_MS,
34
+ maxSuggestionsPerDay: MAX_SUGGESTIONS_PER_DAY,
35
+ enableOptOut: true,
36
+ };
37
+ /**
38
+ * Skill suggestion rules based on project context
39
+ *
40
+ * Rules are evaluated in order. Each rule checks for specific project
41
+ * characteristics and suggests relevant skills.
42
+ */
43
+ const SUGGESTION_RULES = [
44
+ {
45
+ condition: (ctx) => ctx.hasDocker && ctx.hasNativeModules,
46
+ skillId: 'community/docker',
47
+ skillName: 'docker',
48
+ reason: 'Your project uses native modules - Docker ensures consistent builds',
49
+ priority: 1,
50
+ },
51
+ {
52
+ condition: (ctx) => ctx.hasLinear,
53
+ skillId: 'user/linear',
54
+ skillName: 'linear',
55
+ reason: 'Automate Linear issue updates from your commits',
56
+ priority: 2,
57
+ },
58
+ {
59
+ condition: (ctx) => ctx.hasGitHub,
60
+ skillId: 'anthropic/review-pr',
61
+ skillName: 'review-pr',
62
+ reason: 'Get AI-powered code review suggestions for your PRs',
63
+ priority: 2,
64
+ },
65
+ {
66
+ condition: (ctx) => ctx.testFramework === 'jest',
67
+ skillId: 'community/jest-helper',
68
+ skillName: 'jest-helper',
69
+ reason: 'Generate and improve Jest tests automatically',
70
+ priority: 3,
71
+ },
72
+ {
73
+ condition: (ctx) => ctx.testFramework === 'vitest',
74
+ skillId: 'community/vitest-helper',
75
+ skillName: 'vitest-helper',
76
+ reason: 'Generate and improve Vitest tests automatically',
77
+ priority: 3,
78
+ },
79
+ {
80
+ condition: (ctx) => ctx.apiFramework === 'express' || ctx.apiFramework === 'nextjs',
81
+ skillId: 'community/api-docs',
82
+ skillName: 'api-docs',
83
+ reason: 'Generate OpenAPI documentation for your API endpoints',
84
+ priority: 4,
85
+ },
86
+ ];
87
+ /**
88
+ * Engine for generating contextual skill suggestions
89
+ *
90
+ * Manages suggestion state, rate limiting, and skill recommendations
91
+ * based on detected project context.
92
+ *
93
+ * @example
94
+ * const engine = new SuggestionEngine({ cooldownMs: 10 * 60 * 1000 })
95
+ * const context = detectProjectContext('/path/to/project')
96
+ * const suggestions = engine.getSuggestions(context, ['installed/skill'])
97
+ */
98
+ export class SuggestionEngine {
99
+ config;
100
+ state;
101
+ stateDir;
102
+ stateFile;
103
+ /**
104
+ * Create a new SuggestionEngine instance
105
+ *
106
+ * @param config - Partial configuration to override defaults
107
+ *
108
+ * @example
109
+ * // Use defaults
110
+ * const engine = new SuggestionEngine()
111
+ *
112
+ * // Override cooldown
113
+ * const engine = new SuggestionEngine({ cooldownMs: 10 * 60 * 1000 })
114
+ *
115
+ * // Custom state directory (for testing)
116
+ * const engine = new SuggestionEngine({ stateDir: '/tmp/test-state' })
117
+ */
118
+ constructor(config = {}) {
119
+ this.config = { ...DEFAULT_CONFIG, ...config };
120
+ this.stateDir = config.stateDir || DEFAULT_SUGGESTIONS_DIR;
121
+ this.stateFile = join(this.stateDir, 'suggestions-state.json');
122
+ this.state = this.loadState();
123
+ }
124
+ /**
125
+ * Load suggestion state from disk
126
+ *
127
+ * Resets daily count if it's a new day.
128
+ *
129
+ * @returns Loaded or default suggestion state
130
+ */
131
+ loadState() {
132
+ if (existsSync(this.stateFile)) {
133
+ try {
134
+ const data = JSON.parse(readFileSync(this.stateFile, 'utf-8'));
135
+ // Reset daily count if new day
136
+ const today = new Date().toDateString();
137
+ const lastDay = new Date(data.lastSuggestionTime || 0).toDateString();
138
+ if (today !== lastDay) {
139
+ data.suggestionsToday = 0;
140
+ }
141
+ return data;
142
+ }
143
+ catch (error) {
144
+ console.warn('[suggestion-engine] Failed to load state:', this.stateFile, error instanceof Error ? error.message : String(error));
145
+ return this.getDefaultState();
146
+ }
147
+ }
148
+ return this.getDefaultState();
149
+ }
150
+ /**
151
+ * Get default suggestion state
152
+ *
153
+ * @returns Fresh default state object
154
+ */
155
+ getDefaultState() {
156
+ return {
157
+ lastSuggestionTime: 0,
158
+ suggestionsToday: 0,
159
+ optedOut: false,
160
+ dismissedSkills: [],
161
+ };
162
+ }
163
+ /**
164
+ * Save suggestion state to disk
165
+ *
166
+ * Creates the state directory if it doesn't exist.
167
+ */
168
+ saveState() {
169
+ if (!existsSync(this.stateDir)) {
170
+ mkdirSync(this.stateDir, { recursive: true });
171
+ }
172
+ writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
173
+ }
174
+ /**
175
+ * Check if suggestions can be shown based on rate limits
176
+ *
177
+ * Checks:
178
+ * - User has not opted out
179
+ * - Daily limit not reached
180
+ * - Cooldown period has passed
181
+ *
182
+ * @returns True if suggestions are allowed
183
+ *
184
+ * @example
185
+ * if (engine.canSuggest()) {
186
+ * const suggestions = engine.getSuggestions(context)
187
+ * // Show suggestion to user
188
+ * }
189
+ */
190
+ canSuggest() {
191
+ if (this.state.optedOut)
192
+ return false;
193
+ if (this.state.suggestionsToday >= this.config.maxSuggestionsPerDay)
194
+ return false;
195
+ const timeSinceLastSuggestion = Date.now() - this.state.lastSuggestionTime;
196
+ return timeSinceLastSuggestion >= this.config.cooldownMs;
197
+ }
198
+ /**
199
+ * Get skill suggestions based on project context
200
+ *
201
+ * Returns empty array if rate limited or opted out.
202
+ * Filters out already installed and dismissed skills.
203
+ * Returns at most one suggestion (the highest priority match).
204
+ *
205
+ * @param context - Detected project context from project-detector
206
+ * @param installedSkills - Array of currently installed skill IDs
207
+ * @returns Array of skill suggestions (at most one)
208
+ *
209
+ * @example
210
+ * const context = detectProjectContext()
211
+ * const suggestions = engine.getSuggestions(context, ['user/docker'])
212
+ *
213
+ * if (suggestions.length > 0) {
214
+ * console.log(`Try: ${suggestions[0].skillName}`)
215
+ * }
216
+ */
217
+ getSuggestions(context, installedSkills = []) {
218
+ if (!this.canSuggest())
219
+ return [];
220
+ const suggestions = [];
221
+ for (const rule of SUGGESTION_RULES) {
222
+ // Skip if already installed
223
+ if (installedSkills.some((s) => s.includes(rule.skillName)))
224
+ continue;
225
+ // Skip if dismissed
226
+ if (this.state.dismissedSkills.includes(rule.skillId))
227
+ continue;
228
+ // Check condition
229
+ if (rule.condition(context)) {
230
+ suggestions.push({
231
+ skillId: rule.skillId,
232
+ skillName: rule.skillName,
233
+ reason: rule.reason,
234
+ priority: rule.priority,
235
+ contextMatch: this.getContextMatches(context),
236
+ });
237
+ }
238
+ }
239
+ // Sort by priority and return top suggestion
240
+ return suggestions.sort((a, b) => a.priority - b.priority).slice(0, 1);
241
+ }
242
+ /**
243
+ * Get list of context attributes that are true
244
+ *
245
+ * @param context - Project context to analyze
246
+ * @returns Array of context match strings
247
+ */
248
+ getContextMatches(context) {
249
+ const matches = [];
250
+ if (context.hasDocker)
251
+ matches.push('hasDocker');
252
+ if (context.hasLinear)
253
+ matches.push('hasLinear');
254
+ if (context.hasGitHub)
255
+ matches.push('hasGitHub');
256
+ if (context.testFramework)
257
+ matches.push(`testFramework:${context.testFramework}`);
258
+ if (context.apiFramework)
259
+ matches.push(`apiFramework:${context.apiFramework}`);
260
+ if (context.hasNativeModules)
261
+ matches.push('hasNativeModules');
262
+ return matches;
263
+ }
264
+ /**
265
+ * Record that a suggestion was shown to the user
266
+ *
267
+ * Updates the last suggestion time and increments daily counter.
268
+ * Should be called after displaying a suggestion.
269
+ *
270
+ * @example
271
+ * const suggestions = engine.getSuggestions(context)
272
+ * if (suggestions.length > 0) {
273
+ * displaySuggestion(suggestions[0])
274
+ * engine.recordSuggestionShown()
275
+ * }
276
+ */
277
+ recordSuggestionShown() {
278
+ this.state.lastSuggestionTime = Date.now();
279
+ this.state.suggestionsToday++;
280
+ this.saveState();
281
+ }
282
+ /**
283
+ * Dismiss a skill so it won't be suggested again
284
+ *
285
+ * User can dismiss skills they're not interested in.
286
+ *
287
+ * @param skillId - Full skill identifier to dismiss
288
+ *
289
+ * @example
290
+ * // User clicks "Don't show again" on docker suggestion
291
+ * engine.dismissSkill('community/docker')
292
+ */
293
+ dismissSkill(skillId) {
294
+ if (!this.state.dismissedSkills.includes(skillId)) {
295
+ this.state.dismissedSkills.push(skillId);
296
+ this.saveState();
297
+ }
298
+ }
299
+ /**
300
+ * Permanently opt out of all suggestions
301
+ *
302
+ * User can disable all suggestions. Use optIn() to reverse.
303
+ *
304
+ * @example
305
+ * // User clicks "Never show suggestions"
306
+ * engine.optOut()
307
+ */
308
+ optOut() {
309
+ this.state.optedOut = true;
310
+ this.saveState();
311
+ }
312
+ /**
313
+ * Opt back in to suggestions after opting out
314
+ *
315
+ * @example
316
+ * // User re-enables suggestions in settings
317
+ * engine.optIn()
318
+ */
319
+ optIn() {
320
+ this.state.optedOut = false;
321
+ this.saveState();
322
+ }
323
+ /**
324
+ * Reset all suggestion state to defaults
325
+ *
326
+ * Clears dismissed skills, resets counters, and re-enables suggestions.
327
+ *
328
+ * @example
329
+ * // User clicks "Reset suggestions"
330
+ * engine.resetState()
331
+ */
332
+ resetState() {
333
+ this.state = this.getDefaultState();
334
+ this.saveState();
335
+ }
336
+ /**
337
+ * Get a deep copy of the current suggestion state
338
+ *
339
+ * @returns Deep copy of current state (modifications don't affect engine)
340
+ *
341
+ * @example
342
+ * const state = engine.getState()
343
+ * console.log(`Suggestions today: ${state.suggestionsToday}`)
344
+ */
345
+ getState() {
346
+ return {
347
+ ...this.state,
348
+ dismissedSkills: [...this.state.dismissedSkills],
349
+ };
350
+ }
351
+ }
352
+ //# sourceMappingURL=suggestion-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestion-engine.js","sourceRoot":"","sources":["../../../src/suggestions/suggestion-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @fileoverview Type definitions for Contextual Skill Suggestions
3
+ * @module @skillsmith/mcp-server/suggestions/types
4
+ * @see SMI-913: Contextual skill suggestions after first success
5
+ *
6
+ * Defines interfaces for the suggestion engine that recommends relevant Tier 2 skills
7
+ * after a user successfully uses a Tier 1 skill, based on project context.
8
+ *
9
+ * @example
10
+ * import type { SkillSuggestion, SuggestionConfig, SuggestionState } from './types.js'
11
+ *
12
+ * const suggestion: SkillSuggestion = {
13
+ * skillId: 'community/docker',
14
+ * skillName: 'docker',
15
+ * reason: 'Your project uses native modules - Docker ensures consistent builds',
16
+ * priority: 1,
17
+ * contextMatch: ['hasDocker', 'hasNativeModules'],
18
+ * }
19
+ */
20
+ /**
21
+ * A skill suggestion generated based on project context
22
+ *
23
+ * @example
24
+ * const suggestion: SkillSuggestion = {
25
+ * skillId: 'community/docker',
26
+ * skillName: 'docker',
27
+ * reason: 'Your project uses native modules - Docker ensures consistent builds',
28
+ * priority: 1,
29
+ * contextMatch: ['hasDocker', 'hasNativeModules'],
30
+ * }
31
+ */
32
+ export interface SkillSuggestion {
33
+ /** Full skill identifier (e.g., 'community/docker', 'user/linear') */
34
+ skillId: string;
35
+ /** Short skill name for display (e.g., 'docker', 'linear') */
36
+ skillName: string;
37
+ /** Human-readable reason why this skill is being suggested */
38
+ reason: string;
39
+ /** Priority level (1 = highest priority, higher numbers = lower priority) */
40
+ priority: number;
41
+ /** Context attributes that matched this suggestion (e.g., ['hasDocker', 'testFramework:jest']) */
42
+ contextMatch: string[];
43
+ }
44
+ /**
45
+ * Configuration options for the suggestion engine
46
+ *
47
+ * @example
48
+ * const config: SuggestionConfig = {
49
+ * cooldownMs: 5 * 60 * 1000, // 5 minutes
50
+ * maxSuggestionsPerDay: 3,
51
+ * enableOptOut: true,
52
+ * }
53
+ */
54
+ export interface SuggestionConfig {
55
+ /** Minimum time between suggestions in milliseconds (default: 5 minutes) */
56
+ cooldownMs: number;
57
+ /** Maximum number of suggestions per day (default: 3) */
58
+ maxSuggestionsPerDay: number;
59
+ /** Whether to allow permanent opt-out from suggestions (default: true) */
60
+ enableOptOut: boolean;
61
+ /** Custom state directory path (default: ~/.skillsmith) - primarily for testing */
62
+ stateDir?: string;
63
+ }
64
+ /**
65
+ * Persistent state for the suggestion engine
66
+ *
67
+ * This state is persisted to disk in ~/.skillsmith/suggestions-state.json
68
+ * and tracks suggestion history, rate limiting, and user preferences.
69
+ *
70
+ * @example
71
+ * const state: SuggestionState = {
72
+ * lastSuggestionTime: Date.now(),
73
+ * suggestionsToday: 1,
74
+ * optedOut: false,
75
+ * dismissedSkills: ['community/docker'],
76
+ * }
77
+ */
78
+ export interface SuggestionState {
79
+ /** Timestamp of the last suggestion shown (for cooldown calculation) */
80
+ lastSuggestionTime: number;
81
+ /** Number of suggestions shown today (resets at midnight) */
82
+ suggestionsToday: number;
83
+ /** Whether the user has permanently opted out of suggestions */
84
+ optedOut: boolean;
85
+ /** Array of skill IDs the user has dismissed (won't be suggested again) */
86
+ dismissedSkills: string[];
87
+ }
88
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/suggestions/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAA;IAEf,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAA;IAEjB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAA;IAEd,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAA;IAEhB,kGAAkG;IAClG,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAA;IAElB,yDAAyD;IACzD,oBAAoB,EAAE,MAAM,CAAA;IAE5B,0EAA0E;IAC1E,YAAY,EAAE,OAAO,CAAA;IAErB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,kBAAkB,EAAE,MAAM,CAAA;IAE1B,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,CAAA;IAExB,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAA;IAEjB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,EAAE,CAAA;CAC1B"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview Type definitions for Contextual Skill Suggestions
3
+ * @module @skillsmith/mcp-server/suggestions/types
4
+ * @see SMI-913: Contextual skill suggestions after first success
5
+ *
6
+ * Defines interfaces for the suggestion engine that recommends relevant Tier 2 skills
7
+ * after a user successfully uses a Tier 1 skill, based on project context.
8
+ *
9
+ * @example
10
+ * import type { SkillSuggestion, SuggestionConfig, SuggestionState } from './types.js'
11
+ *
12
+ * const suggestion: SkillSuggestion = {
13
+ * skillId: 'community/docker',
14
+ * skillName: 'docker',
15
+ * reason: 'Your project uses native modules - Docker ensures consistent builds',
16
+ * priority: 1,
17
+ * contextMatch: ['hasDocker', 'hasNativeModules'],
18
+ * }
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/suggestions/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}