@skillsmith/core 0.4.14 → 0.4.15

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 (214) hide show
  1. package/README.md +2 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/api/client.d.ts +1 -0
  4. package/dist/src/api/client.d.ts.map +1 -1
  5. package/dist/src/api/client.health.d.ts +26 -0
  6. package/dist/src/api/client.health.d.ts.map +1 -0
  7. package/dist/src/api/client.health.js +74 -0
  8. package/dist/src/api/client.health.js.map +1 -0
  9. package/dist/src/api/client.js +3 -51
  10. package/dist/src/api/client.js.map +1 -1
  11. package/dist/src/db/createDatabase.d.ts +9 -1
  12. package/dist/src/db/createDatabase.d.ts.map +1 -1
  13. package/dist/src/db/createDatabase.js +9 -1
  14. package/dist/src/db/createDatabase.js.map +1 -1
  15. package/dist/src/db/drivers/sqljsDriver.d.ts.map +1 -1
  16. package/dist/src/db/drivers/sqljsDriver.js +5 -2
  17. package/dist/src/db/drivers/sqljsDriver.js.map +1 -1
  18. package/dist/src/db/migrations/v5b-change-type.d.ts +19 -0
  19. package/dist/src/db/migrations/v5b-change-type.d.ts.map +1 -0
  20. package/dist/src/db/migrations/v5b-change-type.js +22 -0
  21. package/dist/src/db/migrations/v5b-change-type.js.map +1 -0
  22. package/dist/src/db/migrations/v6-advisories.d.ts +32 -0
  23. package/dist/src/db/migrations/v6-advisories.d.ts.map +1 -0
  24. package/dist/src/db/migrations/v6-advisories.js +53 -0
  25. package/dist/src/db/migrations/v6-advisories.js.map +1 -0
  26. package/dist/src/db/migrations/v7-compatibility.d.ts +21 -0
  27. package/dist/src/db/migrations/v7-compatibility.d.ts.map +1 -0
  28. package/dist/src/db/migrations/v7-compatibility.js +23 -0
  29. package/dist/src/db/migrations/v7-compatibility.js.map +1 -0
  30. package/dist/src/db/migrations/v8-co-installs.d.ts +21 -0
  31. package/dist/src/db/migrations/v8-co-installs.d.ts.map +1 -0
  32. package/dist/src/db/migrations/v8-co-installs.js +33 -0
  33. package/dist/src/db/migrations/v8-co-installs.js.map +1 -0
  34. package/dist/src/db/schema.d.ts +5 -16
  35. package/dist/src/db/schema.d.ts.map +1 -1
  36. package/dist/src/db/schema.js +37 -22
  37. package/dist/src/db/schema.js.map +1 -1
  38. package/dist/src/exports/repositories.d.ts +5 -0
  39. package/dist/src/exports/repositories.d.ts.map +1 -1
  40. package/dist/src/exports/repositories.js +17 -0
  41. package/dist/src/exports/repositories.js.map +1 -1
  42. package/dist/src/exports/types.d.ts +1 -1
  43. package/dist/src/exports/types.d.ts.map +1 -1
  44. package/dist/src/exports/types.js.map +1 -1
  45. package/dist/src/indexer/SkillParser.d.ts +2 -0
  46. package/dist/src/indexer/SkillParser.d.ts.map +1 -1
  47. package/dist/src/indexer/SkillParser.js.map +1 -1
  48. package/dist/src/learning/PatternStore.d.ts +0 -2
  49. package/dist/src/learning/PatternStore.d.ts.map +1 -1
  50. package/dist/src/learning/PatternStore.helpers.d.ts +19 -1
  51. package/dist/src/learning/PatternStore.helpers.d.ts.map +1 -1
  52. package/dist/src/learning/PatternStore.helpers.js +51 -0
  53. package/dist/src/learning/PatternStore.helpers.js.map +1 -1
  54. package/dist/src/learning/PatternStore.js +5 -37
  55. package/dist/src/learning/PatternStore.js.map +1 -1
  56. package/dist/src/repositories/AdvisoryRepository.d.ts +80 -0
  57. package/dist/src/repositories/AdvisoryRepository.d.ts.map +1 -0
  58. package/dist/src/repositories/AdvisoryRepository.js +128 -0
  59. package/dist/src/repositories/AdvisoryRepository.js.map +1 -0
  60. package/dist/src/repositories/AdvisoryRepository.test.d.ts +6 -0
  61. package/dist/src/repositories/AdvisoryRepository.test.d.ts.map +1 -0
  62. package/dist/src/repositories/AdvisoryRepository.test.js +149 -0
  63. package/dist/src/repositories/AdvisoryRepository.test.js.map +1 -0
  64. package/dist/src/repositories/CoInstallRepository.d.ts +68 -0
  65. package/dist/src/repositories/CoInstallRepository.d.ts.map +1 -0
  66. package/dist/src/repositories/CoInstallRepository.js +122 -0
  67. package/dist/src/repositories/CoInstallRepository.js.map +1 -0
  68. package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
  69. package/dist/src/repositories/SkillRepository.js +16 -5
  70. package/dist/src/repositories/SkillRepository.js.map +1 -1
  71. package/dist/src/services/SearchService.helpers.d.ts.map +1 -1
  72. package/dist/src/services/SearchService.helpers.js +4 -0
  73. package/dist/src/services/SearchService.helpers.js.map +1 -1
  74. package/dist/src/services/SearchService.types.d.ts +1 -0
  75. package/dist/src/services/SearchService.types.d.ts.map +1 -1
  76. package/dist/src/services/TaskRunner.d.ts +5 -81
  77. package/dist/src/services/TaskRunner.d.ts.map +1 -1
  78. package/dist/src/services/TaskRunner.js +7 -68
  79. package/dist/src/services/TaskRunner.js.map +1 -1
  80. package/dist/src/services/TaskRunner.process.d.ts +33 -0
  81. package/dist/src/services/TaskRunner.process.d.ts.map +1 -0
  82. package/dist/src/services/TaskRunner.process.js +70 -0
  83. package/dist/src/services/TaskRunner.process.js.map +1 -0
  84. package/dist/src/services/TaskRunner.types.d.ts +76 -0
  85. package/dist/src/services/TaskRunner.types.d.ts.map +1 -0
  86. package/dist/src/services/TaskRunner.types.js +22 -0
  87. package/dist/src/services/TaskRunner.types.js.map +1 -0
  88. package/dist/src/services/__tests__/TaskRunner.process.test.d.ts +13 -0
  89. package/dist/src/services/__tests__/TaskRunner.process.test.d.ts.map +1 -0
  90. package/dist/src/services/__tests__/TaskRunner.process.test.js +91 -0
  91. package/dist/src/services/__tests__/TaskRunner.process.test.js.map +1 -0
  92. package/dist/src/services/quarantine/QuarantineService.d.ts +3 -24
  93. package/dist/src/services/quarantine/QuarantineService.d.ts.map +1 -1
  94. package/dist/src/services/quarantine/QuarantineService.js +8 -205
  95. package/dist/src/services/quarantine/QuarantineService.js.map +1 -1
  96. package/dist/src/services/quarantine/QuarantineService.multiapproval.d.ts +57 -0
  97. package/dist/src/services/quarantine/QuarantineService.multiapproval.d.ts.map +1 -0
  98. package/dist/src/services/quarantine/QuarantineService.multiapproval.js +211 -0
  99. package/dist/src/services/quarantine/QuarantineService.multiapproval.js.map +1 -0
  100. package/dist/src/session/SessionManager.d.ts +2 -33
  101. package/dist/src/session/SessionManager.d.ts.map +1 -1
  102. package/dist/src/session/SessionManager.js +14 -238
  103. package/dist/src/session/SessionManager.js.map +1 -1
  104. package/dist/src/session/SessionManager.memory.d.ts +67 -0
  105. package/dist/src/session/SessionManager.memory.d.ts.map +1 -0
  106. package/dist/src/session/SessionManager.memory.js +262 -0
  107. package/dist/src/session/SessionManager.memory.js.map +1 -0
  108. package/dist/src/sync/SyncEngine.d.ts +13 -1
  109. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  110. package/dist/src/sync/SyncEngine.js +50 -1
  111. package/dist/src/sync/SyncEngine.js.map +1 -1
  112. package/dist/src/testing/MultiLLMProvider.d.ts +5 -6
  113. package/dist/src/testing/MultiLLMProvider.d.ts.map +1 -1
  114. package/dist/src/testing/MultiLLMProvider.js +18 -130
  115. package/dist/src/testing/MultiLLMProvider.js.map +1 -1
  116. package/dist/src/testing/MultiLLMProvider.metrics.d.ts +33 -0
  117. package/dist/src/testing/MultiLLMProvider.metrics.d.ts.map +1 -0
  118. package/dist/src/testing/MultiLLMProvider.metrics.js +87 -0
  119. package/dist/src/testing/MultiLLMProvider.metrics.js.map +1 -0
  120. package/dist/src/testing/MultiLLMProvider.selection.d.ts +77 -0
  121. package/dist/src/testing/MultiLLMProvider.selection.d.ts.map +1 -0
  122. package/dist/src/testing/MultiLLMProvider.selection.js +151 -0
  123. package/dist/src/testing/MultiLLMProvider.selection.js.map +1 -0
  124. package/dist/src/types/skill.d.ts +10 -0
  125. package/dist/src/types/skill.d.ts.map +1 -1
  126. package/dist/src/types.d.ts +30 -0
  127. package/dist/src/types.d.ts.map +1 -1
  128. package/dist/src/versioning/change-classifier.d.ts +38 -0
  129. package/dist/src/versioning/change-classifier.d.ts.map +1 -0
  130. package/dist/src/versioning/change-classifier.js +187 -0
  131. package/dist/src/versioning/change-classifier.js.map +1 -0
  132. package/dist/src/versioning/change-classifier.test.d.ts +6 -0
  133. package/dist/src/versioning/change-classifier.test.d.ts.map +1 -0
  134. package/dist/src/versioning/change-classifier.test.js +275 -0
  135. package/dist/src/versioning/change-classifier.test.js.map +1 -0
  136. package/dist/src/versioning/update-risk.d.ts +50 -0
  137. package/dist/src/versioning/update-risk.d.ts.map +1 -0
  138. package/dist/src/versioning/update-risk.js +80 -0
  139. package/dist/src/versioning/update-risk.js.map +1 -0
  140. package/dist/src/versioning/update-risk.test.d.ts +6 -0
  141. package/dist/src/versioning/update-risk.test.d.ts.map +1 -0
  142. package/dist/src/versioning/update-risk.test.js +200 -0
  143. package/dist/src/versioning/update-risk.test.js.map +1 -0
  144. package/dist/tests/AuditLogger.edge-cases.test.d.ts +10 -0
  145. package/dist/tests/AuditLogger.edge-cases.test.d.ts.map +1 -0
  146. package/dist/tests/AuditLogger.edge-cases.test.js +183 -0
  147. package/dist/tests/AuditLogger.edge-cases.test.js.map +1 -0
  148. package/dist/tests/CacheManager.test.d.ts +9 -0
  149. package/dist/tests/CacheManager.test.d.ts.map +1 -0
  150. package/dist/tests/CacheManager.test.js +163 -0
  151. package/dist/tests/CacheManager.test.js.map +1 -0
  152. package/dist/tests/GitHubIndexer.edge-cases.test.d.ts +10 -0
  153. package/dist/tests/GitHubIndexer.edge-cases.test.d.ts.map +1 -0
  154. package/dist/tests/GitHubIndexer.edge-cases.test.js +255 -0
  155. package/dist/tests/GitHubIndexer.edge-cases.test.js.map +1 -0
  156. package/dist/tests/SearchService.test.js +71 -0
  157. package/dist/tests/SearchService.test.js.map +1 -1
  158. package/dist/tests/SkillVersionRepository.test.d.ts +0 -11
  159. package/dist/tests/SkillVersionRepository.test.d.ts.map +1 -1
  160. package/dist/tests/SkillVersionRepository.test.js +131 -194
  161. package/dist/tests/SkillVersionRepository.test.js.map +1 -1
  162. package/dist/tests/api/client.health.test.d.ts +15 -0
  163. package/dist/tests/api/client.health.test.d.ts.map +1 -0
  164. package/dist/tests/api/client.health.test.js +111 -0
  165. package/dist/tests/api/client.health.test.js.map +1 -0
  166. package/dist/tests/db/betterSqlite3Driver.test.d.ts +8 -0
  167. package/dist/tests/db/betterSqlite3Driver.test.d.ts.map +1 -0
  168. package/dist/tests/db/betterSqlite3Driver.test.js +88 -0
  169. package/dist/tests/db/betterSqlite3Driver.test.js.map +1 -0
  170. package/dist/tests/db/database-abstraction.test.js +12 -0
  171. package/dist/tests/db/database-abstraction.test.js.map +1 -1
  172. package/dist/tests/db/schema-migrations.test.d.ts +10 -0
  173. package/dist/tests/db/schema-migrations.test.d.ts.map +1 -0
  174. package/dist/tests/db/schema-migrations.test.js +134 -0
  175. package/dist/tests/db/schema-migrations.test.js.map +1 -0
  176. package/dist/tests/db/sqljsDriver.test.d.ts +9 -0
  177. package/dist/tests/db/sqljsDriver.test.d.ts.map +1 -0
  178. package/dist/tests/db/sqljsDriver.test.js +75 -0
  179. package/dist/tests/db/sqljsDriver.test.js.map +1 -0
  180. package/dist/tests/helpers/database.d.ts +32 -0
  181. package/dist/tests/helpers/database.d.ts.map +1 -0
  182. package/dist/tests/helpers/database.js +54 -0
  183. package/dist/tests/helpers/database.js.map +1 -0
  184. package/dist/tests/learning/PatternStore.helpers.test.d.ts +17 -0
  185. package/dist/tests/learning/PatternStore.helpers.test.d.ts.map +1 -0
  186. package/dist/tests/learning/PatternStore.helpers.test.js +301 -0
  187. package/dist/tests/learning/PatternStore.helpers.test.js.map +1 -0
  188. package/dist/tests/repositories/CoInstallRepository.test.d.ts +9 -0
  189. package/dist/tests/repositories/CoInstallRepository.test.d.ts.map +1 -0
  190. package/dist/tests/repositories/CoInstallRepository.test.js +126 -0
  191. package/dist/tests/repositories/CoInstallRepository.test.js.map +1 -0
  192. package/dist/tests/routing/LanguageRouter.test.d.ts +9 -0
  193. package/dist/tests/routing/LanguageRouter.test.d.ts.map +1 -0
  194. package/dist/tests/routing/LanguageRouter.test.js +150 -0
  195. package/dist/tests/routing/LanguageRouter.test.js.map +1 -0
  196. package/dist/tests/session/SessionManager.memory.test.d.ts +11 -0
  197. package/dist/tests/session/SessionManager.memory.test.d.ts.map +1 -0
  198. package/dist/tests/session/SessionManager.memory.test.js +219 -0
  199. package/dist/tests/session/SessionManager.memory.test.js.map +1 -0
  200. package/dist/tests/sync/SyncEngine.test.js +32 -18
  201. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  202. package/dist/tests/testing/MultiLLMProvider.metrics.test.d.ts +13 -0
  203. package/dist/tests/testing/MultiLLMProvider.metrics.test.d.ts.map +1 -0
  204. package/dist/tests/testing/MultiLLMProvider.metrics.test.js +149 -0
  205. package/dist/tests/testing/MultiLLMProvider.metrics.test.js.map +1 -0
  206. package/dist/tests/testing/MultiLLMProvider.selection.test.d.ts +15 -0
  207. package/dist/tests/testing/MultiLLMProvider.selection.test.d.ts.map +1 -0
  208. package/dist/tests/testing/MultiLLMProvider.selection.test.js +249 -0
  209. package/dist/tests/testing/MultiLLMProvider.selection.test.js.map +1 -0
  210. package/dist/tests/unit/quarantine-query-builder.test.d.ts +10 -0
  211. package/dist/tests/unit/quarantine-query-builder.test.d.ts.map +1 -0
  212. package/dist/tests/unit/quarantine-query-builder.test.js +157 -0
  213. package/dist/tests/unit/quarantine-query-builder.test.js.map +1 -0
  214. package/package.json +1 -1
