@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,149 @@
1
+ /**
2
+ * SMI-2754: MultiLLMProvider Metrics Tests
3
+ *
4
+ * Tests for aggregateMetrics and runCompatibilityTest:
5
+ * - aggregateMetrics with populated map
6
+ * - aggregateMetrics with empty map (providerCount === 0 branch)
7
+ * - runCompatibilityTest with all providers succeeding
8
+ * - runCompatibilityTest with one provider throwing
9
+ * - runCompatibilityTest result filtering (score >= 0.7)
10
+ * - runCompatibilityTest with no providers
11
+ */
12
+ import { describe, it, expect, vi } from 'vitest';
13
+ import { aggregateMetrics, runCompatibilityTest, } from '../../src/testing/MultiLLMProvider.metrics.js';
14
+ function makeMetrics(provider, overrides = {}) {
15
+ return {
16
+ provider,
17
+ timestamp: new Date(),
18
+ avgLatencyMs: 100,
19
+ errorRate: 0,
20
+ successRate: 1,
21
+ load: 0,
22
+ totalCost: 0.5,
23
+ totalRequests: 10,
24
+ availability: 1,
25
+ ...overrides,
26
+ };
27
+ }
28
+ function makeLLMResponse(provider, latencyMs = 200) {
29
+ return {
30
+ content: 'Test response content',
31
+ provider,
32
+ model: 'test-model',
33
+ usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
34
+ cost: 0.001,
35
+ latencyMs,
36
+ usedFallback: false,
37
+ };
38
+ }
39
+ // ============================================================================
40
+ // aggregateMetrics
41
+ // ============================================================================
42
+ describe('aggregateMetrics', () => {
43
+ it('aggregates totals correctly from a populated map', () => {
44
+ const metricsMap = new Map([
45
+ [
46
+ 'anthropic',
47
+ makeMetrics('anthropic', {
48
+ totalRequests: 10,
49
+ totalCost: 1.0,
50
+ avgLatencyMs: 200,
51
+ successRate: 0.9,
52
+ }),
53
+ ],
54
+ [
55
+ 'openai',
56
+ makeMetrics('openai', {
57
+ totalRequests: 5,
58
+ totalCost: 0.5,
59
+ avgLatencyMs: 100,
60
+ successRate: 0.8,
61
+ }),
62
+ ],
63
+ ]);
64
+ const result = aggregateMetrics(metricsMap);
65
+ expect(result.totalRequests).toBe(15);
66
+ expect(result.totalCost).toBeCloseTo(1.5);
67
+ // weighted avg: (200*10 + 100*5) / 15 = 2500/15 ≈ 166.67
68
+ expect(result.avgLatencyMs).toBeCloseTo(166.67, 1);
69
+ // avg success rate: (0.9 + 0.8) / 2 = 0.85
70
+ expect(result.avgSuccessRate).toBeCloseTo(0.85);
71
+ expect(result.providerBreakdown.anthropic).toBe(10);
72
+ expect(result.providerBreakdown.openai).toBe(5);
73
+ });
74
+ it('returns zero values when map is empty (providerCount === 0)', () => {
75
+ const metricsMap = new Map();
76
+ const result = aggregateMetrics(metricsMap);
77
+ expect(result.totalRequests).toBe(0);
78
+ expect(result.totalCost).toBe(0);
79
+ expect(result.avgLatencyMs).toBe(0);
80
+ expect(result.avgSuccessRate).toBe(0);
81
+ });
82
+ });
83
+ // ============================================================================
84
+ // runCompatibilityTest
85
+ // ============================================================================
86
+ describe('runCompatibilityTest', () => {
87
+ it('records compatible=true with positive score when all providers succeed', async () => {
88
+ const completer = vi
89
+ .fn()
90
+ .mockImplementation(async (req) => makeLLMResponse(req.provider, 100));
91
+ const result = await runCompatibilityTest('community/jest-helper', ['anthropic', 'openai'], completer);
92
+ expect(result.skillId).toBe('community/jest-helper');
93
+ expect(result.results.anthropic.compatible).toBe(true);
94
+ expect(result.results.anthropic.score).toBeGreaterThan(0);
95
+ expect(result.results.openai.compatible).toBe(true);
96
+ expect(result.overallScore).toBeGreaterThan(0);
97
+ });
98
+ it('records compatible=false with score 0 when a provider throws', async () => {
99
+ const completer = vi.fn().mockImplementation(async (req) => {
100
+ if (req.provider === 'openai') {
101
+ throw new Error('Provider unavailable');
102
+ }
103
+ return makeLLMResponse(req.provider, 200);
104
+ });
105
+ const result = await runCompatibilityTest('community/jest-helper', ['anthropic', 'openai'], completer);
106
+ expect(result.results.anthropic.compatible).toBe(true);
107
+ expect(result.results.openai.compatible).toBe(false);
108
+ expect(result.results.openai.score).toBe(0);
109
+ expect(result.results.openai.error).toBe('Provider unavailable');
110
+ });
111
+ it('includes only providers with score >= 0.7 in recommendedProviders', async () => {
112
+ const completer = vi.fn().mockImplementation(async (req) => {
113
+ // Low latency → high score (1 - 50/5000 + 0.3 = 1.29 → clamped to 1.0)
114
+ if (req.provider === 'anthropic') {
115
+ return makeLLMResponse('anthropic', 50);
116
+ }
117
+ // Very high latency → score of 0.3 (1 - 4900/5000 + 0.3 = 0.32)
118
+ return makeLLMResponse('openai', 4900);
119
+ });
120
+ const result = await runCompatibilityTest('community/test-skill', ['anthropic', 'openai'], completer);
121
+ // anthropic has a high score (>= 0.7); openai has score ~0.32 (< 0.7)
122
+ expect(result.recommendedProviders).toContain('anthropic');
123
+ expect(result.recommendedProviders).not.toContain('openai');
124
+ });
125
+ it('returns overallScore 0 and empty recommendedProviders when no providers given', async () => {
126
+ const completer = vi.fn();
127
+ const result = await runCompatibilityTest('skill/empty', [], completer);
128
+ expect(result.overallScore).toBe(0);
129
+ expect(result.recommendedProviders).toHaveLength(0);
130
+ expect(completer).not.toHaveBeenCalled();
131
+ });
132
+ it('sets testedAt to a Date close to now', async () => {
133
+ const before = new Date();
134
+ const completer = vi.fn().mockResolvedValue(makeLLMResponse('anthropic', 100));
135
+ const result = await runCompatibilityTest('skill/time-check', ['anthropic'], completer);
136
+ const after = new Date();
137
+ expect(result.testedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
138
+ expect(result.testedAt.getTime()).toBeLessThanOrEqual(after.getTime() + 5);
139
+ });
140
+ it('handles non-Error throws by recording "Unknown error"', async () => {
141
+ const completer = vi.fn().mockImplementationOnce(() => {
142
+ throw 'string error thrown'; // non-Error object
143
+ });
144
+ const result = await runCompatibilityTest('skill/string-throw', ['anthropic'], completer);
145
+ expect(result.results.anthropic.compatible).toBe(false);
146
+ expect(result.results.anthropic.error).toBe('Unknown error');
147
+ });
148
+ });
149
+ //# sourceMappingURL=MultiLLMProvider.metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiLLMProvider.metrics.test.js","sourceRoot":"","sources":["../../../tests/testing/MultiLLMProvider.metrics.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SMI-2754: MultiLLMProvider Selection Strategy Tests
3
+ *
4
+ * Tests for selectProvider and getFallbackProvider covering all uncovered branches:
5
+ * - cost-constraint branch
6
+ * - least-loaded strategy
7
+ * - latency-based strategy
8
+ * - cost-based strategy
9
+ * - default-provider fallback
10
+ * - first-available fallback
11
+ * - getFallbackProvider: no matching rule
12
+ * - getFallbackProvider: matching rule with excluded current provider
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=MultiLLMProvider.selection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiLLMProvider.selection.test.d.ts","sourceRoot":"","sources":["../../../tests/testing/MultiLLMProvider.selection.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,249 @@
1
+ /**
2
+ * SMI-2754: MultiLLMProvider Selection Strategy Tests
3
+ *
4
+ * Tests for selectProvider and getFallbackProvider covering all uncovered branches:
5
+ * - cost-constraint branch
6
+ * - least-loaded strategy
7
+ * - latency-based strategy
8
+ * - cost-based strategy
9
+ * - default-provider fallback
10
+ * - first-available fallback
11
+ * - getFallbackProvider: no matching rule
12
+ * - getFallbackProvider: matching rule with excluded current provider
13
+ */
14
+ import { describe, it, expect } from 'vitest';
15
+ import { selectProvider, getFallbackProvider, selectRoundRobin, selectLeastLoaded, selectByLatency, selectByCost, } from '../../src/testing/MultiLLMProvider.selection.js';
16
+ // ============================================================================
17
+ // Shared test fixtures
18
+ // ============================================================================
19
+ function makeConfig(overrides = {}) {
20
+ return {
21
+ defaultProvider: 'anthropic',
22
+ providers: {
23
+ anthropic: {
24
+ provider: 'anthropic',
25
+ model: 'claude-sonnet',
26
+ enabled: true,
27
+ priority: 'quality',
28
+ costPerInputToken: 0.003,
29
+ costPerOutputToken: 0.015,
30
+ },
31
+ openai: {
32
+ provider: 'openai',
33
+ model: 'gpt-4',
34
+ enabled: true,
35
+ priority: 'speed',
36
+ costPerInputToken: 0.01,
37
+ costPerOutputToken: 0.03,
38
+ },
39
+ google: {
40
+ provider: 'google',
41
+ model: 'gemini-pro',
42
+ enabled: true,
43
+ priority: 'cost',
44
+ costPerInputToken: 0.00025,
45
+ costPerOutputToken: 0.0005,
46
+ },
47
+ },
48
+ loadBalancing: { enabled: false, strategy: 'round-robin' },
49
+ costOptimization: { enabled: false },
50
+ circuitBreaker: {
51
+ timeoutMs: 30000,
52
+ errorThresholdPercentage: 50,
53
+ resetTimeoutMs: 60000,
54
+ volumeThreshold: 10,
55
+ },
56
+ enableMetrics: false,
57
+ useV3Integration: false,
58
+ fallbackStrategy: {
59
+ name: 'default',
60
+ enabled: true,
61
+ maxAttempts: 3,
62
+ rules: [],
63
+ },
64
+ ...overrides,
65
+ };
66
+ }
67
+ function makeStatus(currentLoad = 0) {
68
+ return {
69
+ available: true,
70
+ currentLoad,
71
+ queueLength: 0,
72
+ activeRequests: 0,
73
+ circuitState: 'closed',
74
+ };
75
+ }
76
+ function makeMetrics(avgLatencyMs = 100) {
77
+ return {
78
+ provider: 'anthropic',
79
+ timestamp: new Date(),
80
+ avgLatencyMs,
81
+ errorRate: 0,
82
+ successRate: 1,
83
+ load: 0,
84
+ totalCost: 0,
85
+ totalRequests: 10,
86
+ availability: 1,
87
+ };
88
+ }
89
+ // ============================================================================
90
+ // selectRoundRobin
91
+ // ============================================================================
92
+ describe('selectRoundRobin', () => {
93
+ it('selects provider at roundRobinIndex % providers.length and increments index', () => {
94
+ const providers = ['anthropic', 'openai', 'google'];
95
+ const result = selectRoundRobin(providers, 1);
96
+ expect(result.provider).toBe('openai');
97
+ expect(result.nextIndex).toBe(2);
98
+ });
99
+ it('wraps around using modulo', () => {
100
+ const providers = ['anthropic', 'openai'];
101
+ const result = selectRoundRobin(providers, 4);
102
+ expect(result.provider).toBe('anthropic'); // 4 % 2 = 0
103
+ });
104
+ });
105
+ // ============================================================================
106
+ // selectLeastLoaded
107
+ // ============================================================================
108
+ describe('selectLeastLoaded', () => {
109
+ it('selects the provider with the lowest currentLoad', () => {
110
+ const providers = ['anthropic', 'openai', 'google'];
111
+ const loads = { anthropic: 0.8, openai: 0.2, google: 0.5 };
112
+ const getStatus = (p) => makeStatus(loads[p]);
113
+ expect(selectLeastLoaded(providers, getStatus)).toBe('openai');
114
+ });
115
+ });
116
+ // ============================================================================
117
+ // selectByLatency
118
+ // ============================================================================
119
+ describe('selectByLatency', () => {
120
+ it('selects the provider with the lowest avgLatencyMs', () => {
121
+ const providers = ['anthropic', 'openai', 'google'];
122
+ const latencies = { anthropic: 300, openai: 100, google: 200 };
123
+ const getMetrics = (p) => makeMetrics(latencies[p] ?? Infinity);
124
+ expect(selectByLatency(providers, getMetrics)).toBe('openai');
125
+ });
126
+ it('treats missing metrics as Infinity latency', () => {
127
+ const providers = ['anthropic', 'openai'];
128
+ const getMetrics = (p) => (p === 'openai' ? makeMetrics(50) : undefined);
129
+ expect(selectByLatency(providers, getMetrics)).toBe('openai');
130
+ });
131
+ });
132
+ // ============================================================================
133
+ // selectByCost
134
+ // ============================================================================
135
+ describe('selectByCost', () => {
136
+ it('selects the provider with the lowest combined cost per token', () => {
137
+ const providers = ['anthropic', 'openai', 'google'];
138
+ const config = makeConfig();
139
+ // google has 0.00025 + 0.0005 = 0.00075 (cheapest)
140
+ expect(selectByCost(providers, config.providers)).toBe('google');
141
+ });
142
+ });
143
+ // ============================================================================
144
+ // selectProvider
145
+ // ============================================================================
146
+ describe('selectProvider', () => {
147
+ const available = ['anthropic', 'openai', 'google'];
148
+ const getStatus = (p) => makeStatus(p === 'openai' ? 0.1 : 0.9);
149
+ const getMetrics = (p) => makeMetrics(p === 'google' ? 50 : 300);
150
+ it('throws when available list is empty', () => {
151
+ const config = makeConfig();
152
+ expect(() => selectProvider({ messages: [] }, [], config, 0, getStatus, getMetrics)).toThrow('No providers available');
153
+ });
154
+ it('uses cost constraint branch when costConstraints.maxCost is set and costOptimization is enabled', () => {
155
+ const config = makeConfig({ costOptimization: { enabled: true } });
156
+ const result = selectProvider({ messages: [], costConstraints: { maxCost: 0.1 } }, available, config, 0, getStatus, getMetrics);
157
+ // google is cheapest (0.00075 per token total)
158
+ expect(result.provider).toBe('google');
159
+ // round-robin index should be unchanged
160
+ expect(result.nextRoundRobinIndex).toBe(0);
161
+ });
162
+ it('uses least-loaded strategy when load balancing is enabled with least-loaded', () => {
163
+ const config = makeConfig({
164
+ loadBalancing: { enabled: true, strategy: 'least-loaded' },
165
+ });
166
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
167
+ expect(result.provider).toBe('openai'); // lowest load (0.1)
168
+ });
169
+ it('uses latency-based strategy when load balancing is enabled with latency-based', () => {
170
+ const config = makeConfig({
171
+ loadBalancing: { enabled: true, strategy: 'latency-based' },
172
+ });
173
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
174
+ expect(result.provider).toBe('google'); // lowest latency (50ms)
175
+ });
176
+ it('uses cost-based strategy when load balancing is enabled with cost-based', () => {
177
+ const config = makeConfig({
178
+ loadBalancing: { enabled: true, strategy: 'cost-based' },
179
+ });
180
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
181
+ expect(result.provider).toBe('google'); // lowest cost
182
+ });
183
+ it('falls back to configured defaultProvider when load balancing is disabled', () => {
184
+ const config = makeConfig({ loadBalancing: { enabled: false, strategy: 'round-robin' } });
185
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
186
+ expect(result.provider).toBe('anthropic'); // default
187
+ });
188
+ it('falls back to first-available when defaultProvider is not in available list', () => {
189
+ const config = makeConfig({
190
+ defaultProvider: 'cohere',
191
+ loadBalancing: { enabled: false, strategy: 'round-robin' },
192
+ });
193
+ const result = selectProvider({ messages: [] }, ['openai', 'google'], config, 0, getStatus, getMetrics);
194
+ expect(result.provider).toBe('openai'); // first available
195
+ });
196
+ });
197
+ // ============================================================================
198
+ // getFallbackProvider
199
+ // ============================================================================
200
+ describe('getFallbackProvider', () => {
201
+ const available = ['anthropic', 'openai', 'google'];
202
+ it('returns null when no rule matches the error condition', () => {
203
+ const rules = [
204
+ {
205
+ condition: 'rate_limit',
206
+ fallbackProviders: ['openai'],
207
+ retryOriginal: false,
208
+ },
209
+ ];
210
+ // A generic error produces 'error' condition, no rule for that
211
+ const result = getFallbackProvider('anthropic', new Error('generic fail'), rules, available);
212
+ expect(result).toBeNull();
213
+ });
214
+ it('returns the first fallback provider that is not the current provider', () => {
215
+ const rules = [
216
+ {
217
+ condition: 'error',
218
+ fallbackProviders: ['anthropic', 'openai', 'google'],
219
+ retryOriginal: false,
220
+ },
221
+ ];
222
+ // current=anthropic is excluded; openai is next available
223
+ const result = getFallbackProvider('anthropic', new Error('provider error'), rules, available);
224
+ expect(result).toBe('openai');
225
+ });
226
+ it('returns null when all fallback providers are either current or unavailable', () => {
227
+ const rules = [
228
+ {
229
+ condition: 'error',
230
+ fallbackProviders: ['anthropic'], // only option is the current one
231
+ retryOriginal: false,
232
+ },
233
+ ];
234
+ const result = getFallbackProvider('anthropic', new Error('provider error'), rules, available);
235
+ expect(result).toBeNull();
236
+ });
237
+ it('uses rate_limit condition when error message contains "rate limit"', () => {
238
+ const rules = [
239
+ {
240
+ condition: 'rate_limit',
241
+ fallbackProviders: ['google'],
242
+ retryOriginal: true,
243
+ },
244
+ ];
245
+ const result = getFallbackProvider('anthropic', new Error('rate limit exceeded'), rules, available);
246
+ expect(result).toBe('google');
247
+ });
248
+ });
249
+ //# sourceMappingURL=MultiLLMProvider.selection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiLLMProvider.selection.test.js","sourceRoot":"","sources":["../../../tests/testing/MultiLLMProvider.selection.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Quarantine query builder pure-function tests
3
+ *
4
+ * Tests for buildFilteredQuery and rowToEntry from
5
+ * packages/core/src/repositories/quarantine/query-builder.ts.
6
+ *
7
+ * No mocking needed — both are pure functions.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=quarantine-query-builder.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quarantine-query-builder.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/quarantine-query-builder.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Quarantine query builder pure-function tests
3
+ *
4
+ * Tests for buildFilteredQuery and rowToEntry from
5
+ * packages/core/src/repositories/quarantine/query-builder.ts.
6
+ *
7
+ * No mocking needed — both are pure functions.
8
+ */
9
+ import { describe, it, expect } from 'vitest';
10
+ import { buildFilteredQuery, rowToEntry } from '../../src/repositories/quarantine/query-builder.js';
11
+ // ============================================================================
12
+ // buildFilteredQuery — filter branch coverage
13
+ // ============================================================================
14
+ describe('buildFilteredQuery', () => {
15
+ it('returns base WHERE 1=1 query when no filters are provided', () => {
16
+ const result = buildFilteredQuery({});
17
+ expect(result.query).toContain('WHERE 1=1');
18
+ expect(result.countQuery).toContain('WHERE 1=1');
19
+ // No additional WHERE clauses beyond the base
20
+ expect(result.query).not.toContain('AND skill_id');
21
+ expect(result.query).not.toContain('AND source');
22
+ expect(result.query).not.toContain('AND severity');
23
+ expect(result.params).toHaveLength(2); // limit + offset are appended
24
+ });
25
+ it('appends reviewedBy filter correctly', () => {
26
+ const result = buildFilteredQuery({ reviewedBy: 'alice' });
27
+ expect(result.query).toContain('AND reviewed_by = ?');
28
+ expect(result.countQuery).toContain('AND reviewed_by = ?');
29
+ expect(result.params).toContain('alice');
30
+ expect(result.countParams).toContain('alice');
31
+ });
32
+ it('appends since date filter correctly', () => {
33
+ const since = new Date('2025-01-01T00:00:00Z');
34
+ const result = buildFilteredQuery({ since });
35
+ expect(result.query).toContain('AND quarantine_date >= ?');
36
+ expect(result.countQuery).toContain('AND quarantine_date >= ?');
37
+ expect(result.params).toContain(since.toISOString());
38
+ expect(result.countParams).toContain(since.toISOString());
39
+ });
40
+ it('appends until date filter correctly', () => {
41
+ const until = new Date('2025-12-31T23:59:59Z');
42
+ const result = buildFilteredQuery({ until });
43
+ expect(result.query).toContain('AND quarantine_date <= ?');
44
+ expect(result.countQuery).toContain('AND quarantine_date <= ?');
45
+ expect(result.params).toContain(until.toISOString());
46
+ expect(result.countParams).toContain(until.toISOString());
47
+ });
48
+ it('applies combined since + until range', () => {
49
+ const since = new Date('2025-01-01T00:00:00Z');
50
+ const until = new Date('2025-06-30T23:59:59Z');
51
+ const result = buildFilteredQuery({ since, until });
52
+ expect(result.query).toContain('AND quarantine_date >= ?');
53
+ expect(result.query).toContain('AND quarantine_date <= ?');
54
+ expect(result.params).toContain(since.toISOString());
55
+ expect(result.params).toContain(until.toISOString());
56
+ });
57
+ it('applies all filters simultaneously and builds correct param array', () => {
58
+ const since = new Date('2025-01-01T00:00:00Z');
59
+ const until = new Date('2025-12-31T00:00:00Z');
60
+ const result = buildFilteredQuery({
61
+ skillId: 'skill-abc',
62
+ source: 'github',
63
+ severity: 'MALICIOUS',
64
+ reviewStatus: 'pending',
65
+ reviewedBy: 'bob',
66
+ since,
67
+ until,
68
+ limit: 50,
69
+ offset: 10,
70
+ });
71
+ expect(result.query).toContain('AND skill_id = ?');
72
+ expect(result.query).toContain('AND source = ?');
73
+ expect(result.query).toContain('AND severity = ?');
74
+ expect(result.query).toContain('AND review_status = ?');
75
+ expect(result.query).toContain('AND reviewed_by = ?');
76
+ expect(result.query).toContain('AND quarantine_date >= ?');
77
+ expect(result.query).toContain('AND quarantine_date <= ?');
78
+ expect(result.query).toContain('LIMIT ? OFFSET ?');
79
+ // Verify param ordering: skill_id, source, severity, review_status, reviewed_by, since, until, limit, offset
80
+ expect(result.params).toEqual([
81
+ 'skill-abc',
82
+ 'github',
83
+ 'MALICIOUS',
84
+ 'pending',
85
+ 'bob',
86
+ since.toISOString(),
87
+ until.toISOString(),
88
+ 50,
89
+ 10,
90
+ ]);
91
+ });
92
+ it('uses default limit=20 and offset=0 when not specified', () => {
93
+ const result = buildFilteredQuery({});
94
+ // Last two params should be limit and offset defaults
95
+ const lastTwo = result.params.slice(-2);
96
+ expect(lastTwo).toEqual([20, 0]);
97
+ });
98
+ });
99
+ // ============================================================================
100
+ // rowToEntry — column mapping
101
+ // ============================================================================
102
+ describe('rowToEntry', () => {
103
+ it('maps all database row columns to the correct output properties', () => {
104
+ const row = {
105
+ id: 'entry-001',
106
+ skill_id: 'community/bad-skill',
107
+ source: 'github',
108
+ quarantine_reason: 'Detected malicious pattern',
109
+ severity: 'MALICIOUS',
110
+ detected_patterns: '["eval(", "require(fs)"]',
111
+ quarantine_date: '2025-06-15T10:00:00.000Z',
112
+ reviewed_by: 'security-team',
113
+ review_status: 'approved',
114
+ review_notes: 'Confirmed malicious',
115
+ review_date: '2025-06-16T09:00:00.000Z',
116
+ created_at: '2025-06-15T10:00:00.000Z',
117
+ updated_at: '2025-06-16T09:00:00.000Z',
118
+ };
119
+ const entry = rowToEntry(row);
120
+ expect(entry.id).toBe('entry-001');
121
+ expect(entry.skillId).toBe('community/bad-skill');
122
+ expect(entry.source).toBe('github');
123
+ expect(entry.quarantineReason).toBe('Detected malicious pattern');
124
+ expect(entry.severity).toBe('MALICIOUS');
125
+ expect(entry.detectedPatterns).toEqual(['eval(', 'require(fs)']);
126
+ expect(entry.quarantineDate).toBe('2025-06-15T10:00:00.000Z');
127
+ expect(entry.reviewedBy).toBe('security-team');
128
+ expect(entry.reviewStatus).toBe('approved');
129
+ expect(entry.reviewNotes).toBe('Confirmed malicious');
130
+ expect(entry.reviewDate).toBe('2025-06-16T09:00:00.000Z');
131
+ expect(entry.createdAt).toBe('2025-06-15T10:00:00.000Z');
132
+ expect(entry.updatedAt).toBe('2025-06-16T09:00:00.000Z');
133
+ });
134
+ it('handles null optional fields correctly', () => {
135
+ const row = {
136
+ id: 'entry-002',
137
+ skill_id: 'community/suspicious-skill',
138
+ source: 'registry',
139
+ quarantine_reason: 'High risk score',
140
+ severity: 'SUSPICIOUS',
141
+ detected_patterns: '[]',
142
+ quarantine_date: '2025-07-01T00:00:00.000Z',
143
+ reviewed_by: null,
144
+ review_status: 'pending',
145
+ review_notes: null,
146
+ review_date: null,
147
+ created_at: '2025-07-01T00:00:00.000Z',
148
+ updated_at: '2025-07-01T00:00:00.000Z',
149
+ };
150
+ const entry = rowToEntry(row);
151
+ expect(entry.reviewedBy).toBeNull();
152
+ expect(entry.reviewNotes).toBeNull();
153
+ expect(entry.reviewDate).toBeNull();
154
+ expect(entry.detectedPatterns).toEqual([]);
155
+ });
156
+ });
157
+ //# sourceMappingURL=quarantine-query-builder.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quarantine-query-builder.test.js","sourceRoot":"","sources":["../../../tests/unit/quarantine-query-builder.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillsmith/core",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "Core types and utilities for Skillsmith",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",