@skillsmith/core 2.1.0 → 2.1.2

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 (204) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/src/analysis/types.d.ts +2 -0
  3. package/dist/src/analysis/types.d.ts.map +1 -1
  4. package/dist/src/analysis/types.js +13 -1
  5. package/dist/src/analysis/types.js.map +1 -1
  6. package/dist/src/analytics/AnalyticsRepository.d.ts +4 -0
  7. package/dist/src/analytics/AnalyticsRepository.d.ts.map +1 -1
  8. package/dist/src/analytics/AnalyticsRepository.js +26 -44
  9. package/dist/src/analytics/AnalyticsRepository.js.map +1 -1
  10. package/dist/src/analytics/schema.d.ts +1 -1
  11. package/dist/src/analytics/schema.d.ts.map +1 -1
  12. package/dist/src/analytics/schema.js +68 -0
  13. package/dist/src/analytics/schema.js.map +1 -1
  14. package/dist/src/api/client.d.ts +33 -29
  15. package/dist/src/api/client.d.ts.map +1 -1
  16. package/dist/src/api/client.js +15 -10
  17. package/dist/src/api/client.js.map +1 -1
  18. package/dist/src/billing/BillingService.d.ts +139 -0
  19. package/dist/src/billing/BillingService.d.ts.map +1 -0
  20. package/dist/src/billing/BillingService.js +393 -0
  21. package/dist/src/billing/BillingService.js.map +1 -0
  22. package/dist/src/billing/GDPRComplianceService.d.ts +176 -0
  23. package/dist/src/billing/GDPRComplianceService.d.ts.map +1 -0
  24. package/dist/src/billing/GDPRComplianceService.js +361 -0
  25. package/dist/src/billing/GDPRComplianceService.js.map +1 -0
  26. package/dist/src/billing/StripeClient.d.ts +177 -0
  27. package/dist/src/billing/StripeClient.d.ts.map +1 -0
  28. package/dist/src/billing/StripeClient.js +462 -0
  29. package/dist/src/billing/StripeClient.js.map +1 -0
  30. package/dist/src/billing/StripeReconciliationJob.d.ts +95 -0
  31. package/dist/src/billing/StripeReconciliationJob.d.ts.map +1 -0
  32. package/dist/src/billing/StripeReconciliationJob.js +405 -0
  33. package/dist/src/billing/StripeReconciliationJob.js.map +1 -0
  34. package/dist/src/billing/StripeWebhookHandler.d.ts +92 -0
  35. package/dist/src/billing/StripeWebhookHandler.d.ts.map +1 -0
  36. package/dist/src/billing/StripeWebhookHandler.js +409 -0
  37. package/dist/src/billing/StripeWebhookHandler.js.map +1 -0
  38. package/dist/src/billing/index.d.ts +18 -0
  39. package/dist/src/billing/index.d.ts.map +1 -0
  40. package/dist/src/billing/index.js +19 -0
  41. package/dist/src/billing/index.js.map +1 -0
  42. package/dist/src/billing/types.d.ts +266 -0
  43. package/dist/src/billing/types.d.ts.map +1 -0
  44. package/dist/src/billing/types.js +23 -0
  45. package/dist/src/billing/types.js.map +1 -0
  46. package/dist/src/embeddings/hnsw-store.d.ts +568 -0
  47. package/dist/src/embeddings/hnsw-store.d.ts.map +1 -0
  48. package/dist/src/embeddings/hnsw-store.js +805 -0
  49. package/dist/src/embeddings/hnsw-store.js.map +1 -0
  50. package/dist/src/embeddings/index.d.ts +2 -0
  51. package/dist/src/embeddings/index.d.ts.map +1 -1
  52. package/dist/src/embeddings/index.js +2 -0
  53. package/dist/src/embeddings/index.js.map +1 -1
  54. package/dist/src/index.d.ts +1 -0
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +2 -0
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/learning/PatternStore.d.ts +457 -0
  59. package/dist/src/learning/PatternStore.d.ts.map +1 -0
  60. package/dist/src/learning/PatternStore.js +893 -0
  61. package/dist/src/learning/PatternStore.js.map +1 -0
  62. package/dist/src/learning/ReasoningBankIntegration.d.ts +403 -0
  63. package/dist/src/learning/ReasoningBankIntegration.d.ts.map +1 -0
  64. package/dist/src/learning/ReasoningBankIntegration.js +627 -0
  65. package/dist/src/learning/ReasoningBankIntegration.js.map +1 -0
  66. package/dist/src/learning/index.d.ts +15 -0
  67. package/dist/src/learning/index.d.ts.map +1 -0
  68. package/dist/src/learning/index.js +15 -0
  69. package/dist/src/learning/index.js.map +1 -0
  70. package/dist/src/routing/SONARouter.d.ts +154 -0
  71. package/dist/src/routing/SONARouter.d.ts.map +1 -0
  72. package/dist/src/routing/SONARouter.js +679 -0
  73. package/dist/src/routing/SONARouter.js.map +1 -0
  74. package/dist/src/routing/index.d.ts +9 -0
  75. package/dist/src/routing/index.d.ts.map +1 -0
  76. package/dist/src/routing/index.js +10 -0
  77. package/dist/src/routing/index.js.map +1 -0
  78. package/dist/src/routing/types.d.ts +331 -0
  79. package/dist/src/routing/types.d.ts.map +1 -0
  80. package/dist/src/routing/types.js +203 -0
  81. package/dist/src/routing/types.js.map +1 -0
  82. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
  83. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  84. package/dist/src/security/SkillSandbox.d.ts +156 -0
  85. package/dist/src/security/SkillSandbox.d.ts.map +1 -0
  86. package/dist/src/security/SkillSandbox.js +303 -0
  87. package/dist/src/security/SkillSandbox.js.map +1 -0
  88. package/dist/src/security/index.d.ts +3 -1
  89. package/dist/src/security/index.d.ts.map +1 -1
  90. package/dist/src/security/index.js +5 -1
  91. package/dist/src/security/index.js.map +1 -1
  92. package/dist/src/security/rate-limiter/presets.d.ts +12 -0
  93. package/dist/src/security/rate-limiter/presets.d.ts.map +1 -1
  94. package/dist/src/security/rate-limiter/presets.js +12 -0
  95. package/dist/src/security/rate-limiter/presets.js.map +1 -1
  96. package/dist/src/security/sanitization.d.ts +85 -0
  97. package/dist/src/security/sanitization.d.ts.map +1 -1
  98. package/dist/src/security/sanitization.js +133 -0
  99. package/dist/src/security/sanitization.js.map +1 -1
  100. package/dist/src/security/scanner/SecurityScanner.d.ts +23 -0
  101. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  102. package/dist/src/security/scanner/SecurityScanner.js +232 -28
  103. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  104. package/dist/src/security/scanner/patterns.d.ts +13 -0
  105. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  106. package/dist/src/security/scanner/patterns.js +51 -0
  107. package/dist/src/security/scanner/patterns.js.map +1 -1
  108. package/dist/src/security/scanner/types.d.ts +13 -1
  109. package/dist/src/security/scanner/types.d.ts.map +1 -1
  110. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  111. package/dist/src/security/scanner/weights.js +1 -0
  112. package/dist/src/security/scanner/weights.js.map +1 -1
  113. package/dist/src/session/SessionManager.d.ts +7 -0
  114. package/dist/src/session/SessionManager.d.ts.map +1 -1
  115. package/dist/src/session/SessionManager.js +117 -10
  116. package/dist/src/session/SessionManager.js.map +1 -1
  117. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  118. package/dist/src/sync/SyncEngine.js +52 -32
  119. package/dist/src/sync/SyncEngine.js.map +1 -1
  120. package/dist/src/testing/MultiLLMProvider.d.ts +374 -0
  121. package/dist/src/testing/MultiLLMProvider.d.ts.map +1 -0
  122. package/dist/src/testing/MultiLLMProvider.js +720 -0
  123. package/dist/src/testing/MultiLLMProvider.js.map +1 -0
  124. package/dist/src/testing/index.d.ts +8 -0
  125. package/dist/src/testing/index.d.ts.map +1 -0
  126. package/dist/src/testing/index.js +9 -0
  127. package/dist/src/testing/index.js.map +1 -0
  128. package/dist/src/types.d.ts +3 -0
  129. package/dist/src/types.d.ts.map +1 -1
  130. package/dist/tests/SecurityScanner.test.js +337 -1
  131. package/dist/tests/SecurityScanner.test.js.map +1 -1
  132. package/dist/tests/billing/BillingService.test.d.ts +7 -0
  133. package/dist/tests/billing/BillingService.test.d.ts.map +1 -0
  134. package/dist/tests/billing/BillingService.test.js +168 -0
  135. package/dist/tests/billing/BillingService.test.js.map +1 -0
  136. package/dist/tests/billing/GDPRCompliance.test.d.ts +7 -0
  137. package/dist/tests/billing/GDPRCompliance.test.d.ts.map +1 -0
  138. package/dist/tests/billing/GDPRCompliance.test.js +195 -0
  139. package/dist/tests/billing/GDPRCompliance.test.js.map +1 -0
  140. package/dist/tests/billing/StripeReconciliation.test.d.ts +7 -0
  141. package/dist/tests/billing/StripeReconciliation.test.d.ts.map +1 -0
  142. package/dist/tests/billing/StripeReconciliation.test.js +266 -0
  143. package/dist/tests/billing/StripeReconciliation.test.js.map +1 -0
  144. package/dist/tests/billing/stripe-validators.test.d.ts +7 -0
  145. package/dist/tests/billing/stripe-validators.test.d.ts.map +1 -0
  146. package/dist/tests/billing/stripe-validators.test.js +107 -0
  147. package/dist/tests/billing/stripe-validators.test.js.map +1 -0
  148. package/dist/tests/embeddings/hnsw-store.test.d.ts +7 -0
  149. package/dist/tests/embeddings/hnsw-store.test.d.ts.map +1 -0
  150. package/dist/tests/embeddings/hnsw-store.test.js +295 -0
  151. package/dist/tests/embeddings/hnsw-store.test.js.map +1 -0
  152. package/dist/tests/integration/neural/e2e-learning.test.d.ts +17 -0
  153. package/dist/tests/integration/neural/e2e-learning.test.d.ts.map +1 -0
  154. package/dist/tests/integration/neural/e2e-learning.test.js +238 -0
  155. package/dist/tests/integration/neural/e2e-learning.test.js.map +1 -0
  156. package/dist/tests/integration/neural/helpers.d.ts +132 -0
  157. package/dist/tests/integration/neural/helpers.d.ts.map +1 -0
  158. package/dist/tests/integration/neural/helpers.js +287 -0
  159. package/dist/tests/integration/neural/helpers.js.map +1 -0
  160. package/dist/tests/integration/neural/personalization.test.d.ts +21 -0
  161. package/dist/tests/integration/neural/personalization.test.d.ts.map +1 -0
  162. package/dist/tests/integration/neural/personalization.test.js +304 -0
  163. package/dist/tests/integration/neural/personalization.test.js.map +1 -0
  164. package/dist/tests/integration/neural/preference-learner.test.d.ts +23 -0
  165. package/dist/tests/integration/neural/preference-learner.test.d.ts.map +1 -0
  166. package/dist/tests/integration/neural/preference-learner.test.js +289 -0
  167. package/dist/tests/integration/neural/preference-learner.test.js.map +1 -0
  168. package/dist/tests/integration/neural/privacy.test.d.ts +19 -0
  169. package/dist/tests/integration/neural/privacy.test.d.ts.map +1 -0
  170. package/dist/tests/integration/neural/privacy.test.js +249 -0
  171. package/dist/tests/integration/neural/privacy.test.js.map +1 -0
  172. package/dist/tests/integration/neural/setup.d.ts +175 -0
  173. package/dist/tests/integration/neural/setup.d.ts.map +1 -0
  174. package/dist/tests/integration/neural/setup.js +487 -0
  175. package/dist/tests/integration/neural/setup.js.map +1 -0
  176. package/dist/tests/integration/neural/signal-collection.test.d.ts +21 -0
  177. package/dist/tests/integration/neural/signal-collection.test.d.ts.map +1 -0
  178. package/dist/tests/integration/neural/signal-collection.test.js +232 -0
  179. package/dist/tests/integration/neural/signal-collection.test.js.map +1 -0
  180. package/dist/tests/learning/PatternStore.test.d.ts +8 -0
  181. package/dist/tests/learning/PatternStore.test.d.ts.map +1 -0
  182. package/dist/tests/learning/PatternStore.test.js +589 -0
  183. package/dist/tests/learning/PatternStore.test.js.map +1 -0
  184. package/dist/tests/learning/ReasoningBankIntegration.test.d.ts +8 -0
  185. package/dist/tests/learning/ReasoningBankIntegration.test.d.ts.map +1 -0
  186. package/dist/tests/learning/ReasoningBankIntegration.test.js +269 -0
  187. package/dist/tests/learning/ReasoningBankIntegration.test.js.map +1 -0
  188. package/dist/tests/routing/SONARouter.test.d.ts +8 -0
  189. package/dist/tests/routing/SONARouter.test.d.ts.map +1 -0
  190. package/dist/tests/routing/SONARouter.test.js +400 -0
  191. package/dist/tests/routing/SONARouter.test.js.map +1 -0
  192. package/dist/tests/security/ContinuousSecurity.test.js +10 -12
  193. package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
  194. package/dist/tests/security/SkillSandbox.test.d.ts +8 -0
  195. package/dist/tests/security/SkillSandbox.test.d.ts.map +1 -0
  196. package/dist/tests/security/SkillSandbox.test.js +321 -0
  197. package/dist/tests/security/SkillSandbox.test.js.map +1 -0
  198. package/dist/tests/sync/SyncEngine.test.js +4 -2
  199. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  200. package/dist/tests/testing/MultiLLMProvider.test.d.ts +14 -0
  201. package/dist/tests/testing/MultiLLMProvider.test.d.ts.map +1 -0
  202. package/dist/tests/testing/MultiLLMProvider.test.js +438 -0
  203. package/dist/tests/testing/MultiLLMProvider.test.js.map +1 -0
  204. package/package.json +16 -3
