@skillsmith/mcp-server 0.4.2 → 0.4.3

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 (178) hide show
  1. package/README.md +11 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/__tests__/LocalIndexer.test.js +158 -0
  4. package/dist/src/__tests__/LocalIndexer.test.js.map +1 -1
  5. package/dist/src/__tests__/compare.test.d.ts +8 -0
  6. package/dist/src/__tests__/compare.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/compare.test.js +162 -0
  8. package/dist/src/__tests__/compare.test.js.map +1 -0
  9. package/dist/src/__tests__/context.async.test.d.ts +8 -0
  10. package/dist/src/__tests__/context.async.test.d.ts.map +1 -0
  11. package/dist/src/__tests__/context.async.test.js +223 -0
  12. package/dist/src/__tests__/context.async.test.js.map +1 -0
  13. package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts +10 -0
  14. package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts.map +1 -0
  15. package/dist/src/__tests__/middleware/errorFormatter.builders.test.js +93 -0
  16. package/dist/src/__tests__/middleware/errorFormatter.builders.test.js.map +1 -0
  17. package/dist/src/__tests__/middleware/license-renewal.test.d.ts +10 -0
  18. package/dist/src/__tests__/middleware/license-renewal.test.d.ts.map +1 -0
  19. package/dist/src/__tests__/middleware/license-renewal.test.js +152 -0
  20. package/dist/src/__tests__/middleware/license-renewal.test.js.map +1 -0
  21. package/dist/src/__tests__/middleware/quota-helpers.test.d.ts +9 -0
  22. package/dist/src/__tests__/middleware/quota-helpers.test.d.ts.map +1 -0
  23. package/dist/src/__tests__/middleware/quota-helpers.test.js +105 -0
  24. package/dist/src/__tests__/middleware/quota-helpers.test.js.map +1 -0
  25. package/dist/src/__tests__/middleware/quota.test.d.ts +12 -0
  26. package/dist/src/__tests__/middleware/quota.test.d.ts.map +1 -0
  27. package/dist/src/__tests__/middleware/quota.test.js +189 -0
  28. package/dist/src/__tests__/middleware/quota.test.js.map +1 -0
  29. package/dist/src/__tests__/recommend-online-path.test.d.ts +10 -0
  30. package/dist/src/__tests__/recommend-online-path.test.d.ts.map +1 -0
  31. package/dist/src/__tests__/recommend-online-path.test.js +225 -0
  32. package/dist/src/__tests__/recommend-online-path.test.js.map +1 -0
  33. package/dist/src/__tests__/recommend.test.d.ts +2 -0
  34. package/dist/src/__tests__/recommend.test.d.ts.map +1 -1
  35. package/dist/src/__tests__/recommend.test.js +14 -2
  36. package/dist/src/__tests__/recommend.test.js.map +1 -1
  37. package/dist/src/__tests__/search-online-path.test.d.ts +10 -0
  38. package/dist/src/__tests__/search-online-path.test.d.ts.map +1 -0
  39. package/dist/src/__tests__/search-online-path.test.js +140 -0
  40. package/dist/src/__tests__/search-online-path.test.js.map +1 -0
  41. package/dist/src/__tests__/search.test.js +153 -5
  42. package/dist/src/__tests__/search.test.js.map +1 -1
  43. package/dist/src/context/project-detector.d.ts.map +1 -1
  44. package/dist/src/context/project-detector.js +1 -0
  45. package/dist/src/context/project-detector.js.map +1 -1
  46. package/dist/src/context.async.d.ts +48 -0
  47. package/dist/src/context.async.d.ts.map +1 -0
  48. package/dist/src/context.async.js +215 -0
  49. package/dist/src/context.async.js.map +1 -0
  50. package/dist/src/context.d.ts +5 -145
  51. package/dist/src/context.d.ts.map +1 -1
  52. package/dist/src/context.helpers.d.ts +25 -0
  53. package/dist/src/context.helpers.d.ts.map +1 -0
  54. package/dist/src/context.helpers.js +49 -0
  55. package/dist/src/context.helpers.js.map +1 -0
  56. package/dist/src/context.js +11 -228
  57. package/dist/src/context.js.map +1 -1
  58. package/dist/src/context.types.d.ts +110 -0
  59. package/dist/src/context.types.d.ts.map +1 -0
  60. package/dist/src/context.types.js +10 -0
  61. package/dist/src/context.types.js.map +1 -0
  62. package/dist/src/health/readinessCheck.d.ts +1 -1
  63. package/dist/src/health/readinessCheck.d.ts.map +1 -1
  64. package/dist/src/index.js +21 -152
  65. package/dist/src/index.js.map +1 -1
  66. package/dist/src/indexer/FrontmatterParser.d.ts +6 -0
  67. package/dist/src/indexer/FrontmatterParser.d.ts.map +1 -1
  68. package/dist/src/indexer/FrontmatterParser.js +15 -0
  69. package/dist/src/indexer/FrontmatterParser.js.map +1 -1
  70. package/dist/src/indexer/LocalIndexer.d.ts +4 -0
  71. package/dist/src/indexer/LocalIndexer.d.ts.map +1 -1
  72. package/dist/src/indexer/LocalIndexer.js +3 -0
  73. package/dist/src/indexer/LocalIndexer.js.map +1 -1
  74. package/dist/src/middleware/degradation.d.ts.map +1 -1
  75. package/dist/src/middleware/degradation.js +8 -0
  76. package/dist/src/middleware/degradation.js.map +1 -1
  77. package/dist/src/middleware/errorFormatter.builders.d.ts +49 -0
  78. package/dist/src/middleware/errorFormatter.builders.d.ts.map +1 -0
  79. package/dist/src/middleware/errorFormatter.builders.js +237 -0
  80. package/dist/src/middleware/errorFormatter.builders.js.map +1 -0
  81. package/dist/src/middleware/errorFormatter.d.ts +5 -100
  82. package/dist/src/middleware/errorFormatter.d.ts.map +1 -1
  83. package/dist/src/middleware/errorFormatter.js +16 -238
  84. package/dist/src/middleware/errorFormatter.js.map +1 -1
  85. package/dist/src/middleware/errorFormatter.types.d.ts +81 -0
  86. package/dist/src/middleware/errorFormatter.types.d.ts.map +1 -0
  87. package/dist/src/middleware/errorFormatter.types.js +34 -0
  88. package/dist/src/middleware/errorFormatter.types.js.map +1 -0
  89. package/dist/src/middleware/toolFeatureMapping.d.ts +1 -1
  90. package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -1
  91. package/dist/src/middleware/toolFeatureMapping.js +8 -0
  92. package/dist/src/middleware/toolFeatureMapping.js.map +1 -1
  93. package/dist/src/tool-dispatch.d.ts +27 -0
  94. package/dist/src/tool-dispatch.d.ts.map +1 -0
  95. package/dist/src/tool-dispatch.js +127 -0
  96. package/dist/src/tool-dispatch.js.map +1 -0
  97. package/dist/src/tools/LocalSkillSearch.d.ts.map +1 -1
  98. package/dist/src/tools/LocalSkillSearch.js +4 -0
  99. package/dist/src/tools/LocalSkillSearch.js.map +1 -1
  100. package/dist/src/tools/get-skill.d.ts.map +1 -1
  101. package/dist/src/tools/get-skill.js +14 -0
  102. package/dist/src/tools/get-skill.js.map +1 -1
  103. package/dist/src/tools/index.d.ts +6 -0
  104. package/dist/src/tools/index.d.ts.map +1 -1
  105. package/dist/src/tools/index.js +6 -0
  106. package/dist/src/tools/index.js.map +1 -1
  107. package/dist/src/tools/install.d.ts +3 -35
  108. package/dist/src/tools/install.d.ts.map +1 -1
  109. package/dist/src/tools/install.js +22 -74
  110. package/dist/src/tools/install.js.map +1 -1
  111. package/dist/src/tools/install.optimize.d.ts +46 -0
  112. package/dist/src/tools/install.optimize.d.ts.map +1 -0
  113. package/dist/src/tools/install.optimize.js +67 -0
  114. package/dist/src/tools/install.optimize.js.map +1 -0
  115. package/dist/src/tools/install.tool.d.ts +44 -0
  116. package/dist/src/tools/install.tool.d.ts.map +1 -0
  117. package/dist/src/tools/install.tool.js +44 -0
  118. package/dist/src/tools/install.tool.js.map +1 -0
  119. package/dist/src/tools/install.types.d.ts +7 -1
  120. package/dist/src/tools/install.types.d.ts.map +1 -1
  121. package/dist/src/tools/recommend.d.ts +2 -4
  122. package/dist/src/tools/recommend.d.ts.map +1 -1
  123. package/dist/src/tools/recommend.format.d.ts +28 -0
  124. package/dist/src/tools/recommend.format.d.ts.map +1 -0
  125. package/dist/src/tools/recommend.format.js +111 -0
  126. package/dist/src/tools/recommend.format.js.map +1 -0
  127. package/dist/src/tools/recommend.js +6 -97
  128. package/dist/src/tools/recommend.js.map +1 -1
  129. package/dist/src/tools/recommend.types.d.ts +1 -1
  130. package/dist/src/tools/search.d.ts +24 -21
  131. package/dist/src/tools/search.d.ts.map +1 -1
  132. package/dist/src/tools/search.formatter.d.ts +30 -0
  133. package/dist/src/tools/search.formatter.d.ts.map +1 -0
  134. package/dist/src/tools/search.formatter.js +64 -0
  135. package/dist/src/tools/search.formatter.js.map +1 -0
  136. package/dist/src/tools/search.js +55 -54
  137. package/dist/src/tools/search.js.map +1 -1
  138. package/dist/src/tools/skill-audit.d.ts +98 -0
  139. package/dist/src/tools/skill-audit.d.ts.map +1 -0
  140. package/dist/src/tools/skill-audit.js +105 -0
  141. package/dist/src/tools/skill-audit.js.map +1 -0
  142. package/dist/src/tools/skill-audit.test.d.ts +6 -0
  143. package/dist/src/tools/skill-audit.test.d.ts.map +1 -0
  144. package/dist/src/tools/skill-audit.test.js +121 -0
  145. package/dist/src/tools/skill-audit.test.js.map +1 -0
  146. package/dist/src/tools/skill-diff.d.ts +107 -0
  147. package/dist/src/tools/skill-diff.d.ts.map +1 -0
  148. package/dist/src/tools/skill-diff.js +268 -0
  149. package/dist/src/tools/skill-diff.js.map +1 -0
  150. package/dist/src/tools/skill-diff.test.d.ts +6 -0
  151. package/dist/src/tools/skill-diff.test.d.ts.map +1 -0
  152. package/dist/src/tools/skill-diff.test.js +260 -0
  153. package/dist/src/tools/skill-diff.test.js.map +1 -0
  154. package/dist/src/tools/skill-updates.d.ts +1 -1
  155. package/dist/src/tools/skill-updates.d.ts.map +1 -1
  156. package/dist/src/tools/suggest.d.ts +4 -4
  157. package/dist/src/tools/uninstall.d.ts +1 -1
  158. package/dist/src/tools/validate.helpers.d.ts.map +1 -1
  159. package/dist/src/tools/validate.helpers.js +31 -0
  160. package/dist/src/tools/validate.helpers.js.map +1 -1
  161. package/dist/src/utils/validation.d.ts +13 -0
  162. package/dist/src/utils/validation.d.ts.map +1 -1
  163. package/dist/src/utils/validation.js +27 -0
  164. package/dist/src/utils/validation.js.map +1 -1
  165. package/dist/tests/health.test.js +4 -4
  166. package/dist/tests/health.test.js.map +1 -1
  167. package/dist/tests/integration/recommend.integration.test.js +2 -0
  168. package/dist/tests/integration/recommend.integration.test.js.map +1 -1
  169. package/dist/tests/integration/setup.d.ts +3 -1
  170. package/dist/tests/integration/setup.d.ts.map +1 -1
  171. package/dist/tests/integration/setup.js +4 -1
  172. package/dist/tests/integration/setup.js.map +1 -1
  173. package/dist/tests/recommend.test.js +2 -0
  174. package/dist/tests/recommend.test.js.map +1 -1
  175. package/dist/tests/unit/validate-helpers.test.js +54 -0
  176. package/dist/tests/unit/validate-helpers.test.js.map +1 -1
  177. package/package.json +2 -2
  178. package/server.json +2 -2
