@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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/src/__tests__/get-skill.test.d.ts +6 -0
- package/dist/src/__tests__/get-skill.test.d.ts.map +1 -0
- package/dist/src/__tests__/get-skill.test.js +88 -0
- package/dist/src/__tests__/get-skill.test.js.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.d.ts +7 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.js +304 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.js.map +1 -0
- package/dist/src/__tests__/middleware/license.test.d.ts +7 -0
- package/dist/src/__tests__/middleware/license.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/license.test.js +500 -0
- package/dist/src/__tests__/middleware/license.test.js.map +1 -0
- package/dist/src/__tests__/search.test.d.ts +6 -0
- package/dist/src/__tests__/search.test.d.ts.map +1 -0
- package/dist/src/__tests__/search.test.js +86 -0
- package/dist/src/__tests__/search.test.js.map +1 -0
- package/dist/src/__tests__/test-utils.d.ts +19 -0
- package/dist/src/__tests__/test-utils.d.ts.map +1 -0
- package/dist/src/__tests__/test-utils.js +87 -0
- package/dist/src/__tests__/test-utils.js.map +1 -0
- package/dist/src/context/index.d.ts +19 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +25 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/context/project-detector.d.ts +145 -0
- package/dist/src/context/project-detector.d.ts.map +1 -0
- package/dist/src/context/project-detector.js +321 -0
- package/dist/src/context/project-detector.js.map +1 -0
- package/dist/src/context.d.ts +100 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +157 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/core-shim.d.ts +7 -0
- package/dist/src/core-shim.d.ts.map +1 -0
- package/dist/src/core-shim.js +9 -0
- package/dist/src/core-shim.js.map +1 -0
- package/dist/src/health/healthCheck.d.ts +88 -0
- package/dist/src/health/healthCheck.d.ts.map +1 -0
- package/dist/src/health/healthCheck.js +117 -0
- package/dist/src/health/healthCheck.js.map +1 -0
- package/dist/src/health/index.d.ts +21 -0
- package/dist/src/health/index.d.ts.map +1 -0
- package/dist/src/health/index.js +21 -0
- package/dist/src/health/index.js.map +1 -0
- package/dist/src/health/readinessCheck.d.ts +139 -0
- package/dist/src/health/readinessCheck.d.ts.map +1 -0
- package/dist/src/health/readinessCheck.js +266 -0
- package/dist/src/health/readinessCheck.js.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +178 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/index.test.d.ts +2 -0
- package/dist/src/index.test.d.ts.map +1 -0
- package/dist/src/index.test.js +43 -0
- package/dist/src/index.test.js.map +1 -0
- package/dist/src/logger.d.ts +26 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +179 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/middleware/__tests__/csp.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/csp.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/csp.test.js +389 -0
- package/dist/src/middleware/__tests__/csp.test.js.map +1 -0
- package/dist/src/middleware/csp.d.ts +87 -0
- package/dist/src/middleware/csp.d.ts.map +1 -0
- package/dist/src/middleware/csp.js +273 -0
- package/dist/src/middleware/csp.js.map +1 -0
- package/dist/src/middleware/degradation.d.ts +99 -0
- package/dist/src/middleware/degradation.d.ts.map +1 -0
- package/dist/src/middleware/degradation.js +315 -0
- package/dist/src/middleware/degradation.js.map +1 -0
- package/dist/src/middleware/errorFormatter.d.ts +119 -0
- package/dist/src/middleware/errorFormatter.d.ts.map +1 -0
- package/dist/src/middleware/errorFormatter.js +294 -0
- package/dist/src/middleware/errorFormatter.js.map +1 -0
- package/dist/src/middleware/index.d.ts +10 -0
- package/dist/src/middleware/index.d.ts.map +1 -0
- package/dist/src/middleware/index.js +14 -0
- package/dist/src/middleware/index.js.map +1 -0
- package/dist/src/middleware/license.d.ts +161 -0
- package/dist/src/middleware/license.d.ts.map +1 -0
- package/dist/src/middleware/license.js +281 -0
- package/dist/src/middleware/license.js.map +1 -0
- package/dist/src/middleware/toolFeatureMapping.d.ts +36 -0
- package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -0
- package/dist/src/middleware/toolFeatureMapping.js +90 -0
- package/dist/src/middleware/toolFeatureMapping.js.map +1 -0
- package/dist/src/onboarding/first-run.d.ts +64 -0
- package/dist/src/onboarding/first-run.d.ts.map +1 -0
- package/dist/src/onboarding/first-run.js +77 -0
- package/dist/src/onboarding/first-run.js.map +1 -0
- package/dist/src/onboarding/index.d.ts +7 -0
- package/dist/src/onboarding/index.d.ts.map +1 -0
- package/dist/src/onboarding/index.js +7 -0
- package/dist/src/onboarding/index.js.map +1 -0
- package/dist/src/suggestions/index.d.ts +21 -0
- package/dist/src/suggestions/index.d.ts.map +1 -0
- package/dist/src/suggestions/index.js +20 -0
- package/dist/src/suggestions/index.js.map +1 -0
- package/dist/src/suggestions/suggestion-engine.d.ts +185 -0
- package/dist/src/suggestions/suggestion-engine.d.ts.map +1 -0
- package/dist/src/suggestions/suggestion-engine.js +352 -0
- package/dist/src/suggestions/suggestion-engine.js.map +1 -0
- package/dist/src/suggestions/types.d.ts +88 -0
- package/dist/src/suggestions/types.d.ts.map +1 -0
- package/dist/src/suggestions/types.js +21 -0
- package/dist/src/suggestions/types.js.map +1 -0
- package/dist/src/tools/analyze.d.ts +151 -0
- package/dist/src/tools/analyze.d.ts.map +1 -0
- package/dist/src/tools/analyze.js +205 -0
- package/dist/src/tools/analyze.js.map +1 -0
- package/dist/src/tools/compare.d.ts +149 -0
- package/dist/src/tools/compare.d.ts.map +1 -0
- package/dist/src/tools/compare.js +464 -0
- package/dist/src/tools/compare.js.map +1 -0
- package/dist/src/tools/get-skill.d.ts +116 -0
- package/dist/src/tools/get-skill.d.ts.map +1 -0
- package/dist/src/tools/get-skill.js +224 -0
- package/dist/src/tools/get-skill.js.map +1 -0
- package/dist/src/tools/index.d.ts +20 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +20 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/install.d.ts +122 -0
- package/dist/src/tools/install.d.ts.map +1 -0
- package/dist/src/tools/install.js +314 -0
- package/dist/src/tools/install.js.map +1 -0
- package/dist/src/tools/recommend.d.ts +171 -0
- package/dist/src/tools/recommend.d.ts.map +1 -0
- package/dist/src/tools/recommend.js +325 -0
- package/dist/src/tools/recommend.js.map +1 -0
- package/dist/src/tools/search.d.ts +121 -0
- package/dist/src/tools/search.d.ts.map +1 -0
- package/dist/src/tools/search.js +249 -0
- package/dist/src/tools/search.js.map +1 -0
- package/dist/src/tools/suggest.d.ts +181 -0
- package/dist/src/tools/suggest.d.ts.map +1 -0
- package/dist/src/tools/suggest.js +342 -0
- package/dist/src/tools/suggest.js.map +1 -0
- package/dist/src/tools/uninstall.d.ts +123 -0
- package/dist/src/tools/uninstall.d.ts.map +1 -0
- package/dist/src/tools/uninstall.js +250 -0
- package/dist/src/tools/uninstall.js.map +1 -0
- package/dist/src/tools/validate.d.ts +122 -0
- package/dist/src/tools/validate.d.ts.map +1 -0
- package/dist/src/tools/validate.js +497 -0
- package/dist/src/tools/validate.js.map +1 -0
- package/dist/src/utils/installed-skills.d.ts +101 -0
- package/dist/src/utils/installed-skills.d.ts.map +1 -0
- package/dist/src/utils/installed-skills.js +220 -0
- package/dist/src/utils/installed-skills.js.map +1 -0
- package/dist/src/utils/validation.d.ts +76 -0
- package/dist/src/utils/validation.d.ts.map +1 -0
- package/dist/src/utils/validation.js +153 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/dist/src/webhooks/index.d.ts +8 -0
- package/dist/src/webhooks/index.d.ts.map +1 -0
- package/dist/src/webhooks/index.js +9 -0
- package/dist/src/webhooks/index.js.map +1 -0
- package/dist/src/webhooks/webhook-endpoint.d.ts +149 -0
- package/dist/src/webhooks/webhook-endpoint.d.ts.map +1 -0
- package/dist/src/webhooks/webhook-endpoint.js +339 -0
- package/dist/src/webhooks/webhook-endpoint.js.map +1 -0
- package/dist/tests/compare.test.d.ts +6 -0
- package/dist/tests/compare.test.d.ts.map +1 -0
- package/dist/tests/compare.test.js +225 -0
- package/dist/tests/compare.test.js.map +1 -0
- package/dist/tests/context/project-detector.test.d.ts +6 -0
- package/dist/tests/context/project-detector.test.d.ts.map +1 -0
- package/dist/tests/context/project-detector.test.js +719 -0
- package/dist/tests/context/project-detector.test.js.map +1 -0
- package/dist/tests/e2e/compare.e2e.test.d.ts +10 -0
- package/dist/tests/e2e/compare.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/compare.e2e.test.js +286 -0
- package/dist/tests/e2e/compare.e2e.test.js.map +1 -0
- package/dist/tests/e2e/install-flow.e2e.test.d.ts +10 -0
- package/dist/tests/e2e/install-flow.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/install-flow.e2e.test.js +209 -0
- package/dist/tests/e2e/install-flow.e2e.test.js.map +1 -0
- package/dist/tests/e2e/recommend.e2e.test.d.ts +12 -0
- package/dist/tests/e2e/recommend.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/recommend.e2e.test.js +347 -0
- package/dist/tests/e2e/recommend.e2e.test.js.map +1 -0
- package/dist/tests/e2e/skill-flow.e2e.test.d.ts +10 -0
- package/dist/tests/e2e/skill-flow.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/skill-flow.e2e.test.js +280 -0
- package/dist/tests/e2e/skill-flow.e2e.test.js.map +1 -0
- package/dist/tests/e2e/suggest.e2e.test.d.ts +13 -0
- package/dist/tests/e2e/suggest.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/suggest.e2e.test.js +347 -0
- package/dist/tests/e2e/suggest.e2e.test.js.map +1 -0
- package/dist/tests/e2e/utils/baseline-collector.d.ts +107 -0
- package/dist/tests/e2e/utils/baseline-collector.d.ts.map +1 -0
- package/dist/tests/e2e/utils/baseline-collector.js +211 -0
- package/dist/tests/e2e/utils/baseline-collector.js.map +1 -0
- package/dist/tests/e2e/utils/hardcoded-detector.d.ts +46 -0
- package/dist/tests/e2e/utils/hardcoded-detector.d.ts.map +1 -0
- package/dist/tests/e2e/utils/hardcoded-detector.js +255 -0
- package/dist/tests/e2e/utils/hardcoded-detector.js.map +1 -0
- package/dist/tests/e2e/utils/index.d.ts +7 -0
- package/dist/tests/e2e/utils/index.d.ts.map +1 -0
- package/dist/tests/e2e/utils/index.js +7 -0
- package/dist/tests/e2e/utils/index.js.map +1 -0
- package/dist/tests/e2e/utils/linear-reporter.d.ts +60 -0
- package/dist/tests/e2e/utils/linear-reporter.d.ts.map +1 -0
- package/dist/tests/e2e/utils/linear-reporter.js +232 -0
- package/dist/tests/e2e/utils/linear-reporter.js.map +1 -0
- package/dist/tests/health.test.d.ts +9 -0
- package/dist/tests/health.test.d.ts.map +1 -0
- package/dist/tests/health.test.js +308 -0
- package/dist/tests/health.test.js.map +1 -0
- package/dist/tests/integration/analyze.integration.test.d.ts +2 -0
- package/dist/tests/integration/analyze.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/analyze.integration.test.js +244 -0
- package/dist/tests/integration/analyze.integration.test.js.map +1 -0
- package/dist/tests/integration/compare.integration.test.d.ts +2 -0
- package/dist/tests/integration/compare.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/compare.integration.test.js +120 -0
- package/dist/tests/integration/compare.integration.test.js.map +1 -0
- package/dist/tests/integration/fixtures/test-skills.d.ts +62 -0
- package/dist/tests/integration/fixtures/test-skills.d.ts.map +1 -0
- package/dist/tests/integration/fixtures/test-skills.js +644 -0
- package/dist/tests/integration/fixtures/test-skills.js.map +1 -0
- package/dist/tests/integration/get-skill.integration.test.d.ts +6 -0
- package/dist/tests/integration/get-skill.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/get-skill.integration.test.js +203 -0
- package/dist/tests/integration/get-skill.integration.test.js.map +1 -0
- package/dist/tests/integration/github-api.integration.test.d.ts +14 -0
- package/dist/tests/integration/github-api.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/github-api.integration.test.js +190 -0
- package/dist/tests/integration/github-api.integration.test.js.map +1 -0
- package/dist/tests/integration/install.integration.test.d.ts +6 -0
- package/dist/tests/integration/install.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/install.integration.test.js +282 -0
- package/dist/tests/integration/install.integration.test.js.map +1 -0
- package/dist/tests/integration/recommend.integration.test.d.ts +2 -0
- package/dist/tests/integration/recommend.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/recommend.integration.test.js +215 -0
- package/dist/tests/integration/recommend.integration.test.js.map +1 -0
- package/dist/tests/integration/search.integration.test.d.ts +6 -0
- package/dist/tests/integration/search.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/search.integration.test.js +229 -0
- package/dist/tests/integration/search.integration.test.js.map +1 -0
- package/dist/tests/integration/setup.d.ts +71 -0
- package/dist/tests/integration/setup.d.ts.map +1 -0
- package/dist/tests/integration/setup.js +124 -0
- package/dist/tests/integration/setup.js.map +1 -0
- package/dist/tests/integration/uninstall.integration.test.d.ts +6 -0
- package/dist/tests/integration/uninstall.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/uninstall.integration.test.js +296 -0
- package/dist/tests/integration/uninstall.integration.test.js.map +1 -0
- package/dist/tests/integration/validate.integration.test.d.ts +2 -0
- package/dist/tests/integration/validate.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/validate.integration.test.js +181 -0
- package/dist/tests/integration/validate.integration.test.js.map +1 -0
- package/dist/tests/onboarding/first-run.test.d.ts +7 -0
- package/dist/tests/onboarding/first-run.test.d.ts.map +1 -0
- package/dist/tests/onboarding/first-run.test.js +258 -0
- package/dist/tests/onboarding/first-run.test.js.map +1 -0
- package/dist/tests/performance/search-performance.test.d.ts +10 -0
- package/dist/tests/performance/search-performance.test.d.ts.map +1 -0
- package/dist/tests/performance/search-performance.test.js +218 -0
- package/dist/tests/performance/search-performance.test.js.map +1 -0
- package/dist/tests/recommend.test.d.ts +6 -0
- package/dist/tests/recommend.test.d.ts.map +1 -0
- package/dist/tests/recommend.test.js +208 -0
- package/dist/tests/recommend.test.js.map +1 -0
- package/dist/tests/suggestions/suggestion-engine.test.d.ts +6 -0
- package/dist/tests/suggestions/suggestion-engine.test.d.ts.map +1 -0
- package/dist/tests/suggestions/suggestion-engine.test.js +448 -0
- package/dist/tests/suggestions/suggestion-engine.test.js.map +1 -0
- package/dist/tests/test-utils.d.ts +74 -0
- package/dist/tests/test-utils.d.ts.map +1 -0
- package/dist/tests/test-utils.js +98 -0
- package/dist/tests/test-utils.js.map +1 -0
- package/dist/tests/tools.test.d.ts +5 -0
- package/dist/tests/tools.test.d.ts.map +1 -0
- package/dist/tests/tools.test.js +138 -0
- package/dist/tests/tools.test.js.map +1 -0
- package/dist/tests/unit/installed-skills.test.d.ts +6 -0
- package/dist/tests/unit/installed-skills.test.d.ts.map +1 -0
- package/dist/tests/unit/installed-skills.test.js +285 -0
- package/dist/tests/unit/installed-skills.test.js.map +1 -0
- package/dist/tests/unit/logger.test.d.ts +6 -0
- package/dist/tests/unit/logger.test.d.ts.map +1 -0
- package/dist/tests/unit/logger.test.js +281 -0
- package/dist/tests/unit/logger.test.js.map +1 -0
- package/dist/tests/validate.test.d.ts +5 -0
- package/dist/tests/validate.test.d.ts.map +1 -0
- package/dist/tests/validate.test.js +303 -0
- package/dist/tests/validate.test.js.map +1 -0
- package/dist/tests/webhooks/proxy-trust.security.test.d.ts +8 -0
- package/dist/tests/webhooks/proxy-trust.security.test.d.ts.map +1 -0
- package/dist/tests/webhooks/proxy-trust.security.test.js +145 -0
- package/dist/tests/webhooks/proxy-trust.security.test.js.map +1 -0
- package/dist/tests/webhooks/rate-limiter.security.test.d.ts +8 -0
- package/dist/tests/webhooks/rate-limiter.security.test.d.ts.map +1 -0
- package/dist/tests/webhooks/rate-limiter.security.test.js +122 -0
- package/dist/tests/webhooks/rate-limiter.security.test.js.map +1 -0
- package/dist/vitest.config.d.ts +6 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +13 -0
- package/dist/vitest.config.js.map +1 -0
- 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"}
|