@@ -0,0 +1,304 @@
1
+ /**
2
+ * SMI-1535: Personalization Engine Integration Tests
3
+ *
4
+ * Tests the IPersonalizationEngine interface for applying learned
5
+ * preferences to recommendation results in the Recommendation Learning Loop.
6
+ *
7
+ * Test Cases:
8
+ * 1. shouldPersonalize() returns false with <5 signals
9
+ * 2. shouldPersonalize() returns true with 5+ signals
10
+ * 3. personalizeRecommendations() re-ranks by learned scores
11
+ * 4. Category weight boosts preferred categories
12
+ * 5. Dismiss patterns reduce scores for related skills
13
+ * 6. Uninstall patterns have strongest negative effect
14
+ * 7. Score breakdown shows contributing factors
15
+ * 8. Personalization disabled by user preference
16
+ *
17
+ * @see packages/core/src/learning/interfaces.ts
18
+ * @see docs/execution/phase5-testing-execution.md
19
+ */
20
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
21
+ import { createNeuralTestContext, cleanupNeuralTestContext, createDefaultProfile, } from './setup.js';
22
+ import { generateContext } from './helpers.js';
23
+ import { SkillCategory } from '../../../src/learning/types.js';
24
+ describe('PersonalizationEngine Integration', () => {
25
+ let ctx;
26
+ beforeEach(() => {
27
+ ctx = createNeuralTestContext();
28
+ });
29
+ afterEach(async () => {
30
+ await cleanupNeuralTestContext(ctx);
31
+ });
32
+ describe('Personalization Threshold', () => {
33
+ it('should return false for shouldPersonalize with <5 signals', async () => {
34
+ // Add only 4 signals
35
+ for (let i = 0; i < 4; i++) {
36
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext());
37
+ }
38
+ const shouldPersonalize = await ctx.personalizationEngine.shouldPersonalize();
39
+ expect(shouldPersonalize).toBe(false);
40
+ });
41
+ it('should return true for shouldPersonalize with 5+ signals', async () => {
42
+ // Add exactly 5 signals (threshold)
43
+ for (let i = 0; i < 5; i++) {
44
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext());
45
+ }
46
+ const shouldPersonalize = await ctx.personalizationEngine.shouldPersonalize();
47
+ expect(shouldPersonalize).toBe(true);
48
+ });
49
+ it('should return true with many signals', async () => {
50
+ // Add 20 signals
51
+ for (let i = 0; i < 20; i++) {
52
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext());
53
+ }
54
+ const shouldPersonalize = await ctx.personalizationEngine.shouldPersonalize();
55
+ expect(shouldPersonalize).toBe(true);
56
+ });
57
+ });
58
+ describe('Recommendation Re-ranking', () => {
59
+ it('should re-rank recommendations by learned scores', async () => {
60
+ // Build up preference for testing category
61
+ for (let i = 0; i < 10; i++) {
62
+ await ctx.signalCollector.recordAccept(`testing-skill-${i}`, generateContext({ category: SkillCategory.TESTING }));
63
+ }
64
+ // Train the profile
65
+ let profile = createDefaultProfile();
66
+ const signals = await ctx.signalCollector.getSignals({});
67
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
68
+ await ctx.profileRepository.saveProfile(profile);
69
+ // Create recommendations with testing skill having lower base score
70
+ const recommendations = [
71
+ {
72
+ skill_id: 'devops-skill',
73
+ base_score: 0.9,
74
+ skill_data: { category: SkillCategory.DEVOPS, trustTier: 'community' },
75
+ },
76
+ {
77
+ skill_id: 'testing-skill',
78
+ base_score: 0.7, // Lower base score
79
+ skill_data: { category: SkillCategory.TESTING, trustTier: 'community' },
80
+ },
81
+ {
82
+ skill_id: 'frontend-skill',
83
+ base_score: 0.8,
84
+ skill_data: { category: SkillCategory.FRONTEND, trustTier: 'community' },
85
+ },
86
+ ];
87
+ const personalized = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
88
+ // Results should be sorted by personalized_score descending
89
+ for (let i = 1; i < personalized.length; i++) {
90
+ expect(personalized[i - 1].personalized_score).toBeGreaterThanOrEqual(personalized[i].personalized_score);
91
+ }
92
+ });
93
+ });
94
+ describe('Category Weight Boosting', () => {
95
+ it('should boost scores for preferred categories', async () => {
96
+ // Build strong preference for TESTING
97
+ for (let i = 0; i < 15; i++) {
98
+ await ctx.signalCollector.recordAccept(`testing-skill-${i}`, generateContext({ category: SkillCategory.TESTING }));
99
+ await ctx.signalCollector.recordUsage(`testing-skill-${i}`, 'daily');
100
+ }
101
+ // Train profile
102
+ let profile = createDefaultProfile();
103
+ const signals = await ctx.signalCollector.getSignals({});
104
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
105
+ await ctx.profileRepository.saveProfile(profile);
106
+ // Test single skill personalization
107
+ const recommendations = [
108
+ {
109
+ skill_id: 'preferred-skill',
110
+ base_score: 0.5,
111
+ skill_data: { category: SkillCategory.TESTING, trustTier: 'community' },
112
+ },
113
+ ];
114
+ const [result] = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
115
+ // Category boost should be positive
116
+ expect(result.score_breakdown.category_boost).toBeGreaterThan(0);
117
+ expect(result.personalization_applied).toBe(true);
118
+ });
119
+ });
120
+ describe('Dismiss Pattern Effects', () => {
121
+ it('should reduce scores for skills matching dismiss patterns', async () => {
122
+ // Dismiss multiple skills to establish negative patterns
123
+ const dismissedSkills = ['skill-a', 'skill-b', 'skill-c', 'skill-d', 'skill-e'];
124
+ for (const skillId of dismissedSkills) {
125
+ await ctx.signalCollector.recordDismiss(skillId, generateContext());
126
+ }
127
+ // Train profile with dismissals
128
+ let profile = createDefaultProfile();
129
+ const signals = await ctx.signalCollector.getSignals({});
130
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
131
+ await ctx.profileRepository.saveProfile(profile);
132
+ // Try to recommend a dismissed skill
133
+ const recommendations = [
134
+ {
135
+ skill_id: 'skill-a', // Previously dismissed
136
+ base_score: 0.9,
137
+ skill_data: { category: SkillCategory.GIT, trustTier: 'verified' },
138
+ },
139
+ {
140
+ skill_id: 'new-skill', // Never seen before
141
+ base_score: 0.7,
142
+ skill_data: { category: SkillCategory.GIT, trustTier: 'verified' },
143
+ },
144
+ ];
145
+ const personalized = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
146
+ // Find results
147
+ const dismissedResult = personalized.find((r) => r.skill_id === 'skill-a');
148
+ const newResult = personalized.find((r) => r.skill_id === 'new-skill');
149
+ // Dismissed skill should have anti-penalty
150
+ expect(dismissedResult.score_breakdown.anti_penalty).toBeLessThan(0);
151
+ // New skill should have no anti-penalty
152
+ expect(newResult.score_breakdown.anti_penalty).toBe(0);
153
+ });
154
+ });
155
+ describe('Uninstall Impact', () => {
156
+ it('should apply strongest negative effect for uninstalled skills', async () => {
157
+ // Uninstall some skills (strongest negative signal)
158
+ const uninstalledSkills = ['uninstalled-1', 'uninstalled-2'];
159
+ for (const skillId of uninstalledSkills) {
160
+ await ctx.signalCollector.recordAccept(skillId, generateContext());
161
+ await ctx.signalCollector.recordUninstall(skillId, 7);
162
+ }
163
+ // Also dismiss some (weaker negative)
164
+ await ctx.signalCollector.recordDismiss('dismissed-1', generateContext());
165
+ // Train profile
166
+ let profile = createDefaultProfile();
167
+ const signals = await ctx.signalCollector.getSignals({});
168
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
169
+ await ctx.profileRepository.saveProfile(profile);
170
+ // Both uninstalled and dismissed should be in negative patterns
171
+ expect(profile.negative_patterns.skill_ids).toContain('uninstalled-1');
172
+ expect(profile.negative_patterns.skill_ids).toContain('dismissed-1');
173
+ // Try to recommend
174
+ const recommendations = [
175
+ {
176
+ skill_id: 'uninstalled-1',
177
+ base_score: 0.9,
178
+ skill_data: { category: SkillCategory.BACKEND },
179
+ },
180
+ {
181
+ skill_id: 'dismissed-1',
182
+ base_score: 0.9,
183
+ skill_data: { category: SkillCategory.BACKEND },
184
+ },
185
+ ];
186
+ const personalized = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
187
+ // Both should have anti-penalty
188
+ for (const result of personalized) {
189
+ expect(result.score_breakdown.anti_penalty).toBeLessThan(0);
190
+ }
191
+ });
192
+ });
193
+ describe('Score Breakdown', () => {
194
+ it('should show contributing factors in score breakdown', async () => {
195
+ // Build mixed preferences
196
+ for (let i = 0; i < 8; i++) {
197
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext({
198
+ category: SkillCategory.TESTING,
199
+ trustTier: 'verified',
200
+ }));
201
+ }
202
+ let profile = createDefaultProfile();
203
+ const signals = await ctx.signalCollector.getSignals({});
204
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
205
+ await ctx.profileRepository.saveProfile(profile);
206
+ const recommendations = [
207
+ {
208
+ skill_id: 'new-testing-skill',
209
+ base_score: 0.75,
210
+ skill_data: {
211
+ category: SkillCategory.TESTING,
212
+ trustTier: 'verified',
213
+ keywords: ['unit', 'integration'],
214
+ },
215
+ },
216
+ ];
217
+ const [result] = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
218
+ // Score breakdown should contain all components
219
+ expect(result.score_breakdown).toHaveProperty('category_boost');
220
+ expect(result.score_breakdown).toHaveProperty('trust_boost');
221
+ expect(result.score_breakdown).toHaveProperty('keyword_boost');
222
+ expect(result.score_breakdown).toHaveProperty('anti_penalty');
223
+ // For this profile, we should see positive category and trust boosts
224
+ expect(result.score_breakdown.category_boost).toBeGreaterThan(0);
225
+ expect(result.score_breakdown.trust_boost).toBeGreaterThan(0);
226
+ expect(result.score_breakdown.anti_penalty).toBe(0); // Not a dismissed skill
227
+ });
228
+ });
229
+ describe('Personalization Control', () => {
230
+ it('should not apply personalization when below threshold', async () => {
231
+ // Only 3 signals (below threshold of 5)
232
+ for (let i = 0; i < 3; i++) {
233
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext({ category: SkillCategory.GIT }));
234
+ }
235
+ // Even with profile data, personalization shouldn't apply
236
+ let profile = createDefaultProfile();
237
+ const signals = await ctx.signalCollector.getSignals({});
238
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
239
+ await ctx.profileRepository.saveProfile(profile);
240
+ const recommendations = [
241
+ {
242
+ skill_id: 'some-skill',
243
+ base_score: 0.8,
244
+ skill_data: { category: SkillCategory.GIT },
245
+ },
246
+ ];
247
+ const [result] = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
248
+ // Personalization should not be applied
249
+ expect(result.personalization_applied).toBe(false);
250
+ // Score should equal base score when not personalized
251
+ expect(result.personalized_score).toBe(0.8);
252
+ });
253
+ it('should reset to default when requested', async () => {
254
+ // Build some preferences
255
+ for (let i = 0; i < 10; i++) {
256
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext());
257
+ }
258
+ let profile = createDefaultProfile();
259
+ const signals = await ctx.signalCollector.getSignals({});
260
+ profile = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
261
+ await ctx.profileRepository.saveProfile(profile, 'user-1');
262
+ // Verify profile exists
263
+ expect(await ctx.profileRepository.exists('user-1')).toBe(true);
264
+ // Reset
265
+ await ctx.personalizationEngine.resetToDefault('user-1');
266
+ // Profile should be deleted
267
+ expect(await ctx.profileRepository.exists('user-1')).toBe(false);
268
+ });
269
+ });
270
+ describe('Edge Cases', () => {
271
+ it('should handle empty recommendations list', async () => {
272
+ const personalized = await ctx.personalizationEngine.personalizeRecommendations([]);
273
+ expect(personalized).toEqual([]);
274
+ });
275
+ it('should handle skills without category', async () => {
276
+ // Add signals to meet threshold
277
+ for (let i = 0; i < 5; i++) {
278
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext());
279
+ }
280
+ const recommendations = [
281
+ {
282
+ skill_id: 'no-category-skill',
283
+ base_score: 0.7,
284
+ skill_data: { trustTier: 'community' }, // No category
285
+ },
286
+ ];
287
+ const [result] = await ctx.personalizationEngine.personalizeRecommendations(recommendations);
288
+ expect(result.skill_id).toBe('no-category-skill');
289
+ expect(result.score_breakdown.category_boost).toBe(0);
290
+ });
291
+ it('should handle user with no profile', async () => {
292
+ // Add signals to meet threshold
293
+ for (let i = 0; i < 5; i++) {
294
+ await ctx.signalCollector.recordAccept(`skill-${i}`, generateContext());
295
+ }
296
+ // No profile saved for 'new-user'
297
+ const profile = await ctx.personalizationEngine.getUserProfile('new-user');
298
+ // Should return default profile
299
+ expect(profile.signal_count).toBe(0);
300
+ expect(profile.version).toBe(1);
301
+ });
302
+ });
303
+ });
304
+ //# sourceMappingURL=personalization.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personalization.test.js","sourceRoot":"","sources":["../../../../tests/integration/neural/personalization.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SMI-1535: Preference Learner Integration Tests
3
+ *
4
+ * Tests the IPreferenceLearner interface for updating user profiles
5
+ * based on interaction signals in the Recommendation Learning Loop.
6
+ *
7
+ * Test Cases:
8
+ * 1. Update profile from single ACCEPT signal
9
+ * 2. Update profile from single DISMISS signal
10
+ * 3. Batch update with 100 signals
11
+ * 4. Weight decay after 30 days
12
+ * 5. Weight bounds enforcement (-2.0 to 2.0)
13
+ * 6. Category weight accumulation
14
+ * 7. Trust tier preference learning
15
+ * 8. Author preference learning
16
+ * 9. Cold start default weights
17
+ * 10. Profile persistence across sessions
18
+ *
19
+ * @see packages/core/src/learning/interfaces.ts
20
+ * @see docs/execution/phase5-testing-execution.md
21
+ */
22
+ export {};
23
+ //# sourceMappingURL=preference-learner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-learner.test.d.ts","sourceRoot":"","sources":["../../../../tests/integration/neural/preference-learner.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG"}
@@ -0,0 +1,289 @@
1
+ /**
2
+ * SMI-1535: Preference Learner Integration Tests
3
+ *
4
+ * Tests the IPreferenceLearner interface for updating user profiles
5
+ * based on interaction signals in the Recommendation Learning Loop.
6
+ *
7
+ * Test Cases:
8
+ * 1. Update profile from single ACCEPT signal
9
+ * 2. Update profile from single DISMISS signal
10
+ * 3. Batch update with 100 signals
11
+ * 4. Weight decay after 30 days
12
+ * 5. Weight bounds enforcement (-2.0 to 2.0)
13
+ * 6. Category weight accumulation
14
+ * 7. Trust tier preference learning
15
+ * 8. Author preference learning
16
+ * 9. Cold start default weights
17
+ * 10. Profile persistence across sessions
18
+ *
19
+ * @see packages/core/src/learning/interfaces.ts
20
+ * @see docs/execution/phase5-testing-execution.md
21
+ */
22
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
23
+ import { createNeuralTestContext, cleanupNeuralTestContext, createDefaultProfile, } from './setup.js';
24
+ import { generateSignal } from './helpers.js';
25
+ import { SignalType, SkillCategory, SIGNAL_WEIGHTS, DEFAULT_LEARNING_CONFIG, COLD_START_WEIGHTS, } from '../../../src/learning/types.js';
26
+ describe('PreferenceLearner Integration', () => {
27
+ let ctx;
28
+ beforeEach(() => {
29
+ ctx = createNeuralTestContext();
30
+ });
31
+ afterEach(async () => {
32
+ await cleanupNeuralTestContext(ctx);
33
+ });
34
+ describe('Single Signal Updates', () => {
35
+ it('should update profile from single ACCEPT signal', async () => {
36
+ const profile = createDefaultProfile();
37
+ const signal = generateSignal({
38
+ type: SignalType.ACCEPT,
39
+ skillId: 'test-skill-1',
40
+ category: SkillCategory.TESTING,
41
+ trustTier: 'verified',
42
+ });
43
+ const updated = await ctx.preferenceLearner.updateProfile(profile, signal);
44
+ // Signal count should increment
45
+ expect(updated.signal_count).toBe(1);
46
+ // Category weight should increase (ACCEPT weight is 0.5, learning rate 0.1)
47
+ const expectedCategoryIncrease = SIGNAL_WEIGHTS[SignalType.ACCEPT] * DEFAULT_LEARNING_CONFIG.learning_rate;
48
+ const originalCategoryWeight = COLD_START_WEIGHTS.category_weights[SkillCategory.TESTING] ?? 0;
49
+ expect(updated.category_weights[SkillCategory.TESTING]).toBeCloseTo(originalCategoryWeight + expectedCategoryIncrease, 5);
50
+ // Trust tier weight should increase
51
+ expect(updated.trust_tier_weights['verified']).toBeGreaterThan(profile.trust_tier_weights['verified'] ?? 0);
52
+ // Timestamp should be updated (allow 1 second tolerance for timing)
53
+ const timeDiff = updated.last_updated - profile.last_updated;
54
+ expect(timeDiff).toBeGreaterThanOrEqual(0);
55
+ expect(timeDiff).toBeLessThan(1000);
56
+ });
57
+ it('should update profile from single DISMISS signal', async () => {
58
+ const profile = createDefaultProfile();
59
+ const signal = generateSignal({
60
+ type: SignalType.DISMISS,
61
+ skillId: 'unwanted-skill',
62
+ category: SkillCategory.DEVOPS,
63
+ trustTier: 'experimental',
64
+ });
65
+ const updated = await ctx.preferenceLearner.updateProfile(profile, signal);
66
+ // Category weight should decrease (DISMISS weight is -0.3)
67
+ const expectedCategoryDecrease = SIGNAL_WEIGHTS[SignalType.DISMISS] * DEFAULT_LEARNING_CONFIG.learning_rate;
68
+ const originalCategoryWeight = COLD_START_WEIGHTS.category_weights[SkillCategory.DEVOPS] ?? 0;
69
+ expect(updated.category_weights[SkillCategory.DEVOPS]).toBeCloseTo(originalCategoryWeight + expectedCategoryDecrease, 5);
70
+ // Skill should be added to negative patterns
71
+ expect(updated.negative_patterns.skill_ids).toContain('unwanted-skill');
72
+ });
73
+ });
74
+ describe('Batch Updates', () => {
75
+ it('should batch update with 100 signals', async () => {
76
+ const profile = createDefaultProfile();
77
+ // Generate 100 signals with varied types and categories
78
+ const signals = [];
79
+ for (let i = 0; i < 100; i++) {
80
+ const types = [
81
+ SignalType.ACCEPT,
82
+ SignalType.DISMISS,
83
+ SignalType.USAGE_DAILY,
84
+ SignalType.USAGE_WEEKLY,
85
+ ];
86
+ const categories = Object.values(SkillCategory);
87
+ signals.push(generateSignal({
88
+ type: types[i % types.length],
89
+ skillId: `skill-${i}`,
90
+ category: categories[i % categories.length],
91
+ trustTier: i % 2 === 0 ? 'verified' : 'community',
92
+ }));
93
+ }
94
+ const startTime = Date.now();
95
+ const updated = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
96
+ const duration = Date.now() - startTime;
97
+ // Signal count should be 100
98
+ expect(updated.signal_count).toBe(100);
99
+ // Should complete quickly (< 1000ms including test overhead)
100
+ expect(duration).toBeLessThan(1000);
101
+ // Should have updated multiple category weights
102
+ const nonZeroCategoryWeights = Object.values(updated.category_weights).filter((w) => w !== undefined &&
103
+ Math.abs(w - (COLD_START_WEIGHTS.category_weights[SkillCategory.TESTING] ?? 0)) > 0.001);
104
+ expect(nonZeroCategoryWeights.length).toBeGreaterThan(0);
105
+ });
106
+ });
107
+ describe('Weight Decay', () => {
108
+ it('should apply weight decay after 30 days', async () => {
109
+ // Create profile with significant weights
110
+ const profile = createDefaultProfile();
111
+ profile.category_weights = {
112
+ [SkillCategory.TESTING]: 1.5,
113
+ [SkillCategory.GIT]: -1.0,
114
+ [SkillCategory.DEVOPS]: 0.8,
115
+ };
116
+ profile.trust_tier_weights = {
117
+ verified: 1.2,
118
+ community: 0.5,
119
+ };
120
+ profile.keyword_weights = {
121
+ test: 0.9,
122
+ ci: -0.4,
123
+ };
124
+ // Apply decay with default factor (0.95)
125
+ const decayed = await ctx.preferenceLearner.decayWeights(profile);
126
+ // All weights should be reduced by decay factor
127
+ expect(decayed.category_weights[SkillCategory.TESTING]).toBeCloseTo(1.5 * DEFAULT_LEARNING_CONFIG.decay_factor, 5);
128
+ expect(decayed.category_weights[SkillCategory.GIT]).toBeCloseTo(-1.0 * DEFAULT_LEARNING_CONFIG.decay_factor, 5);
129
+ expect(decayed.trust_tier_weights.verified).toBeCloseTo(1.2 * DEFAULT_LEARNING_CONFIG.decay_factor, 5);
130
+ expect(decayed.keyword_weights.test).toBeCloseTo(0.9 * DEFAULT_LEARNING_CONFIG.decay_factor, 5);
131
+ });
132
+ it('should support custom decay factor', async () => {
133
+ const profile = createDefaultProfile();
134
+ profile.category_weights = {
135
+ [SkillCategory.TESTING]: 1.0,
136
+ };
137
+ // Apply aggressive decay (0.8)
138
+ const decayed = await ctx.preferenceLearner.decayWeights(profile, 0.8);
139
+ expect(decayed.category_weights[SkillCategory.TESTING]).toBeCloseTo(0.8, 5);
140
+ });
141
+ });
142
+ describe('Weight Bounds', () => {
143
+ it('should enforce weight bounds (-2.0 to 2.0)', async () => {
144
+ const profile = createDefaultProfile();
145
+ // Generate many ACCEPT signals for the same category to push weight high
146
+ const signals = Array.from({ length: 100 }, () => generateSignal({
147
+ type: SignalType.USAGE_DAILY, // Highest positive weight (1.0)
148
+ category: SkillCategory.TESTING,
149
+ trustTier: 'verified',
150
+ }));
151
+ const updated = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
152
+ // Weight should be capped at max bound
153
+ expect(updated.category_weights[SkillCategory.TESTING]).toBeLessThanOrEqual(DEFAULT_LEARNING_CONFIG.weight_bounds.max);
154
+ expect(updated.category_weights[SkillCategory.TESTING]).toBeGreaterThanOrEqual(DEFAULT_LEARNING_CONFIG.weight_bounds.min);
155
+ });
156
+ it('should enforce negative weight bounds', async () => {
157
+ const profile = createDefaultProfile();
158
+ // Generate many UNINSTALL signals to push weight negative
159
+ const signals = Array.from({ length: 100 }, () => generateSignal({
160
+ type: SignalType.UNINSTALL, // Most negative weight (-1.0)
161
+ category: SkillCategory.SECURITY,
162
+ trustTier: 'experimental',
163
+ }));
164
+ const updated = await ctx.preferenceLearner.batchUpdateProfile(profile, signals);
165
+ // Weight should be capped at min bound
166
+ expect(updated.category_weights[SkillCategory.SECURITY]).toBeGreaterThanOrEqual(DEFAULT_LEARNING_CONFIG.weight_bounds.min);
167
+ });
168
+ });
169
+ describe('Category Weight Accumulation', () => {
170
+ it('should accumulate category weights over multiple interactions', async () => {
171
+ let profile = createDefaultProfile();
172
+ const originalWeight = profile.category_weights[SkillCategory.FRONTEND] ?? 0;
173
+ // Simulate 5 accept interactions over time
174
+ for (let i = 0; i < 5; i++) {
175
+ const signal = generateSignal({
176
+ type: SignalType.ACCEPT,
177
+ skillId: `frontend-skill-${i}`,
178
+ category: SkillCategory.FRONTEND,
179
+ });
180
+ profile = await ctx.preferenceLearner.updateProfile(profile, signal);
181
+ }
182
+ // Weight should have accumulated
183
+ const expectedIncrease = 5 * SIGNAL_WEIGHTS[SignalType.ACCEPT] * DEFAULT_LEARNING_CONFIG.learning_rate;
184
+ expect(profile.category_weights[SkillCategory.FRONTEND]).toBeCloseTo(Math.min(originalWeight + expectedIncrease, DEFAULT_LEARNING_CONFIG.weight_bounds.max), 5);
185
+ });
186
+ });
187
+ describe('Trust Tier Learning', () => {
188
+ it('should learn trust tier preferences', async () => {
189
+ let profile = createDefaultProfile();
190
+ // User consistently accepts verified skills
191
+ for (let i = 0; i < 10; i++) {
192
+ const signal = generateSignal({
193
+ type: SignalType.ACCEPT,
194
+ skillId: `verified-skill-${i}`,
195
+ trustTier: 'verified',
196
+ });
197
+ profile = await ctx.preferenceLearner.updateProfile(profile, signal);
198
+ }
199
+ // User consistently dismisses experimental skills
200
+ for (let i = 0; i < 10; i++) {
201
+ const signal = generateSignal({
202
+ type: SignalType.DISMISS,
203
+ skillId: `experimental-skill-${i}`,
204
+ trustTier: 'experimental',
205
+ });
206
+ profile = await ctx.preferenceLearner.updateProfile(profile, signal);
207
+ }
208
+ // Verified should have higher weight than experimental
209
+ expect(profile.trust_tier_weights['verified']).toBeGreaterThan(profile.trust_tier_weights['experimental']);
210
+ });
211
+ });
212
+ describe('Author Preference Learning', () => {
213
+ it('should track negative patterns for dismissed authors', async () => {
214
+ let profile = createDefaultProfile();
215
+ // Dismiss skills from a specific author multiple times
216
+ const authorSkills = ['author-x/skill-1', 'author-x/skill-2', 'author-x/skill-3'];
217
+ for (const skillId of authorSkills) {
218
+ const signal = generateSignal({
219
+ type: SignalType.DISMISS,
220
+ skillId,
221
+ });
222
+ profile = await ctx.preferenceLearner.updateProfile(profile, signal);
223
+ }
224
+ // All dismissed skills should be in negative patterns
225
+ for (const skillId of authorSkills) {
226
+ expect(profile.negative_patterns.skill_ids).toContain(skillId);
227
+ }
228
+ });
229
+ });
230
+ describe('Cold Start', () => {
231
+ it('should use cold start default weights for new users', () => {
232
+ const profile = createDefaultProfile();
233
+ // Should have default category weights
234
+ expect(profile.category_weights[SkillCategory.TESTING]).toBe(COLD_START_WEIGHTS.category_weights[SkillCategory.TESTING]);
235
+ expect(profile.category_weights[SkillCategory.GIT]).toBe(COLD_START_WEIGHTS.category_weights[SkillCategory.GIT]);
236
+ // Should have default trust tier weights
237
+ expect(profile.trust_tier_weights['verified']).toBe(COLD_START_WEIGHTS.trust_tier_weights['verified']);
238
+ expect(profile.trust_tier_weights['community']).toBe(COLD_START_WEIGHTS.trust_tier_weights['community']);
239
+ // Signal count should be 0
240
+ expect(profile.signal_count).toBe(0);
241
+ });
242
+ });
243
+ describe('Profile Persistence', () => {
244
+ it('should persist profile across save/load cycles', async () => {
245
+ // Create and modify profile
246
+ let profile = createDefaultProfile();
247
+ for (let i = 0; i < 10; i++) {
248
+ const signal = generateSignal({
249
+ type: SignalType.ACCEPT,
250
+ skillId: `skill-${i}`,
251
+ category: SkillCategory.BACKEND,
252
+ });
253
+ profile = await ctx.preferenceLearner.updateProfile(profile, signal);
254
+ }
255
+ // Save profile
256
+ await ctx.profileRepository.saveProfile(profile, 'test-user');
257
+ // Load profile
258
+ const loaded = await ctx.profileRepository.getProfile('test-user');
259
+ expect(loaded).not.toBeNull();
260
+ expect(loaded.signal_count).toBe(10);
261
+ expect(loaded.category_weights[SkillCategory.BACKEND]).toBeCloseTo(profile.category_weights[SkillCategory.BACKEND], 5);
262
+ });
263
+ });
264
+ describe('Configuration', () => {
265
+ it('should use configurable learning rate', async () => {
266
+ // Set a custom learning rate
267
+ ctx.preferenceLearner.setConfig({ learning_rate: 0.5 });
268
+ const profile = createDefaultProfile();
269
+ const signal = generateSignal({
270
+ type: SignalType.ACCEPT,
271
+ skillId: 'test',
272
+ category: SkillCategory.DATABASE,
273
+ });
274
+ const updated = await ctx.preferenceLearner.updateProfile(profile, signal);
275
+ // Weight increase should reflect higher learning rate
276
+ const expectedIncrease = SIGNAL_WEIGHTS[SignalType.ACCEPT] * 0.5;
277
+ const originalWeight = COLD_START_WEIGHTS.category_weights[SkillCategory.DATABASE] ?? 0;
278
+ expect(updated.category_weights[SkillCategory.DATABASE]).toBeCloseTo(originalWeight + expectedIncrease, 5);
279
+ });
280
+ it('should return current config', () => {
281
+ const config = ctx.preferenceLearner.getConfig();
282
+ expect(config.learning_rate).toBe(DEFAULT_LEARNING_CONFIG.learning_rate);
283
+ expect(config.decay_factor).toBe(DEFAULT_LEARNING_CONFIG.decay_factor);
284
+ expect(config.min_signals_threshold).toBe(DEFAULT_LEARNING_CONFIG.min_signals_threshold);
285
+ expect(config.weight_bounds).toEqual(DEFAULT_LEARNING_CONFIG.weight_bounds);
286
+ });
287
+ });
288
+ });
289
+ //# sourceMappingURL=preference-learner.test.js.map