@@ -0,0 +1,301 @@
1
+ /**
2
+ * SMI-2754: PatternStore Helper Functions Tests
3
+ *
4
+ * Tests covering pure math functions, FisherInformationMatrix,
5
+ * and DB-touching helpers extracted to PatternStore.helpers.ts.
6
+ *
7
+ * Categories:
8
+ * - FisherInformationMatrix: construct, update, accumulate, decay,
9
+ * serialize/deserialize round-trip, reset, invalid buffer size
10
+ * - contextToText: empty, partial, full context
11
+ * - computeGradient, cosineSimilarity, importanceWeightedSimilarity (zero-norm edge)
12
+ * - calculatePatternImportance: positive and negative reward
13
+ * - shouldConsolidate: too recent, no patterns, ratio threshold, near max patterns
14
+ * - computeAverageEmbedding: empty table → zero vector, populated → average
15
+ */
16
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
17
+ import { FisherInformationMatrix, contextToText, computeGradient, cosineSimilarity, importanceWeightedSimilarity, calculatePatternImportance, shouldConsolidate, computeAverageEmbedding, PATTERN_STORE_SCHEMA, } from '../../src/learning/PatternStore.helpers.js';
18
+ import { createDatabase } from '../../src/db/createDatabase.js';
19
+ // ============================================================================
20
+ // FisherInformationMatrix
21
+ // ============================================================================
22
+ describe('FisherInformationMatrix', () => {
23
+ it('constructs with zero importance vector', () => {
24
+ const fim = new FisherInformationMatrix(4);
25
+ expect(fim.getImportanceVector()).toEqual(new Float32Array(4));
26
+ expect(fim.getUpdateCount()).toBe(0);
27
+ expect(fim.getAverageImportance()).toBe(0);
28
+ });
29
+ it('updates importance from a gradient', () => {
30
+ const fim = new FisherInformationMatrix(3);
31
+ const gradient = new Float32Array([1.0, 2.0, 0.0]);
32
+ fim.update(gradient);
33
+ expect(fim.getUpdateCount()).toBe(1);
34
+ // importance = runningSum / updateCount = [1, 4, 0] / 1
35
+ expect(fim.getImportance(0)).toBeCloseTo(1.0);
36
+ expect(fim.getImportance(1)).toBeCloseTo(4.0);
37
+ expect(fim.getImportance(2)).toBeCloseTo(0.0);
38
+ });
39
+ it('accumulates importance over multiple updates', () => {
40
+ const fim = new FisherInformationMatrix(2);
41
+ fim.update(new Float32Array([2.0, 0.0]));
42
+ fim.update(new Float32Array([2.0, 0.0]));
43
+ // runningSum = [4+4, 0] = [8, 0]; importance = [8/2, 0/2] = [4, 0]
44
+ expect(fim.getImportance(0)).toBeCloseTo(4.0);
45
+ expect(fim.getUpdateCount()).toBe(2);
46
+ });
47
+ it('decays importance by decayFactor', () => {
48
+ const fim = new FisherInformationMatrix(2);
49
+ fim.update(new Float32Array([2.0, 4.0]));
50
+ // After update: runningSum=[4, 16], importance=[4, 16]
51
+ fim.decay(0.5);
52
+ // runningSum=[2, 8]; importance = [2, 8] / max(1, 1) = [2, 8]
53
+ expect(fim.getImportance(0)).toBeCloseTo(2.0);
54
+ expect(fim.getImportance(1)).toBeCloseTo(8.0);
55
+ });
56
+ it('serialize and deserialize round-trip preserves values', () => {
57
+ const fim = new FisherInformationMatrix(3);
58
+ fim.update(new Float32Array([1.0, 2.0, 3.0]));
59
+ fim.update(new Float32Array([0.5, 1.0, 1.5]));
60
+ const buffer = fim.serialize();
61
+ const fim2 = new FisherInformationMatrix(3);
62
+ fim2.deserialize(buffer);
63
+ expect(fim2.getUpdateCount()).toBe(2);
64
+ // Values should match within float32 precision
65
+ for (let i = 0; i < 3; i++) {
66
+ expect(fim2.getImportance(i)).toBeCloseTo(fim.getImportance(i), 4);
67
+ }
68
+ });
69
+ it('reset clears all values to zero', () => {
70
+ const fim = new FisherInformationMatrix(3);
71
+ fim.update(new Float32Array([1.0, 2.0, 3.0]));
72
+ fim.reset();
73
+ expect(fim.getUpdateCount()).toBe(0);
74
+ expect(fim.getAverageImportance()).toBe(0);
75
+ expect(fim.getImportance(0)).toBe(0);
76
+ });
77
+ it('throws when deserializing buffer that is too small', () => {
78
+ const fim = new FisherInformationMatrix(4);
79
+ const tooSmall = Buffer.alloc(4); // way smaller than needed
80
+ expect(() => fim.deserialize(tooSmall)).toThrow(/Invalid Fisher matrix buffer/);
81
+ });
82
+ });
83
+ // ============================================================================
84
+ // contextToText
85
+ // ============================================================================
86
+ describe('contextToText', () => {
87
+ it('returns "empty context" for an empty context object', () => {
88
+ const ctx = { installedSkills: [] };
89
+ expect(contextToText(ctx)).toBe('empty context');
90
+ });
91
+ it('returns partial text when only some fields are set', () => {
92
+ const ctx = {
93
+ installedSkills: ['anthropic/commit'],
94
+ timeOfDay: 'morning',
95
+ };
96
+ const text = contextToText(ctx);
97
+ expect(text).toContain('installed: anthropic/commit');
98
+ expect(text).toContain('time: morning');
99
+ expect(text).not.toContain('frameworks');
100
+ });
101
+ it('returns all fields when all are set', () => {
102
+ const ctx = {
103
+ installedSkills: ['anthropic/commit', 'community/jest'],
104
+ frameworks: ['react', 'typescript'],
105
+ keywords: ['testing'],
106
+ timeOfDay: 'afternoon',
107
+ dayType: 'weekday',
108
+ };
109
+ const text = contextToText(ctx);
110
+ expect(text).toContain('installed: anthropic/commit, community/jest');
111
+ expect(text).toContain('frameworks: react, typescript');
112
+ expect(text).toContain('keywords: testing');
113
+ expect(text).toContain('time: afternoon');
114
+ expect(text).toContain('day: weekday');
115
+ });
116
+ });
117
+ // ============================================================================
118
+ // computeGradient
119
+ // ============================================================================
120
+ describe('computeGradient', () => {
121
+ it('computes element-wise difference a - b', () => {
122
+ const a = new Float32Array([3.0, 1.0, 2.0]);
123
+ const b = new Float32Array([1.0, 2.0, 1.0]);
124
+ const gradient = computeGradient(a, b);
125
+ expect(gradient[0]).toBeCloseTo(2.0);
126
+ expect(gradient[1]).toBeCloseTo(-1.0);
127
+ expect(gradient[2]).toBeCloseTo(1.0);
128
+ });
129
+ });
130
+ // ============================================================================
131
+ // cosineSimilarity
132
+ // ============================================================================
133
+ describe('cosineSimilarity', () => {
134
+ it('returns 1.0 for identical non-zero vectors', () => {
135
+ const a = new Float32Array([1.0, 0.0, 0.0]);
136
+ expect(cosineSimilarity(a, a)).toBeCloseTo(1.0);
137
+ });
138
+ it('returns 0 for orthogonal vectors', () => {
139
+ const a = new Float32Array([1.0, 0.0]);
140
+ const b = new Float32Array([0.0, 1.0]);
141
+ expect(cosineSimilarity(a, b)).toBeCloseTo(0.0);
142
+ });
143
+ it('returns 0 when either vector is all zeros (zero-norm edge case)', () => {
144
+ const zero = new Float32Array([0.0, 0.0]);
145
+ const a = new Float32Array([1.0, 2.0]);
146
+ expect(cosineSimilarity(zero, a)).toBe(0);
147
+ expect(cosineSimilarity(a, zero)).toBe(0);
148
+ });
149
+ });
150
+ // ============================================================================
151
+ // importanceWeightedSimilarity
152
+ // ============================================================================
153
+ describe('importanceWeightedSimilarity', () => {
154
+ it('computes weighted similarity between two identical vectors', () => {
155
+ const a = new Float32Array([1.0, 0.0]);
156
+ const importance = new Float32Array([1.0, 0.0]);
157
+ const result = importanceWeightedSimilarity(a, a, importance);
158
+ expect(result).toBeCloseTo(1.0);
159
+ });
160
+ it('returns 0 when the weighted norms are zero (zero-norm edge case)', () => {
161
+ const zero = new Float32Array([0.0, 0.0]);
162
+ const importance = new Float32Array([1.0, 1.0]);
163
+ const result = importanceWeightedSimilarity(zero, zero, importance);
164
+ expect(result).toBe(0);
165
+ });
166
+ });
167
+ // ============================================================================
168
+ // calculatePatternImportance
169
+ // ============================================================================
170
+ function makeStoredPattern(reward) {
171
+ return {
172
+ id: 'test-id',
173
+ context: { installedSkills: [] },
174
+ skill: {
175
+ skillId: 'community/test',
176
+ category: 'testing',
177
+ trustTier: 'community',
178
+ keywords: [],
179
+ qualityScore: 80,
180
+ installCount: 100,
181
+ },
182
+ originalScore: 0.8,
183
+ source: 'recommend',
184
+ contextEmbedding: new Float32Array([0.1, 0.2]),
185
+ outcome: { type: reward >= 0 ? 'accept' : 'dismiss', reward },
186
+ importance: 0.5,
187
+ accessCount: 3,
188
+ createdAt: new Date(),
189
+ lastAccessedAt: new Date(),
190
+ };
191
+ }
192
+ describe('calculatePatternImportance', () => {
193
+ it('gives higher importance for positive reward (1.5x multiplier)', () => {
194
+ const pattern = makeStoredPattern(1.0);
195
+ const outcome = { type: 'accept', reward: 1.0 };
196
+ const importance = calculatePatternImportance(pattern, outcome);
197
+ // baseImportance = 1.0 * 1.5 = 1.5, times other factors
198
+ expect(importance).toBeGreaterThan(0);
199
+ });
200
+ it('gives lower importance for negative reward (no 1.5x multiplier)', () => {
201
+ const positivePattern = makeStoredPattern(1.0);
202
+ const negativePattern = makeStoredPattern(-0.5);
203
+ const positiveOutcome = { type: 'accept', reward: 1.0 };
204
+ const negativeOutcome = { type: 'dismiss', reward: -0.5 };
205
+ const importancePos = calculatePatternImportance(positivePattern, positiveOutcome);
206
+ const importanceNeg = calculatePatternImportance(negativePattern, negativeOutcome);
207
+ // positive reward has 1.5x boost, so its importance should be higher
208
+ expect(importancePos).toBeGreaterThan(importanceNeg);
209
+ });
210
+ });
211
+ // ============================================================================
212
+ // shouldConsolidate
213
+ // ============================================================================
214
+ function makeConsolidationState(overrides = {}) {
215
+ return {
216
+ totalPatterns: 100,
217
+ patternsSinceLastConsolidation: 5,
218
+ lastConsolidation: null,
219
+ ...overrides,
220
+ };
221
+ }
222
+ const ewcConfig = {
223
+ lambda: 5.0,
224
+ fisherDecay: 0.95,
225
+ importanceThreshold: 0.01,
226
+ fisherSampleSize: 100,
227
+ consolidationThreshold: 0.1,
228
+ maxPatterns: 1000,
229
+ };
230
+ describe('shouldConsolidate', () => {
231
+ it('returns false when last consolidation was less than 1 hour ago', () => {
232
+ const state = makeConsolidationState({
233
+ lastConsolidation: new Date(Date.now() - 30 * 60 * 1000), // 30 min ago
234
+ totalPatterns: 200,
235
+ patternsSinceLastConsolidation: 50,
236
+ });
237
+ expect(shouldConsolidate(state, ewcConfig)).toBe(false);
238
+ });
239
+ it('returns false when totalPatterns is 0', () => {
240
+ const state = makeConsolidationState({ totalPatterns: 0, patternsSinceLastConsolidation: 0 });
241
+ expect(shouldConsolidate(state, ewcConfig)).toBe(false);
242
+ });
243
+ it('returns true when newPatternsRatio exceeds consolidationThreshold', () => {
244
+ // 20/100 = 0.2 > 0.1 threshold
245
+ const state = makeConsolidationState({
246
+ totalPatterns: 100,
247
+ patternsSinceLastConsolidation: 20,
248
+ lastConsolidation: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
249
+ });
250
+ expect(shouldConsolidate(state, ewcConfig)).toBe(true);
251
+ });
252
+ it('returns true when totalPatterns > 90% of maxPatterns', () => {
253
+ const state = makeConsolidationState({
254
+ totalPatterns: 950, // > 1000 * 0.9 = 900
255
+ patternsSinceLastConsolidation: 5, // ratio = 5/950 < 0.1
256
+ lastConsolidation: new Date(Date.now() - 2 * 60 * 60 * 1000),
257
+ });
258
+ expect(shouldConsolidate(state, ewcConfig)).toBe(true);
259
+ });
260
+ });
261
+ // ============================================================================
262
+ // computeAverageEmbedding (requires in-memory SQLite)
263
+ // ============================================================================
264
+ describe('computeAverageEmbedding', () => {
265
+ let db;
266
+ beforeEach(() => {
267
+ db = createDatabase(':memory:');
268
+ db.exec(PATTERN_STORE_SCHEMA);
269
+ });
270
+ afterEach(() => {
271
+ db.close();
272
+ });
273
+ it('returns zero vector when the patterns table is empty', async () => {
274
+ const result = await computeAverageEmbedding(db, 100, 4);
275
+ expect(result).toEqual(new Float32Array(4));
276
+ });
277
+ it('returns the average of all stored embeddings', async () => {
278
+ const dims = 4;
279
+ // Store two embeddings: [1,2,3,4] and [3,4,5,6]
280
+ // Expected average: [2,3,4,5]
281
+ const emb1 = new Float32Array([1, 2, 3, 4]);
282
+ const emb2 = new Float32Array([3, 4, 5, 6]);
283
+ const buf1 = Buffer.from(emb1.buffer);
284
+ const buf2 = Buffer.from(emb2.buffer);
285
+ const insertStmt = db.prepare(`
286
+ INSERT INTO patterns (
287
+ pattern_id, context_embedding, skill_id, skill_features, context_data,
288
+ outcome_type, outcome_reward, importance, original_score, source,
289
+ access_count, created_at, last_accessed_at
290
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch(), unixepoch())
291
+ `);
292
+ insertStmt.run('p1', buf1, 'skill1', '{}', '{}', 'accept', 1.0, 0.5, 0.8, 'recommend', 0);
293
+ insertStmt.run('p2', buf2, 'skill2', '{}', '{}', 'accept', 1.0, 0.5, 0.8, 'recommend', 0);
294
+ const result = await computeAverageEmbedding(db, 100, dims);
295
+ expect(result[0]).toBeCloseTo(2.0);
296
+ expect(result[1]).toBeCloseTo(3.0);
297
+ expect(result[2]).toBeCloseTo(4.0);
298
+ expect(result[3]).toBeCloseTo(5.0);
299
+ });
300
+ });
301
+ //# sourceMappingURL=PatternStore.helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PatternStore.helpers.test.js","sourceRoot":"","sources":["../../../tests/learning/PatternStore.helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Tests for CoInstallRepository (SMI-2761)
3
+ * @module @skillsmith/core/tests/repositories/CoInstallRepository
4
+ *
5
+ * Tests co-install recording, upsert symmetry, and min-count threshold.
6
+ * Uses createTestDatabase() which runs all migrations including v8.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=CoInstallRepository.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CoInstallRepository.test.d.ts","sourceRoot":"","sources":["../../../tests/repositories/CoInstallRepository.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @fileoverview Tests for CoInstallRepository (SMI-2761)
3
+ * @module @skillsmith/core/tests/repositories/CoInstallRepository
4
+ *
5
+ * Tests co-install recording, upsert symmetry, and min-count threshold.
6
+ * Uses createTestDatabase() which runs all migrations including v8.
7
+ */
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import { createTestDatabase, closeDatabase } from '../helpers/database.js';
10
+ import { CoInstallRepository } from '../../src/repositories/CoInstallRepository.js';
11
+ let db;
12
+ let repo;
13
+ beforeEach(() => {
14
+ db = createTestDatabase();
15
+ repo = new CoInstallRepository(db);
16
+ // Insert fixture skills so JOIN works in getTopCoInstalls
17
+ db.exec(`
18
+ INSERT INTO skills (id, name, author, description)
19
+ VALUES
20
+ ('skill-a', 'skill-a', 'test', 'Skill A description'),
21
+ ('skill-b', 'skill-b', 'test', 'Skill B description'),
22
+ ('skill-c', 'skill-c', 'test', 'Skill C description');
23
+ `);
24
+ });
25
+ afterEach(() => {
26
+ closeDatabase(db);
27
+ });
28
+ describe('CoInstallRepository', () => {
29
+ describe('getTopCoInstalls — empty table', () => {
30
+ it('returns empty array when no co-installs recorded', () => {
31
+ const results = repo.getTopCoInstalls('skill-a');
32
+ expect(results).toEqual([]);
33
+ });
34
+ });
35
+ describe('recordCoInstall — upsert symmetry', () => {
36
+ it('creates (A,B) and (B,A) rows on first call', () => {
37
+ repo.recordCoInstall('skill-a', 'skill-b');
38
+ const forA = repo.getTopCoInstalls('skill-a', 10, 1);
39
+ const forB = repo.getTopCoInstalls('skill-b', 10, 1);
40
+ expect(forA.map((r) => r.skillId)).toContain('skill-b');
41
+ expect(forB.map((r) => r.skillId)).toContain('skill-a');
42
+ });
43
+ it('increments install_count on repeated calls', () => {
44
+ repo.recordCoInstall('skill-a', 'skill-b');
45
+ repo.recordCoInstall('skill-a', 'skill-b');
46
+ repo.recordCoInstall('skill-a', 'skill-b');
47
+ const results = repo.getTopCoInstalls('skill-a', 10, 1);
48
+ const entry = results.find((r) => r.skillId === 'skill-b');
49
+ expect(entry?.installCount).toBe(3);
50
+ });
51
+ it('is idempotent for self-pairs (no-op)', () => {
52
+ // Should not throw, simply return
53
+ expect(() => repo.recordCoInstall('skill-a', 'skill-a')).not.toThrow();
54
+ const results = repo.getTopCoInstalls('skill-a', 10, 1);
55
+ expect(results).toEqual([]);
56
+ });
57
+ });
58
+ describe('recordSessionCoInstalls', () => {
59
+ it('records all pairs in a session', () => {
60
+ repo.recordSessionCoInstalls(['skill-a', 'skill-b', 'skill-c']);
61
+ const forA = repo.getTopCoInstalls('skill-a', 10, 1);
62
+ const ids = forA.map((r) => r.skillId);
63
+ expect(ids).toContain('skill-b');
64
+ expect(ids).toContain('skill-c');
65
+ });
66
+ it('is a no-op for fewer than 2 skills', () => {
67
+ expect(() => repo.recordSessionCoInstalls([])).not.toThrow();
68
+ expect(() => repo.recordSessionCoInstalls(['skill-a'])).not.toThrow();
69
+ expect(repo.getTopCoInstalls('skill-a', 10, 1)).toEqual([]);
70
+ });
71
+ });
72
+ describe('getTopCoInstalls — min-count boundary', () => {
73
+ it('excludes entries below minCount (4 → excluded at default threshold 5)', () => {
74
+ // Record 4 co-installs
75
+ for (let i = 0; i < 4; i++) {
76
+ repo.recordCoInstall('skill-a', 'skill-b');
77
+ }
78
+ const results = repo.getTopCoInstalls('skill-a'); // default minCount=5
79
+ expect(results.find((r) => r.skillId === 'skill-b')).toBeUndefined();
80
+ });
81
+ it('includes entries at exactly minCount (5 → included at default threshold 5)', () => {
82
+ // Record exactly 5 co-installs
83
+ for (let i = 0; i < 5; i++) {
84
+ repo.recordCoInstall('skill-a', 'skill-b');
85
+ }
86
+ const results = repo.getTopCoInstalls('skill-a'); // default minCount=5
87
+ expect(results.find((r) => r.skillId === 'skill-b')).toBeDefined();
88
+ });
89
+ });
90
+ describe('getTopCoInstalls — result shape', () => {
91
+ it('returns skills with name/description/author from skills table', () => {
92
+ for (let i = 0; i < 5; i++) {
93
+ repo.recordCoInstall('skill-a', 'skill-b');
94
+ }
95
+ const results = repo.getTopCoInstalls('skill-a');
96
+ expect(results).toHaveLength(1);
97
+ expect(results[0]).toMatchObject({
98
+ skillId: 'skill-b',
99
+ name: 'skill-b',
100
+ description: 'Skill B description',
101
+ author: 'test',
102
+ installCount: 5,
103
+ });
104
+ });
105
+ it('respects the limit parameter', () => {
106
+ for (let i = 0; i < 5; i++) {
107
+ repo.recordCoInstall('skill-a', 'skill-b');
108
+ repo.recordCoInstall('skill-a', 'skill-c');
109
+ }
110
+ const results = repo.getTopCoInstalls('skill-a', 1); // limit=1
111
+ expect(results).toHaveLength(1);
112
+ });
113
+ it('orders results by install_count descending', () => {
114
+ for (let i = 0; i < 10; i++)
115
+ repo.recordCoInstall('skill-a', 'skill-b');
116
+ for (let i = 0; i < 5; i++)
117
+ repo.recordCoInstall('skill-a', 'skill-c');
118
+ const results = repo.getTopCoInstalls('skill-a', 10, 1);
119
+ expect(results[0].skillId).toBe('skill-b');
120
+ expect(results[0].installCount).toBe(10);
121
+ expect(results[1].skillId).toBe('skill-c');
122
+ expect(results[1].installCount).toBe(5);
123
+ });
124
+ });
125
+ });
126
+ //# sourceMappingURL=CoInstallRepository.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CoInstallRepository.test.js","sourceRoot":"","sources":["../../../tests/repositories/CoInstallRepository.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SMI-2756: Wave 3 — LanguageRouter tests
3
+ *
4
+ * Tests the LanguageRouter dispatch layer: adapter registration,
5
+ * extension-based lookup, tryGetAdapter, canHandle, getLanguage,
6
+ * getSupportedLanguages, framework rules aggregation, and dispose.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=LanguageRouter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LanguageRouter.test.d.ts","sourceRoot":"","sources":["../../../tests/routing/LanguageRouter.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * SMI-2756: Wave 3 — LanguageRouter tests
3
+ *
4
+ * Tests the LanguageRouter dispatch layer: adapter registration,
5
+ * extension-based lookup, tryGetAdapter, canHandle, getLanguage,
6
+ * getSupportedLanguages, framework rules aggregation, and dispose.
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { LanguageRouter } from '../../src/analysis/router.js';
10
+ describe('LanguageRouter', () => {
11
+ // -------------------------------------------------------------------------
12
+ // createWithAllAdapters factory
13
+ // -------------------------------------------------------------------------
14
+ describe('createWithAllAdapters', () => {
15
+ it('creates a router with multiple adapters pre-registered', () => {
16
+ const router = LanguageRouter.createWithAllAdapters();
17
+ expect(router.adapterCount).toBeGreaterThan(0);
18
+ });
19
+ it('creates a router with only specified languages', () => {
20
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript'] });
21
+ expect(router.getSupportedLanguages()).toContain('typescript');
22
+ // Should have fewer adapters than the full set
23
+ const fullRouter = LanguageRouter.createWithAllAdapters();
24
+ expect(router.adapterCount).toBeLessThanOrEqual(fullRouter.adapterCount);
25
+ });
26
+ });
27
+ // -------------------------------------------------------------------------
28
+ // registerAdapter / unregisterAdapter
29
+ // -------------------------------------------------------------------------
30
+ describe('registerAdapter / unregisterAdapter', () => {
31
+ it('registers an adapter and makes its extension routable', () => {
32
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript'] });
33
+ expect(router.canHandle('main.ts')).toBe(true);
34
+ });
35
+ it('unregisterAdapter removes extension mappings', () => {
36
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript'] });
37
+ expect(router.canHandle('main.ts')).toBe(true);
38
+ const removed = router.unregisterAdapter('typescript');
39
+ expect(removed).toBe(true);
40
+ expect(router.canHandle('main.ts')).toBe(false);
41
+ });
42
+ it('unregisterAdapter returns false for unknown language', () => {
43
+ const router = new LanguageRouter();
44
+ const result = router.unregisterAdapter('typescript');
45
+ expect(result).toBe(false);
46
+ });
47
+ it('re-registering an adapter replaces the old one', () => {
48
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript'] });
49
+ const before = router.adapterCount;
50
+ // Re-register the same language
51
+ const tsAdapter = router.getAdapterByLanguage('typescript');
52
+ router.registerAdapter(tsAdapter);
53
+ // Count should not grow
54
+ expect(router.adapterCount).toBe(before);
55
+ });
56
+ });
57
+ // -------------------------------------------------------------------------
58
+ // getAdapter / tryGetAdapter / canHandle / getLanguage
59
+ // -------------------------------------------------------------------------
60
+ describe('getAdapter', () => {
61
+ it('returns adapter for a known extension', () => {
62
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript'] });
63
+ const adapter = router.tryGetAdapter('main.ts');
64
+ expect(adapter).not.toBeNull();
65
+ expect(adapter?.language).toBe('typescript');
66
+ });
67
+ it('throws for unknown extension', () => {
68
+ const router = new LanguageRouter();
69
+ expect(() => router.getAdapter('unknown.xyz')).toThrow(/No adapter registered/);
70
+ });
71
+ it('tryGetAdapter returns null for unknown extension', () => {
72
+ const router = new LanguageRouter();
73
+ expect(router.tryGetAdapter('unknown.xyz')).toBeNull();
74
+ });
75
+ });
76
+ describe('canHandle', () => {
77
+ it('returns true for registered extension', () => {
78
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['python'] });
79
+ expect(router.canHandle('app.py')).toBe(true);
80
+ });
81
+ it('returns false for unregistered extension', () => {
82
+ const router = new LanguageRouter();
83
+ expect(router.canHandle('app.cobol')).toBe(false);
84
+ });
85
+ });
86
+ describe('getLanguage', () => {
87
+ it('returns language for a known file path', () => {
88
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['python'] });
89
+ expect(router.getLanguage('script.py')).toBe('python');
90
+ });
91
+ it('returns null for unsupported extension', () => {
92
+ const router = new LanguageRouter();
93
+ expect(router.getLanguage('file.cobol')).toBeNull();
94
+ });
95
+ });
96
+ // -------------------------------------------------------------------------
97
+ // getSupportedLanguages / getSupportedExtensions
98
+ // -------------------------------------------------------------------------
99
+ describe('getSupportedLanguages / getSupportedExtensions', () => {
100
+ it('getSupportedLanguages returns registered language keys', () => {
101
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript', 'python'] });
102
+ const langs = router.getSupportedLanguages();
103
+ expect(langs).toContain('typescript');
104
+ expect(langs).toContain('python');
105
+ });
106
+ it('getSupportedExtensions includes dot-prefixed extensions', () => {
107
+ const router = LanguageRouter.createWithAllAdapters({ languages: ['typescript'] });
108
+ const exts = router.getSupportedExtensions();
109
+ expect(exts.some((e) => e.startsWith('.'))).toBe(true);
110
+ });
111
+ });
112
+ // -------------------------------------------------------------------------
113
+ // getAllFrameworkRules / getFrameworkRules
114
+ // -------------------------------------------------------------------------
115
+ describe('framework rules', () => {
116
+ it('getAllFrameworkRules returns an array', () => {
117
+ const router = LanguageRouter.createWithAllAdapters();
118
+ const rules = router.getAllFrameworkRules();
119
+ expect(Array.isArray(rules)).toBe(true);
120
+ });
121
+ it('getFrameworkRules returns empty array for unregistered language', () => {
122
+ const router = new LanguageRouter();
123
+ const rules = router.getFrameworkRules('typescript');
124
+ expect(rules).toEqual([]);
125
+ });
126
+ });
127
+ // -------------------------------------------------------------------------
128
+ // dispose
129
+ // -------------------------------------------------------------------------
130
+ describe('dispose', () => {
131
+ it('clears adapter registry after dispose', () => {
132
+ const router = LanguageRouter.createWithAllAdapters();
133
+ expect(router.adapterCount).toBeGreaterThan(0);
134
+ router.dispose();
135
+ expect(router.adapterCount).toBe(0);
136
+ });
137
+ });
138
+ // -------------------------------------------------------------------------
139
+ // detectLanguageFromContent (disabled)
140
+ // -------------------------------------------------------------------------
141
+ describe('detectLanguageFromContent', () => {
142
+ it('returns language:null and method:none when detection is disabled', () => {
143
+ const router = new LanguageRouter({ enableLanguageDetection: false });
144
+ const result = router.detectLanguageFromContent('#!/usr/bin/env python3\nprint("hello")');
145
+ expect(result.language).toBeNull();
146
+ expect(result.method).toBe('none');
147
+ });
148
+ });
149
+ });
150
+ //# sourceMappingURL=LanguageRouter.test.js.map