@@ -0,0 +1,105 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Quota helpers pure-function unit tests
3
+ *
4
+ * Tests for InMemoryQuotaStorage, getWarningMessage, and getWarningLevel
5
+ * from quota-helpers.ts. No mocking needed — all pure functions or
6
+ * simple in-memory operations.
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { InMemoryQuotaStorage, getWarningMessage, getWarningLevel, } from '../../middleware/quota-helpers.js';
10
+ // ============================================================================
11
+ // InMemoryQuotaStorage
12
+ // ============================================================================
13
+ describe('InMemoryQuotaStorage', () => {
14
+ it('starts with zero usage for a new customer', async () => {
15
+ const storage = new InMemoryQuotaStorage();
16
+ const usage = await storage.getUsage('customer-1');
17
+ expect(usage.used).toBe(0);
18
+ expect(usage.periodStart).toBeInstanceOf(Date);
19
+ expect(usage.periodEnd).toBeInstanceOf(Date);
20
+ expect(usage.periodEnd.getTime()).toBeGreaterThan(usage.periodStart.getTime());
21
+ });
22
+ it('accumulates usage correctly across multiple incrementUsage calls', async () => {
23
+ const storage = new InMemoryQuotaStorage();
24
+ await storage.incrementUsage('customer-a', 1);
25
+ await storage.incrementUsage('customer-a', 1);
26
+ await storage.incrementUsage('customer-a', 3);
27
+ const usage = await storage.getUsage('customer-a');
28
+ expect(usage.used).toBe(5);
29
+ });
30
+ it('tracks usage independently per customer', async () => {
31
+ const storage = new InMemoryQuotaStorage();
32
+ await storage.incrementUsage('cust-x', 10);
33
+ await storage.incrementUsage('cust-y', 5);
34
+ const usageX = await storage.getUsage('cust-x');
35
+ const usageY = await storage.getUsage('cust-y');
36
+ expect(usageX.used).toBe(10);
37
+ expect(usageY.used).toBe(5);
38
+ });
39
+ it('resets usage after initializePeriod', async () => {
40
+ const storage = new InMemoryQuotaStorage();
41
+ await storage.incrementUsage('cust-z', 50);
42
+ await storage.initializePeriod('cust-z', 1000);
43
+ const usage = await storage.getUsage('cust-z');
44
+ expect(usage.used).toBe(0);
45
+ });
46
+ });
47
+ // ============================================================================
48
+ // getWarningMessage()
49
+ // ============================================================================
50
+ describe('getWarningMessage()', () => {
51
+ it('returns undefined for warning level 0 (no warning)', () => {
52
+ const msg = getWarningMessage(0, 100, 1000, 'community');
53
+ expect(msg).toBeUndefined();
54
+ });
55
+ it('returns 80% notice string for warning level 80', () => {
56
+ const msg = getWarningMessage(80, 800, 1000, 'community');
57
+ expect(msg).toBeDefined();
58
+ expect(msg).toContain('80%');
59
+ expect(msg).toContain('200'); // 200 remaining
60
+ });
61
+ it('returns 90% warning string for warning level 90', () => {
62
+ const msg = getWarningMessage(90, 900, 1000, 'community');
63
+ expect(msg).toBeDefined();
64
+ expect(msg).toContain('90%');
65
+ expect(msg).toContain('100'); // 100 remaining
66
+ expect(msg).toContain('upgrading');
67
+ });
68
+ it('returns exceeded string for warning level 100', () => {
69
+ const msg = getWarningMessage(100, 1000, 1000, 'community');
70
+ expect(msg).toBeDefined();
71
+ expect(msg).toContain('exceeded');
72
+ expect(msg).toContain('Upgrade');
73
+ });
74
+ });
75
+ // ============================================================================
76
+ // getWarningLevel()
77
+ // ============================================================================
78
+ describe('getWarningLevel()', () => {
79
+ it('returns 0 for usage below 80%', () => {
80
+ expect(getWarningLevel(0)).toBe(0);
81
+ expect(getWarningLevel(50)).toBe(0);
82
+ expect(getWarningLevel(79.9)).toBe(0);
83
+ });
84
+ it('returns 80 for usage at exactly 80%', () => {
85
+ expect(getWarningLevel(80)).toBe(80);
86
+ });
87
+ it('returns 80 for usage between 80% and 90%', () => {
88
+ expect(getWarningLevel(85)).toBe(80);
89
+ expect(getWarningLevel(89.9)).toBe(80);
90
+ });
91
+ it('returns 90 for usage at exactly 90%', () => {
92
+ expect(getWarningLevel(90)).toBe(90);
93
+ });
94
+ it('returns 90 for usage between 90% and 100%', () => {
95
+ expect(getWarningLevel(95)).toBe(90);
96
+ expect(getWarningLevel(99.9)).toBe(90);
97
+ });
98
+ it('returns 100 for usage at exactly 100%', () => {
99
+ expect(getWarningLevel(100)).toBe(100);
100
+ });
101
+ it('returns 100 for usage above 100% (over-quota)', () => {
102
+ expect(getWarningLevel(110)).toBe(100);
103
+ });
104
+ });
105
+ //# sourceMappingURL=quota-helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-helpers.test.js","sourceRoot":"","sources":["../../../../src/__tests__/middleware/quota-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,GAChB,MAAM,mCAAmC,CAAA;AAE1C,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAA;QAE1C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QAElD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC1B,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;QAC9C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;QAC5C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,MAAM,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAA;QAE1C,MAAM,OAAO,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;QAE7C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QAElD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAA;QAE1C,MAAM,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QAC1C,MAAM,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;QAEzC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC/C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAE/C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC5B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAA;QAE1C,MAAM,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QAC1C,MAAM,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAE9C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAE9C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,+EAA+E;AAC/E,sBAAsB;AACtB,+EAA+E;AAE/E,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,GAAG,GAAG,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;QAExD,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,GAAG,GAAG,iBAAiB,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;QAEzD,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA,CAAC,gBAAgB;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,GAAG,GAAG,iBAAiB,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;QAEzD,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA,CAAC,gBAAgB;QAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,GAAG,GAAG,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;QAE3D,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACjC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAClC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACnC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Quota middleware unit tests
3
+ *
4
+ * CRITICAL: This file uses ONLY beforeEach/afterEach-scoped spies.
5
+ * There are NO module-level vi.mock() calls here to avoid conflicts
6
+ * with the existing quota-wiring.test.ts file that lives in the same
7
+ * directory and uses the same modules.
8
+ *
9
+ * @see packages/mcp-server/src/__tests__/middleware/quota-wiring.test.ts
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=quota.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/middleware/quota.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Quota middleware unit tests
3
+ *
4
+ * CRITICAL: This file uses ONLY beforeEach/afterEach-scoped spies.
5
+ * There are NO module-level vi.mock() calls here to avoid conflicts
6
+ * with the existing quota-wiring.test.ts file that lives in the same
7
+ * directory and uses the same modules.
8
+ *
9
+ * @see packages/mcp-server/src/__tests__/middleware/quota-wiring.test.ts
10
+ */
11
+ import { describe, it, expect, afterEach, vi } from 'vitest';
12
+ import { createQuotaMiddleware, withQuotaEnforcement, isUnlimitedTier, getQuotaLimit, formatQuotaRemaining, } from '../../middleware/quota.js';
13
+ import { createLicenseMiddleware } from '../../middleware/license.js';
14
+ // ============================================================================
15
+ // Shared test helpers
16
+ // ============================================================================
17
+ function makeStorage(used) {
18
+ return {
19
+ getUsage: vi.fn().mockResolvedValue({
20
+ used,
21
+ periodStart: new Date(),
22
+ periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
23
+ }),
24
+ incrementUsage: vi.fn().mockResolvedValue(undefined),
25
+ initializePeriod: vi.fn().mockResolvedValue(undefined),
26
+ };
27
+ }
28
+ function makeLicenseInfo(tier) {
29
+ return {
30
+ valid: true,
31
+ tier,
32
+ features: [],
33
+ };
34
+ }
35
+ // ============================================================================
36
+ // getStatus()
37
+ // ============================================================================
38
+ describe('createQuotaMiddleware - getStatus()', () => {
39
+ afterEach(() => {
40
+ vi.restoreAllMocks();
41
+ });
42
+ it('returns unlimited result for enterprise tier', async () => {
43
+ const quota = createQuotaMiddleware();
44
+ const licenseInfo = makeLicenseInfo('enterprise');
45
+ const status = await quota.getStatus(licenseInfo);
46
+ expect(status.allowed).toBe(true);
47
+ expect(status.remaining).toBe(-1);
48
+ expect(status.limit).toBe(-1);
49
+ expect(status.percentUsed).toBe(0);
50
+ expect(status.warningLevel).toBe(0);
51
+ });
52
+ it('returns 0% usage for community tier at zero calls', async () => {
53
+ const storage = makeStorage(0);
54
+ const quota = createQuotaMiddleware({ storage });
55
+ const licenseInfo = makeLicenseInfo('community');
56
+ const status = await quota.getStatus(licenseInfo);
57
+ expect(status.allowed).toBe(true);
58
+ expect(status.limit).toBe(1_000);
59
+ expect(status.remaining).toBe(1_000);
60
+ expect(status.percentUsed).toBe(0);
61
+ expect(status.warningLevel).toBe(0);
62
+ expect(status.message).toBeUndefined();
63
+ });
64
+ it('returns 80% warning for community tier at 800/1000 calls', async () => {
65
+ const storage = makeStorage(800);
66
+ const quota = createQuotaMiddleware({ storage });
67
+ const licenseInfo = makeLicenseInfo('community');
68
+ const status = await quota.getStatus(licenseInfo);
69
+ expect(status.allowed).toBe(true);
70
+ expect(status.percentUsed).toBe(80);
71
+ expect(status.warningLevel).toBe(80);
72
+ expect(status.message).toContain('80%');
73
+ });
74
+ it('returns 90% warning for community tier at 900/1000 calls', async () => {
75
+ const storage = makeStorage(900);
76
+ const quota = createQuotaMiddleware({ storage });
77
+ const licenseInfo = makeLicenseInfo('community');
78
+ const status = await quota.getStatus(licenseInfo);
79
+ expect(status.allowed).toBe(true);
80
+ expect(status.percentUsed).toBe(90);
81
+ expect(status.warningLevel).toBe(90);
82
+ expect(status.message).toContain('90%');
83
+ expect(status.upgradeUrl).toBeDefined();
84
+ });
85
+ it('returns not-allowed for community tier at 1000/1000 calls (quota exhausted)', async () => {
86
+ const storage = makeStorage(1000);
87
+ const quota = createQuotaMiddleware({ storage });
88
+ const licenseInfo = makeLicenseInfo('community');
89
+ const status = await quota.getStatus(licenseInfo);
90
+ expect(status.allowed).toBe(false);
91
+ expect(status.remaining).toBe(0);
92
+ expect(status.percentUsed).toBe(100);
93
+ });
94
+ it('works with null licenseInfo (defaults to community tier)', async () => {
95
+ const storage = makeStorage(0);
96
+ const quota = createQuotaMiddleware({ storage });
97
+ const status = await quota.getStatus(null);
98
+ expect(status.limit).toBe(1_000);
99
+ expect(status.allowed).toBe(true);
100
+ });
101
+ });
102
+ // ============================================================================
103
+ // withQuotaEnforcement()
104
+ // ============================================================================
105
+ describe('withQuotaEnforcement()', () => {
106
+ afterEach(() => {
107
+ vi.restoreAllMocks();
108
+ });
109
+ it('delegates to inner handler when quota allows', async () => {
110
+ const storage = makeStorage(0);
111
+ const quota = createQuotaMiddleware({ storage });
112
+ const license = createLicenseMiddleware();
113
+ const innerHandler = vi.fn().mockResolvedValue({ result: 'success' });
114
+ const wrapped = withQuotaEnforcement(innerHandler, license, quota);
115
+ const result = await wrapped('skill_search', { query: 'commit' });
116
+ expect(innerHandler).toHaveBeenCalledTimes(1);
117
+ expect(innerHandler).toHaveBeenCalledWith({ query: 'commit' });
118
+ expect(result).toEqual({ result: 'success' });
119
+ });
120
+ it('returns quota-exceeded error response when quota is exceeded', async () => {
121
+ const exhaustedStorage = makeStorage(1000); // community limit = 1000
122
+ const quota = createQuotaMiddleware({ storage: exhaustedStorage });
123
+ const license = createLicenseMiddleware();
124
+ const innerHandler = vi.fn().mockResolvedValue({ result: 'should not reach here' });
125
+ const wrapped = withQuotaEnforcement(innerHandler, license, quota);
126
+ const result = await wrapped('skill_search', { query: 'commit' });
127
+ // Inner handler should NOT have been called
128
+ expect(innerHandler).not.toHaveBeenCalled();
129
+ // Result must be MCP error response shape
130
+ expect(result).toMatchObject({ isError: true });
131
+ const errorResult = result;
132
+ expect(errorResult.isError).toBe(true);
133
+ expect(Array.isArray(errorResult.content)).toBe(true);
134
+ expect(errorResult.content[0].type).toBe('text');
135
+ });
136
+ });
137
+ // ============================================================================
138
+ // isUnlimitedTier()
139
+ // ============================================================================
140
+ describe('isUnlimitedTier()', () => {
141
+ it('returns true for enterprise tier', () => {
142
+ expect(isUnlimitedTier('enterprise')).toBe(true);
143
+ });
144
+ it('returns false for community tier', () => {
145
+ expect(isUnlimitedTier('community')).toBe(false);
146
+ });
147
+ it('returns false for individual tier', () => {
148
+ expect(isUnlimitedTier('individual')).toBe(false);
149
+ });
150
+ it('returns false for team tier', () => {
151
+ expect(isUnlimitedTier('team')).toBe(false);
152
+ });
153
+ });
154
+ // ============================================================================
155
+ // getQuotaLimit()
156
+ // ============================================================================
157
+ describe('getQuotaLimit()', () => {
158
+ it('returns 1000 for community tier', () => {
159
+ expect(getQuotaLimit('community')).toBe(1_000);
160
+ });
161
+ it('returns 10000 for individual tier', () => {
162
+ expect(getQuotaLimit('individual')).toBe(10_000);
163
+ });
164
+ it('returns 100000 for team tier', () => {
165
+ expect(getQuotaLimit('team')).toBe(100_000);
166
+ });
167
+ it('returns -1 for enterprise tier (unlimited)', () => {
168
+ expect(getQuotaLimit('enterprise')).toBe(-1);
169
+ });
170
+ });
171
+ // ============================================================================
172
+ // formatQuotaRemaining()
173
+ // ============================================================================
174
+ describe('formatQuotaRemaining()', () => {
175
+ it('returns "Unlimited" when limit is -1', () => {
176
+ expect(formatQuotaRemaining(-1, -1)).toBe('Unlimited');
177
+ });
178
+ it('formats remaining / limit for finite limits', () => {
179
+ const result = formatQuotaRemaining(750, 1000);
180
+ expect(result).toContain('750');
181
+ expect(result).toContain('1,000');
182
+ });
183
+ it('formats zero remaining correctly', () => {
184
+ const result = formatQuotaRemaining(0, 1000);
185
+ expect(result).toContain('0');
186
+ expect(result).toContain('1,000');
187
+ });
188
+ });
189
+ //# sourceMappingURL=quota.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.test.js","sourceRoot":"","sources":["../../../../src/__tests__/middleware/quota.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Online API path tests for executeRecommend
3
+ *
4
+ * Tests the branch where context.apiClient.isOffline() returns false,
5
+ * exercising the Promise.allSettled(API + local) merge path.
6
+ *
7
+ * Split from recommend.test.ts to keep each file under 500 lines.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=recommend-online-path.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend-online-path.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/recommend-online-path.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Online API path tests for executeRecommend
3
+ *
4
+ * Tests the branch where context.apiClient.isOffline() returns false,
5
+ * exercising the Promise.allSettled(API + local) merge path.
6
+ *
7
+ * Split from recommend.test.ts to keep each file under 500 lines.
8
+ */
9
+ import { describe, it, expect, beforeAll, afterAll, vi, beforeEach, afterEach } from 'vitest';
10
+ import { executeRecommend } from '../tools/recommend.js';
11
+ import { createTestContext } from './test-utils.js';
12
+ import * as LocalSkillSearchModule from '../tools/LocalSkillSearch.js';
13
+ import * as CoreModule from '@skillsmith/core';
14
+ /**
15
+ * SMI-2755 Wave 2: Online API path tests for executeRecommend
16
+ *
17
+ * Tests the branch where context.apiClient.isOffline() returns false,
18
+ * exercising the Promise.allSettled(API + local) merge path.
19
+ */
20
+ describe('Recommend Tool - Online API Path (SMI-2755)', () => {
21
+ let onlineContext;
22
+ const mockLocalSkills = [
23
+ {
24
+ id: 'local/my-tool',
25
+ name: 'my-tool',
26
+ description: 'A local tool',
27
+ author: 'local',
28
+ tags: ['productivity'],
29
+ qualityScore: 70,
30
+ trustTier: 'local',
31
+ source: 'local',
32
+ path: '/home/user/.claude/skills/my-tool',
33
+ hasSkillMd: true,
34
+ lastModified: new Date().toISOString(),
35
+ repository: null,
36
+ },
37
+ ];
38
+ beforeAll(() => {
39
+ onlineContext = createTestContext();
40
+ });
41
+ afterAll(() => {
42
+ onlineContext.db.close();
43
+ });
44
+ beforeEach(() => {
45
+ // Mock local indexer returning a single skill
46
+ // Partial mock — only methods called by executeRecommend are implemented
47
+ vi.spyOn(LocalSkillSearchModule, 'getLocalIndexer').mockReturnValue({
48
+ index: vi.fn().mockResolvedValue(mockLocalSkills),
49
+ indexSync: vi.fn().mockReturnValue(mockLocalSkills),
50
+ search: vi.fn().mockReturnValue(mockLocalSkills),
51
+ clearCache: vi.fn(),
52
+ getSkillsDir: vi.fn().mockReturnValue('/home/user/.claude/skills'),
53
+ calculateQualityScore: vi.fn().mockReturnValue(70),
54
+ indexSkillDir: vi.fn(),
55
+ });
56
+ });
57
+ afterEach(() => {
58
+ vi.restoreAllMocks();
59
+ });
60
+ it('takes the online path when isOffline() returns false', async () => {
61
+ // Spy on isOffline to return false and getRecommendations to return controlled data
62
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
63
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
64
+ data: [
65
+ {
66
+ id: 'community/jest-helper',
67
+ name: 'jest-helper',
68
+ description: 'Jest test helper',
69
+ author: 'community',
70
+ tags: ['testing'],
71
+ trust_tier: 'community',
72
+ quality_score: 0.87,
73
+ },
74
+ ],
75
+ meta: { total: 1 },
76
+ });
77
+ const result = await executeRecommend({ project_context: 'testing', limit: 5 }, onlineContext);
78
+ expect(result.recommendations).toBeDefined();
79
+ expect(Array.isArray(result.recommendations)).toBe(true);
80
+ expect(onlineContext.apiClient.getRecommendations).toHaveBeenCalledTimes(1);
81
+ });
82
+ it('merges API results with local results and deduplicates by skill_id', async () => {
83
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
84
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
85
+ data: [
86
+ {
87
+ id: 'community/jest-helper',
88
+ name: 'jest-helper',
89
+ description: 'Jest helper',
90
+ author: 'community',
91
+ tags: ['testing'],
92
+ trust_tier: 'community',
93
+ quality_score: 0.87,
94
+ },
95
+ {
96
+ id: 'anthropic/commit',
97
+ name: 'commit',
98
+ description: 'Commit helper',
99
+ author: 'anthropic',
100
+ tags: ['git'],
101
+ trust_tier: 'verified',
102
+ quality_score: 0.95,
103
+ },
104
+ ],
105
+ meta: { total: 2 },
106
+ });
107
+ const result = await executeRecommend({ project_context: 'git workflow', limit: 10 }, onlineContext);
108
+ // No duplicate skill IDs
109
+ const ids = result.recommendations.map((r) => r.skill_id);
110
+ const uniqueIds = new Set(ids);
111
+ expect(ids.length).toBe(uniqueIds.size);
112
+ });
113
+ it('falls back to local-only results when API call fails', async () => {
114
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
115
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockRejectedValue(new Error('Network error'));
116
+ // Should not throw — falls back gracefully
117
+ const result = await executeRecommend({ project_context: 'testing', limit: 5 }, onlineContext);
118
+ expect(result.recommendations).toBeDefined();
119
+ expect(Array.isArray(result.recommendations)).toBe(true);
120
+ });
121
+ it('applies role filter and +30 score boost for matched roles in online path', async () => {
122
+ expect.hasAssertions();
123
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
124
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
125
+ data: [
126
+ {
127
+ id: 'community/jest-helper',
128
+ name: 'jest-helper',
129
+ description: 'Jest helper',
130
+ author: 'community',
131
+ tags: ['testing'],
132
+ trust_tier: 'community',
133
+ quality_score: 0.5,
134
+ },
135
+ ],
136
+ meta: { total: 1 },
137
+ });
138
+ const result = await executeRecommend({ project_context: 'testing', role: 'testing', limit: 5 }, onlineContext);
139
+ expect(result.context.role_filter).toBe('testing');
140
+ // If any recommendation survived the role filter, its score should be boosted
141
+ result.recommendations.forEach((rec) => {
142
+ if (rec.roles?.includes('testing')) {
143
+ expect(rec.quality_score).toBeGreaterThanOrEqual(50 + 30);
144
+ expect(rec.reason).toContain('role: testing');
145
+ }
146
+ });
147
+ });
148
+ it('calls trackEvent when context.distinctId is set', async () => {
149
+ const trackEventSpy = vi.spyOn(CoreModule, 'trackEvent').mockImplementation(() => { });
150
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
151
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
152
+ data: [],
153
+ meta: { total: 0 },
154
+ });
155
+ const contextWithId = { ...onlineContext, distinctId: 'test-user-123' };
156
+ await executeRecommend({ project_context: 'testing', limit: 5 }, contextWithId);
157
+ expect(trackEventSpy).toHaveBeenCalledWith('test-user-123', 'skill_recommend', expect.objectContaining({ source: 'mcp' }));
158
+ });
159
+ it('does not call trackEvent when context.distinctId is absent', async () => {
160
+ const trackEventSpy = vi.spyOn(CoreModule, 'trackEvent').mockImplementation(() => { });
161
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
162
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
163
+ data: [],
164
+ meta: { total: 0 },
165
+ });
166
+ // onlineContext has no distinctId (createTestContext doesn't set one)
167
+ await executeRecommend({ project_context: 'testing', limit: 5 }, onlineContext);
168
+ expect(trackEventSpy).not.toHaveBeenCalled();
169
+ });
170
+ it('includes local skill results in candidates_considered count', async () => {
171
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
172
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
173
+ data: [
174
+ {
175
+ id: 'community/docker-compose',
176
+ name: 'docker-compose',
177
+ description: 'Docker helper',
178
+ author: 'community',
179
+ tags: ['devops'],
180
+ trust_tier: 'community',
181
+ quality_score: 0.84,
182
+ },
183
+ ],
184
+ meta: { total: 1 },
185
+ });
186
+ const result = await executeRecommend({ project_context: 'devops', limit: 5 }, onlineContext);
187
+ // candidates_considered = API results + local results
188
+ // Local indexer returns 1 skill (mockLocalSkills has 1 item)
189
+ expect(result.candidates_considered).toBeGreaterThanOrEqual(1);
190
+ });
191
+ it('handles API returning empty data gracefully', async () => {
192
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
193
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
194
+ data: [],
195
+ meta: { total: 0 },
196
+ });
197
+ const result = await executeRecommend({ project_context: 'anything', limit: 5 }, onlineContext);
198
+ expect(result.recommendations).toBeDefined();
199
+ // Local skill (my-tool) may appear in results
200
+ expect(Array.isArray(result.recommendations)).toBe(true);
201
+ });
202
+ it('combines online + role filter + tracking simultaneously', async () => {
203
+ const trackEventSpy = vi.spyOn(CoreModule, 'trackEvent').mockImplementation(() => { });
204
+ vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
205
+ vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
206
+ data: [
207
+ {
208
+ id: 'community/jest-helper',
209
+ name: 'jest-helper',
210
+ description: 'Jest test helper',
211
+ author: 'community',
212
+ tags: ['testing'],
213
+ trust_tier: 'community',
214
+ quality_score: 0.6,
215
+ },
216
+ ],
217
+ meta: { total: 1 },
218
+ });
219
+ const contextWithId = { ...onlineContext, distinctId: 'combined-test-user' };
220
+ const result = await executeRecommend({ project_context: 'testing', role: 'testing', limit: 5 }, contextWithId);
221
+ expect(result.context.role_filter).toBe('testing');
222
+ expect(trackEventSpy).toHaveBeenCalledWith('combined-test-user', 'skill_recommend', expect.objectContaining({ source: 'mcp' }));
223
+ });
224
+ });
225
+ //# sourceMappingURL=recommend-online-path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend-online-path.test.js","sourceRoot":"","sources":["../../../src/__tests__/recommend-online-path.test.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@
2
2
  * Tests for SMI-1837: Include Local Skills in Recommendations
3
3
  * Verifies that local skills are searched in parallel with the API,
4
4
  * not just as a fallback.
5
+ *
6
+ * SMI-2755: Online API path tests split to recommend-online-path.test.ts.
5
7
  */
6
8
  export {};
7
9
  //# sourceMappingURL=recommend.test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recommend.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/recommend.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
1
+ {"version":3,"file":"recommend.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/recommend.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}