@skillsmith/mcp-server 0.3.3 → 0.3.5

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 (261) hide show
  1. package/README.md +5 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/__tests__/LocalIndexer.test.d.ts +5 -0
  4. package/dist/src/__tests__/LocalIndexer.test.d.ts.map +1 -0
  5. package/dist/src/__tests__/LocalIndexer.test.js +396 -0
  6. package/dist/src/__tests__/LocalIndexer.test.js.map +1 -0
  7. package/dist/src/__tests__/context.test.d.ts +10 -0
  8. package/dist/src/__tests__/context.test.d.ts.map +1 -0
  9. package/dist/src/__tests__/context.test.js +345 -0
  10. package/dist/src/__tests__/context.test.js.map +1 -0
  11. package/dist/src/__tests__/get-skill.test.d.ts +1 -0
  12. package/dist/src/__tests__/get-skill.test.d.ts.map +1 -1
  13. package/dist/src/__tests__/get-skill.test.js +279 -0
  14. package/dist/src/__tests__/get-skill.test.js.map +1 -1
  15. package/dist/src/__tests__/index-local.test.d.ts +5 -0
  16. package/dist/src/__tests__/index-local.test.d.ts.map +1 -0
  17. package/dist/src/__tests__/index-local.test.js +203 -0
  18. package/dist/src/__tests__/index-local.test.js.map +1 -0
  19. package/dist/src/__tests__/middleware/license.test.js +180 -78
  20. package/dist/src/__tests__/middleware/license.test.js.map +1 -1
  21. package/dist/src/__tests__/recommend.test.d.ts +7 -0
  22. package/dist/src/__tests__/recommend.test.d.ts.map +1 -0
  23. package/dist/src/__tests__/recommend.test.js +277 -0
  24. package/dist/src/__tests__/recommend.test.js.map +1 -0
  25. package/dist/src/__tests__/search.test.js +140 -1
  26. package/dist/src/__tests__/search.test.js.map +1 -1
  27. package/dist/src/__tests__/utils/validation.test.d.ts +7 -0
  28. package/dist/src/__tests__/utils/validation.test.d.ts.map +1 -0
  29. package/dist/src/__tests__/utils/validation.test.js +82 -0
  30. package/dist/src/__tests__/utils/validation.test.js.map +1 -0
  31. package/dist/src/context.d.ts +22 -0
  32. package/dist/src/context.d.ts.map +1 -1
  33. package/dist/src/context.js +63 -9
  34. package/dist/src/context.js.map +1 -1
  35. package/dist/src/index.js +15 -1
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/indexer/FrontmatterParser.d.ts +30 -0
  38. package/dist/src/indexer/FrontmatterParser.d.ts.map +1 -0
  39. package/dist/src/indexer/FrontmatterParser.js +109 -0
  40. package/dist/src/indexer/FrontmatterParser.js.map +1 -0
  41. package/dist/src/indexer/LocalIndexer.d.ts +136 -0
  42. package/dist/src/indexer/LocalIndexer.d.ts.map +1 -0
  43. package/dist/src/indexer/LocalIndexer.js +271 -0
  44. package/dist/src/indexer/LocalIndexer.js.map +1 -0
  45. package/dist/src/indexer/index.d.ts +8 -0
  46. package/dist/src/indexer/index.d.ts.map +1 -0
  47. package/dist/src/indexer/index.js +8 -0
  48. package/dist/src/indexer/index.js.map +1 -0
  49. package/dist/src/llm/failover.d.ts +210 -0
  50. package/dist/src/llm/failover.d.ts.map +1 -0
  51. package/dist/src/llm/failover.js +329 -0
  52. package/dist/src/llm/failover.js.map +1 -0
  53. package/dist/src/middleware/errorFormatter.d.ts +27 -0
  54. package/dist/src/middleware/errorFormatter.d.ts.map +1 -1
  55. package/dist/src/middleware/errorFormatter.js +95 -0
  56. package/dist/src/middleware/errorFormatter.js.map +1 -1
  57. package/dist/src/middleware/index.d.ts +1 -1
  58. package/dist/src/middleware/index.d.ts.map +1 -1
  59. package/dist/src/middleware/index.js.map +1 -1
  60. package/dist/src/middleware/license.d.ts +5 -0
  61. package/dist/src/middleware/license.d.ts.map +1 -1
  62. package/dist/src/middleware/license.js +2 -1
  63. package/dist/src/middleware/license.js.map +1 -1
  64. package/dist/src/middleware/quota-helpers.d.ts +40 -0
  65. package/dist/src/middleware/quota-helpers.d.ts.map +1 -0
  66. package/dist/src/middleware/quota-helpers.js +90 -0
  67. package/dist/src/middleware/quota-helpers.js.map +1 -0
  68. package/dist/src/middleware/quota-types.d.ts +105 -0
  69. package/dist/src/middleware/quota-types.d.ts.map +1 -0
  70. package/dist/src/middleware/quota-types.js +4 -0
  71. package/dist/src/middleware/quota-types.js.map +1 -0
  72. package/dist/src/middleware/quota.d.ts +3 -97
  73. package/dist/src/middleware/quota.d.ts.map +1 -1
  74. package/dist/src/middleware/quota.js +2 -87
  75. package/dist/src/middleware/quota.js.map +1 -1
  76. package/dist/src/tools/LocalSkillSearch.d.ts +32 -0
  77. package/dist/src/tools/LocalSkillSearch.d.ts.map +1 -0
  78. package/dist/src/tools/LocalSkillSearch.js +74 -0
  79. package/dist/src/tools/LocalSkillSearch.js.map +1 -0
  80. package/dist/src/tools/compare.d.ts +3 -102
  81. package/dist/src/tools/compare.d.ts.map +1 -1
  82. package/dist/src/tools/compare.helpers.d.ts +36 -0
  83. package/dist/src/tools/compare.helpers.d.ts.map +1 -0
  84. package/dist/src/tools/compare.helpers.js +252 -0
  85. package/dist/src/tools/compare.helpers.js.map +1 -0
  86. package/dist/src/tools/compare.js +6 -288
  87. package/dist/src/tools/compare.js.map +1 -1
  88. package/dist/src/tools/compare.types.d.ts +134 -0
  89. package/dist/src/tools/compare.types.d.ts.map +1 -0
  90. package/dist/src/tools/compare.types.js +47 -0
  91. package/dist/src/tools/compare.types.js.map +1 -0
  92. package/dist/src/tools/get-skill.d.ts.map +1 -1
  93. package/dist/src/tools/get-skill.js +38 -2
  94. package/dist/src/tools/get-skill.js.map +1 -1
  95. package/dist/src/tools/index-local.d.ts +117 -0
  96. package/dist/src/tools/index-local.d.ts.map +1 -0
  97. package/dist/src/tools/index-local.js +126 -0
  98. package/dist/src/tools/index-local.js.map +1 -0
  99. package/dist/src/tools/index.d.ts +2 -0
  100. package/dist/src/tools/index.d.ts.map +1 -1
  101. package/dist/src/tools/index.js +2 -0
  102. package/dist/src/tools/index.js.map +1 -1
  103. package/dist/src/tools/install.conflict-helpers.d.ts +64 -0
  104. package/dist/src/tools/install.conflict-helpers.d.ts.map +1 -0
  105. package/dist/src/tools/install.conflict-helpers.js +165 -0
  106. package/dist/src/tools/install.conflict-helpers.js.map +1 -0
  107. package/dist/src/tools/install.conflict-helpers.test.d.ts +15 -0
  108. package/dist/src/tools/install.conflict-helpers.test.d.ts.map +1 -0
  109. package/dist/src/tools/install.conflict-helpers.test.js +243 -0
  110. package/dist/src/tools/install.conflict-helpers.test.js.map +1 -0
  111. package/dist/src/tools/install.conflict.d.ts +58 -0
  112. package/dist/src/tools/install.conflict.d.ts.map +1 -0
  113. package/dist/src/tools/install.conflict.js +160 -0
  114. package/dist/src/tools/install.conflict.js.map +1 -0
  115. package/dist/src/tools/install.conflict.test.d.ts +15 -0
  116. package/dist/src/tools/install.conflict.test.d.ts.map +1 -0
  117. package/dist/src/tools/install.conflict.test.js +354 -0
  118. package/dist/src/tools/install.conflict.test.js.map +1 -0
  119. package/dist/src/tools/install.d.ts +11 -60
  120. package/dist/src/tools/install.d.ts.map +1 -1
  121. package/dist/src/tools/install.helpers.d.ts +83 -0
  122. package/dist/src/tools/install.helpers.d.ts.map +1 -0
  123. package/dist/src/tools/install.helpers.js +333 -0
  124. package/dist/src/tools/install.helpers.js.map +1 -0
  125. package/dist/src/tools/install.js +199 -255
  126. package/dist/src/tools/install.js.map +1 -1
  127. package/dist/src/tools/install.types.d.ts +178 -0
  128. package/dist/src/tools/install.types.d.ts.map +1 -0
  129. package/dist/src/tools/install.types.js +105 -0
  130. package/dist/src/tools/install.types.js.map +1 -0
  131. package/dist/src/tools/merge.d.ts +53 -0
  132. package/dist/src/tools/merge.d.ts.map +1 -0
  133. package/dist/src/tools/merge.js +276 -0
  134. package/dist/src/tools/merge.js.map +1 -0
  135. package/dist/src/tools/merge.test.d.ts +11 -0
  136. package/dist/src/tools/merge.test.d.ts.map +1 -0
  137. package/dist/src/tools/merge.test.js +224 -0
  138. package/dist/src/tools/merge.test.js.map +1 -0
  139. package/dist/src/tools/recommend.d.ts +3 -148
  140. package/dist/src/tools/recommend.d.ts.map +1 -1
  141. package/dist/src/tools/recommend.helpers.d.ts +42 -0
  142. package/dist/src/tools/recommend.helpers.d.ts.map +1 -0
  143. package/dist/src/tools/recommend.helpers.js +155 -0
  144. package/dist/src/tools/recommend.helpers.js.map +1 -0
  145. package/dist/src/tools/recommend.js +216 -154
  146. package/dist/src/tools/recommend.js.map +1 -1
  147. package/dist/src/tools/recommend.types.d.ts +164 -0
  148. package/dist/src/tools/recommend.types.d.ts.map +1 -0
  149. package/dist/src/tools/recommend.types.js +87 -0
  150. package/dist/src/tools/recommend.types.js.map +1 -0
  151. package/dist/src/tools/search.d.ts +18 -4
  152. package/dist/src/tools/search.d.ts.map +1 -1
  153. package/dist/src/tools/search.js +91 -16
  154. package/dist/src/tools/search.js.map +1 -1
  155. package/dist/src/tools/validate.d.ts +3 -70
  156. package/dist/src/tools/validate.d.ts.map +1 -1
  157. package/dist/src/tools/validate.helpers.d.ts +22 -0
  158. package/dist/src/tools/validate.helpers.d.ts.map +1 -0
  159. package/dist/src/tools/validate.helpers.js +276 -0
  160. package/dist/src/tools/validate.helpers.js.map +1 -0
  161. package/dist/src/tools/validate.js +4 -337
  162. package/dist/src/tools/validate.js.map +1 -1
  163. package/dist/src/tools/validate.types.d.ts +96 -0
  164. package/dist/src/tools/validate.types.d.ts.map +1 -0
  165. package/dist/src/tools/validate.types.js +71 -0
  166. package/dist/src/tools/validate.types.js.map +1 -0
  167. package/dist/src/utils/validation.d.ts +6 -2
  168. package/dist/src/utils/validation.d.ts.map +1 -1
  169. package/dist/src/utils/validation.js +11 -2
  170. package/dist/src/utils/validation.js.map +1 -1
  171. package/dist/src/webhooks/index.d.ts +1 -0
  172. package/dist/src/webhooks/index.d.ts.map +1 -1
  173. package/dist/src/webhooks/index.js +2 -0
  174. package/dist/src/webhooks/index.js.map +1 -1
  175. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts +68 -0
  176. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts.map +1 -0
  177. package/dist/src/webhooks/stripe-webhook-endpoint.js +213 -0
  178. package/dist/src/webhooks/stripe-webhook-endpoint.js.map +1 -0
  179. package/dist/src/webhooks/webhook-endpoint.d.ts +4 -49
  180. package/dist/src/webhooks/webhook-endpoint.d.ts.map +1 -1
  181. package/dist/src/webhooks/webhook-endpoint.js +3 -125
  182. package/dist/src/webhooks/webhook-endpoint.js.map +1 -1
  183. package/dist/src/webhooks/webhook-helpers.d.ts +69 -0
  184. package/dist/src/webhooks/webhook-helpers.d.ts.map +1 -0
  185. package/dist/src/webhooks/webhook-helpers.js +146 -0
  186. package/dist/src/webhooks/webhook-helpers.js.map +1 -0
  187. package/dist/tests/e2e/conflict-resolution.e2e.test.d.ts +12 -0
  188. package/dist/tests/e2e/conflict-resolution.e2e.test.d.ts.map +1 -0
  189. package/dist/tests/e2e/conflict-resolution.e2e.test.js +343 -0
  190. package/dist/tests/e2e/conflict-resolution.e2e.test.js.map +1 -0
  191. package/dist/tests/integration/fixtures/development-skills.d.ts +22 -0
  192. package/dist/tests/integration/fixtures/development-skills.d.ts.map +1 -0
  193. package/dist/tests/integration/fixtures/development-skills.js +165 -0
  194. package/dist/tests/integration/fixtures/development-skills.js.map +1 -0
  195. package/dist/tests/integration/fixtures/devops-skills.d.ts +10 -0
  196. package/dist/tests/integration/fixtures/devops-skills.d.ts.map +1 -0
  197. package/dist/tests/integration/fixtures/devops-skills.js +70 -0
  198. package/dist/tests/integration/fixtures/devops-skills.js.map +1 -0
  199. package/dist/tests/integration/fixtures/experimental-skills.d.ts +14 -0
  200. package/dist/tests/integration/fixtures/experimental-skills.d.ts.map +1 -0
  201. package/dist/tests/integration/fixtures/experimental-skills.js +255 -0
  202. package/dist/tests/integration/fixtures/experimental-skills.js.map +1 -0
  203. package/dist/tests/integration/fixtures/skill-types.d.ts +18 -0
  204. package/dist/tests/integration/fixtures/skill-types.d.ts.map +1 -0
  205. package/dist/tests/integration/fixtures/skill-types.js +6 -0
  206. package/dist/tests/integration/fixtures/skill-types.js.map +1 -0
  207. package/dist/tests/integration/fixtures/test-skills.d.ts +10 -14
  208. package/dist/tests/integration/fixtures/test-skills.d.ts.map +1 -1
  209. package/dist/tests/integration/fixtures/test-skills.js +23 -590
  210. package/dist/tests/integration/fixtures/test-skills.js.map +1 -1
  211. package/dist/tests/integration/fixtures/testing-skills.d.ts +10 -0
  212. package/dist/tests/integration/fixtures/testing-skills.d.ts.map +1 -0
  213. package/dist/tests/integration/fixtures/testing-skills.js +70 -0
  214. package/dist/tests/integration/fixtures/testing-skills.js.map +1 -0
  215. package/dist/tests/integration/fixtures/verified-skills.d.ts +11 -0
  216. package/dist/tests/integration/fixtures/verified-skills.d.ts.map +1 -0
  217. package/dist/tests/integration/fixtures/verified-skills.js +91 -0
  218. package/dist/tests/integration/fixtures/verified-skills.js.map +1 -0
  219. package/dist/tests/integration/install-conflict.integration.test.d.ts +8 -0
  220. package/dist/tests/integration/install-conflict.integration.test.d.ts.map +1 -0
  221. package/dist/tests/integration/install-conflict.integration.test.js +384 -0
  222. package/dist/tests/integration/install-conflict.integration.test.js.map +1 -0
  223. package/dist/tests/integration/install-trust-parsing.integration.test.d.ts +13 -0
  224. package/dist/tests/integration/install-trust-parsing.integration.test.d.ts.map +1 -0
  225. package/dist/tests/integration/install-trust-parsing.integration.test.js +290 -0
  226. package/dist/tests/integration/install-trust-parsing.integration.test.js.map +1 -0
  227. package/dist/tests/integration/install.integration.test.js +151 -1
  228. package/dist/tests/integration/install.integration.test.js.map +1 -1
  229. package/dist/tests/integration/recommend.integration.test.js +2 -1
  230. package/dist/tests/integration/recommend.integration.test.js.map +1 -1
  231. package/dist/tests/llm/failover.test.d.ts +13 -0
  232. package/dist/tests/llm/failover.test.d.ts.map +1 -0
  233. package/dist/tests/llm/failover.test.js +250 -0
  234. package/dist/tests/llm/failover.test.js.map +1 -0
  235. package/dist/tests/recommend.test.js +133 -1
  236. package/dist/tests/recommend.test.js.map +1 -1
  237. package/dist/tests/tools.test.d.ts +1 -0
  238. package/dist/tests/tools.test.d.ts.map +1 -1
  239. package/dist/tests/tools.test.js +59 -1
  240. package/dist/tests/tools.test.js.map +1 -1
  241. package/dist/tests/unit/compare-helpers.test.d.ts +8 -0
  242. package/dist/tests/unit/compare-helpers.test.d.ts.map +1 -0
  243. package/dist/tests/unit/compare-helpers.test.js +224 -0
  244. package/dist/tests/unit/compare-helpers.test.js.map +1 -0
  245. package/dist/tests/unit/install-helpers.test.d.ts +8 -0
  246. package/dist/tests/unit/install-helpers.test.d.ts.map +1 -0
  247. package/dist/tests/unit/install-helpers.test.js +460 -0
  248. package/dist/tests/unit/install-helpers.test.js.map +1 -0
  249. package/dist/tests/unit/recommend-helpers.test.d.ts +8 -0
  250. package/dist/tests/unit/recommend-helpers.test.d.ts.map +1 -0
  251. package/dist/tests/unit/recommend-helpers.test.js +117 -0
  252. package/dist/tests/unit/recommend-helpers.test.js.map +1 -0
  253. package/dist/tests/unit/validate-helpers.test.d.ts +8 -0
  254. package/dist/tests/unit/validate-helpers.test.d.ts.map +1 -0
  255. package/dist/tests/unit/validate-helpers.test.js +243 -0
  256. package/dist/tests/unit/validate-helpers.test.js.map +1 -0
  257. package/package.json +7 -6
  258. package/src/assets/docs/USER_GUIDE.md +0 -220
  259. package/src/assets/skills/skillsmith/docs/QUOTAS.md +0 -182
  260. package/src/assets/skills/skillsmith/docs/SECURITY.md +0 -174
  261. package/src/assets/skills/skillsmith/docs/TRUST_TIERS.md +0 -142
@@ -0,0 +1,460 @@
1
+ /**
2
+ * @fileoverview Tests for install.helpers.ts functions
3
+ * @module @skillsmith/mcp-server/tests/unit/install-helpers
4
+ *
5
+ * SMI-1721: Comprehensive tests to improve coverage from 36% to 80%+
6
+ */
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ import * as fs from 'fs/promises';
9
+ import { parseSkillId, parseRepoUrl, validateSkillMd, generateTips, acquireManifestLock, releaseManifestLock, loadManifest, saveManifest, updateManifestSafely, lookupSkillFromRegistry, fetchFromGitHub, } from '../../src/tools/install.helpers.js';
10
+ // Mock fs module
11
+ vi.mock('fs/promises');
12
+ // Mock global fetch
13
+ const mockFetch = vi.fn();
14
+ global.fetch = mockFetch;
15
+ describe('install.helpers', () => {
16
+ describe('parseSkillId', () => {
17
+ it('parses full GitHub URL', () => {
18
+ const result = parseSkillId('https://github.com/owner/repo');
19
+ expect(result).toEqual({
20
+ owner: 'owner',
21
+ repo: 'repo',
22
+ path: '',
23
+ isRegistryId: false,
24
+ });
25
+ });
26
+ it('parses GitHub URL with path', () => {
27
+ const result = parseSkillId('https://github.com/owner/repo/tree/main/skills/my-skill');
28
+ expect(result).toEqual({
29
+ owner: 'owner',
30
+ repo: 'repo',
31
+ path: 'tree/main/skills/my-skill',
32
+ isRegistryId: false,
33
+ });
34
+ });
35
+ it('parses 2-part registry ID', () => {
36
+ const result = parseSkillId('author/skill-name');
37
+ expect(result).toEqual({
38
+ owner: 'author',
39
+ repo: 'skill-name',
40
+ path: '',
41
+ isRegistryId: true,
42
+ });
43
+ });
44
+ it('parses 3-part direct reference', () => {
45
+ const result = parseSkillId('owner/repo/skills/my-skill');
46
+ expect(result).toEqual({
47
+ owner: 'owner',
48
+ repo: 'repo',
49
+ path: 'skills/my-skill',
50
+ isRegistryId: false,
51
+ });
52
+ });
53
+ it('throws for invalid format', () => {
54
+ expect(() => parseSkillId('invalid')).toThrow('Invalid skill ID format');
55
+ });
56
+ });
57
+ describe('parseRepoUrl', () => {
58
+ it('parses simple GitHub URL', () => {
59
+ const result = parseRepoUrl('https://github.com/owner/repo');
60
+ expect(result).toEqual({
61
+ owner: 'owner',
62
+ repo: 'repo',
63
+ path: '',
64
+ branch: 'main',
65
+ });
66
+ });
67
+ it('parses GitHub URL with tree path', () => {
68
+ const result = parseRepoUrl('https://github.com/owner/repo/tree/develop/src/skills');
69
+ expect(result).toEqual({
70
+ owner: 'owner',
71
+ repo: 'repo',
72
+ path: 'src/skills',
73
+ branch: 'develop',
74
+ });
75
+ });
76
+ it('parses GitHub URL with blob path', () => {
77
+ const result = parseRepoUrl('https://github.com/owner/repo/blob/feature/path/file.md');
78
+ expect(result).toEqual({
79
+ owner: 'owner',
80
+ repo: 'repo',
81
+ path: 'path/file.md',
82
+ branch: 'feature',
83
+ });
84
+ });
85
+ it('rejects non-GitHub hosts', () => {
86
+ expect(() => parseRepoUrl('https://gitlab.com/owner/repo')).toThrow('Invalid repository host');
87
+ });
88
+ it('rejects malicious hosts', () => {
89
+ expect(() => parseRepoUrl('https://evil.com/owner/repo')).toThrow('Invalid repository host');
90
+ });
91
+ it('accepts www.github.com', () => {
92
+ const result = parseRepoUrl('https://www.github.com/owner/repo');
93
+ expect(result.owner).toBe('owner');
94
+ expect(result.repo).toBe('repo');
95
+ });
96
+ });
97
+ describe('validateSkillMd', () => {
98
+ it('validates valid SKILL.md', () => {
99
+ const content = `# My Skill
100
+
101
+ This is a skill that does something useful. It has enough content to pass validation.
102
+
103
+ ## Usage
104
+ Use this skill to do things.
105
+ `;
106
+ const result = validateSkillMd(content);
107
+ expect(result.valid).toBe(true);
108
+ expect(result.errors).toEqual([]);
109
+ });
110
+ it('rejects missing title', () => {
111
+ const content = 'This is content without a heading. It is long enough but has no title marker.';
112
+ const result = validateSkillMd(content);
113
+ expect(result.valid).toBe(false);
114
+ expect(result.errors).toContain('Missing title (# heading)');
115
+ });
116
+ it('rejects too short content', () => {
117
+ const content = '# Title\n\nToo short.';
118
+ const result = validateSkillMd(content);
119
+ expect(result.valid).toBe(false);
120
+ expect(result.errors).toContain('SKILL.md is too short (minimum 100 characters)');
121
+ });
122
+ it('collects multiple errors', () => {
123
+ const content = 'No title, too short';
124
+ const result = validateSkillMd(content);
125
+ expect(result.valid).toBe(false);
126
+ expect(result.errors.length).toBe(2);
127
+ });
128
+ });
129
+ describe('generateTips', () => {
130
+ it('generates tips with skill name', () => {
131
+ const tips = generateTips('my-skill');
132
+ expect(tips).toHaveLength(4);
133
+ expect(tips[0]).toContain('my-skill');
134
+ expect(tips[0]).toContain('installed successfully');
135
+ });
136
+ it('includes usage instructions', () => {
137
+ const tips = generateTips('test-skill');
138
+ expect(tips.some((t) => t.includes('Use the test-skill skill'))).toBe(true);
139
+ });
140
+ it('includes ls command', () => {
141
+ const tips = generateTips('any-skill');
142
+ expect(tips.some((t) => t.includes('ls ~/.claude/skills/'))).toBe(true);
143
+ });
144
+ it('includes uninstall hint', () => {
145
+ const tips = generateTips('any-skill');
146
+ expect(tips.some((t) => t.includes('uninstall_skill'))).toBe(true);
147
+ });
148
+ });
149
+ // ============================================================================
150
+ // SMI-1721: New tests for async/file system functions
151
+ // ============================================================================
152
+ describe('acquireManifestLock', () => {
153
+ const mockWriteFile = vi.mocked(fs.writeFile);
154
+ const mockStat = vi.mocked(fs.stat);
155
+ const mockUnlink = vi.mocked(fs.unlink);
156
+ beforeEach(() => {
157
+ vi.clearAllMocks();
158
+ });
159
+ it('acquires lock successfully on first try', async () => {
160
+ mockWriteFile.mockResolvedValueOnce(undefined);
161
+ await expect(acquireManifestLock()).resolves.toBeUndefined();
162
+ expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining('manifest.json.lock'), expect.any(String), { flag: 'wx' });
163
+ });
164
+ it('retries when lock exists and eventually succeeds', async () => {
165
+ // First call fails with EEXIST, second succeeds
166
+ const existsError = new Error('EEXIST');
167
+ existsError.code = 'EEXIST';
168
+ mockWriteFile.mockRejectedValueOnce(existsError).mockResolvedValueOnce(undefined);
169
+ // Mock stat to show lock is NOT stale (recent)
170
+ mockStat.mockResolvedValueOnce({
171
+ mtimeMs: Date.now() - 1000, // 1 second old
172
+ });
173
+ await expect(acquireManifestLock()).resolves.toBeUndefined();
174
+ expect(mockWriteFile).toHaveBeenCalledTimes(2);
175
+ });
176
+ it('removes stale lock and acquires', async () => {
177
+ const existsError = new Error('EEXIST');
178
+ existsError.code = 'EEXIST';
179
+ mockWriteFile.mockRejectedValueOnce(existsError).mockResolvedValueOnce(undefined);
180
+ // Mock stat to show lock is stale (old)
181
+ mockStat.mockResolvedValueOnce({
182
+ mtimeMs: Date.now() - 60000, // 60 seconds old (stale)
183
+ });
184
+ mockUnlink.mockResolvedValueOnce(undefined);
185
+ await expect(acquireManifestLock()).resolves.toBeUndefined();
186
+ expect(mockUnlink).toHaveBeenCalled();
187
+ });
188
+ it('throws non-EEXIST errors immediately', async () => {
189
+ const permError = new Error('EACCES');
190
+ permError.code = 'EACCES';
191
+ mockWriteFile.mockRejectedValueOnce(permError);
192
+ await expect(acquireManifestLock()).rejects.toThrow('EACCES');
193
+ });
194
+ });
195
+ describe('releaseManifestLock', () => {
196
+ const mockUnlink = vi.mocked(fs.unlink);
197
+ beforeEach(() => {
198
+ vi.clearAllMocks();
199
+ });
200
+ it('releases lock successfully', async () => {
201
+ mockUnlink.mockResolvedValueOnce(undefined);
202
+ await expect(releaseManifestLock()).resolves.toBeUndefined();
203
+ expect(mockUnlink).toHaveBeenCalledWith(expect.stringContaining('manifest.json.lock'));
204
+ });
205
+ it('ignores errors when lock already released', async () => {
206
+ mockUnlink.mockRejectedValueOnce(new Error('ENOENT'));
207
+ // Should not throw
208
+ await expect(releaseManifestLock()).resolves.toBeUndefined();
209
+ });
210
+ });
211
+ describe('loadManifest', () => {
212
+ const mockReadFile = vi.mocked(fs.readFile);
213
+ beforeEach(() => {
214
+ vi.clearAllMocks();
215
+ });
216
+ it('loads existing manifest', async () => {
217
+ const manifest = {
218
+ version: '1.0.0',
219
+ installedSkills: {
220
+ 'test/skill': {
221
+ id: 'test/skill',
222
+ name: 'test-skill',
223
+ version: '1.0.0',
224
+ source: 'github',
225
+ installPath: '/path/to/skill',
226
+ installedAt: '2024-01-01',
227
+ lastUpdated: '2024-01-01',
228
+ },
229
+ },
230
+ };
231
+ mockReadFile.mockResolvedValueOnce(JSON.stringify(manifest));
232
+ const result = await loadManifest();
233
+ expect(result).toEqual(manifest);
234
+ });
235
+ it('returns empty manifest when file does not exist', async () => {
236
+ mockReadFile.mockRejectedValueOnce(new Error('ENOENT'));
237
+ const result = await loadManifest();
238
+ expect(result).toEqual({
239
+ version: '1.0.0',
240
+ installedSkills: {},
241
+ });
242
+ });
243
+ it('returns empty manifest on parse error', async () => {
244
+ mockReadFile.mockResolvedValueOnce('invalid json {{{');
245
+ const result = await loadManifest();
246
+ expect(result).toEqual({
247
+ version: '1.0.0',
248
+ installedSkills: {},
249
+ });
250
+ });
251
+ });
252
+ describe('saveManifest', () => {
253
+ const mockMkdir = vi.mocked(fs.mkdir);
254
+ const mockWriteFile = vi.mocked(fs.writeFile);
255
+ const mockRename = vi.mocked(fs.rename);
256
+ beforeEach(() => {
257
+ vi.clearAllMocks();
258
+ });
259
+ it('saves manifest with atomic write', async () => {
260
+ mockMkdir.mockResolvedValueOnce(undefined);
261
+ mockWriteFile.mockResolvedValueOnce(undefined);
262
+ mockRename.mockResolvedValueOnce(undefined);
263
+ const manifest = {
264
+ version: '1.0.0',
265
+ installedSkills: {},
266
+ };
267
+ await saveManifest(manifest);
268
+ expect(mockMkdir).toHaveBeenCalledWith(expect.any(String), { recursive: true });
269
+ expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining('.tmp.'), JSON.stringify(manifest, null, 2));
270
+ expect(mockRename).toHaveBeenCalled();
271
+ });
272
+ });
273
+ describe('updateManifestSafely', () => {
274
+ const mockWriteFile = vi.mocked(fs.writeFile);
275
+ const mockReadFile = vi.mocked(fs.readFile);
276
+ const mockMkdir = vi.mocked(fs.mkdir);
277
+ const mockRename = vi.mocked(fs.rename);
278
+ const mockUnlink = vi.mocked(fs.unlink);
279
+ beforeEach(() => {
280
+ vi.clearAllMocks();
281
+ });
282
+ it('acquires lock, updates, and releases lock', async () => {
283
+ // Mock lock acquisition
284
+ mockWriteFile.mockResolvedValue(undefined);
285
+ // Mock load
286
+ mockReadFile.mockResolvedValueOnce(JSON.stringify({
287
+ version: '1.0.0',
288
+ installedSkills: {},
289
+ }));
290
+ // Mock save
291
+ mockMkdir.mockResolvedValueOnce(undefined);
292
+ mockRename.mockResolvedValueOnce(undefined);
293
+ // Mock release
294
+ mockUnlink.mockResolvedValueOnce(undefined);
295
+ const updateFn = vi.fn((m) => ({
296
+ ...m,
297
+ installedSkills: { 'new/skill': { id: 'new/skill' } },
298
+ }));
299
+ await updateManifestSafely(updateFn);
300
+ expect(updateFn).toHaveBeenCalled();
301
+ expect(mockUnlink).toHaveBeenCalled(); // Lock released
302
+ });
303
+ it('releases lock even on error', async () => {
304
+ // Mock lock acquisition
305
+ mockWriteFile.mockResolvedValue(undefined);
306
+ // Mock load - throw error
307
+ mockReadFile.mockRejectedValueOnce(new Error('Read error'));
308
+ // Mock release
309
+ mockUnlink.mockResolvedValueOnce(undefined);
310
+ const updateFn = vi.fn();
311
+ // loadManifest catches errors and returns empty manifest
312
+ // so this should still succeed
313
+ await expect(updateManifestSafely(updateFn)).resolves.toBeUndefined();
314
+ });
315
+ });
316
+ describe('lookupSkillFromRegistry', () => {
317
+ it('returns skill info from API when online', async () => {
318
+ const mockContext = {
319
+ apiClient: {
320
+ isOffline: () => false,
321
+ getSkill: vi.fn().mockResolvedValue({
322
+ data: {
323
+ name: 'test-skill',
324
+ repo_url: 'https://github.com/owner/repo',
325
+ trust_tier: 'community',
326
+ },
327
+ }),
328
+ },
329
+ skillRepository: {
330
+ findById: vi.fn(),
331
+ },
332
+ };
333
+ const result = await lookupSkillFromRegistry('test/skill', mockContext);
334
+ expect(result).toEqual({
335
+ repoUrl: 'https://github.com/owner/repo',
336
+ name: 'test-skill',
337
+ trustTier: 'community',
338
+ });
339
+ expect(mockContext.apiClient.getSkill).toHaveBeenCalledWith('test/skill');
340
+ });
341
+ it('falls back to local DB when API offline', async () => {
342
+ const mockContext = {
343
+ apiClient: {
344
+ isOffline: () => true,
345
+ },
346
+ skillRepository: {
347
+ findById: vi.fn().mockReturnValue({
348
+ name: 'local-skill',
349
+ repoUrl: 'https://github.com/local/repo',
350
+ trustTier: 'experimental',
351
+ }),
352
+ },
353
+ };
354
+ const result = await lookupSkillFromRegistry('local/skill', mockContext);
355
+ expect(result).toEqual({
356
+ repoUrl: 'https://github.com/local/repo',
357
+ name: 'local-skill',
358
+ trustTier: 'experimental',
359
+ });
360
+ });
361
+ it('falls back to local DB when API fails', async () => {
362
+ const mockContext = {
363
+ apiClient: {
364
+ isOffline: () => false,
365
+ getSkill: vi.fn().mockRejectedValue(new Error('API error')),
366
+ },
367
+ skillRepository: {
368
+ findById: vi.fn().mockReturnValue({
369
+ name: 'fallback-skill',
370
+ repoUrl: 'https://github.com/fallback/repo',
371
+ trustTier: 'community',
372
+ }),
373
+ },
374
+ };
375
+ const result = await lookupSkillFromRegistry('fallback/skill', mockContext);
376
+ expect(result).toEqual({
377
+ repoUrl: 'https://github.com/fallback/repo',
378
+ name: 'fallback-skill',
379
+ trustTier: 'community',
380
+ });
381
+ });
382
+ it('returns null when skill not found anywhere', async () => {
383
+ const mockContext = {
384
+ apiClient: {
385
+ isOffline: () => true,
386
+ },
387
+ skillRepository: {
388
+ findById: vi.fn().mockReturnValue(null),
389
+ },
390
+ };
391
+ const result = await lookupSkillFromRegistry('nonexistent/skill', mockContext);
392
+ expect(result).toBeNull();
393
+ });
394
+ it('returns null when API returns skill without repo_url', async () => {
395
+ const mockContext = {
396
+ apiClient: {
397
+ isOffline: () => false,
398
+ getSkill: vi.fn().mockResolvedValue({
399
+ data: {
400
+ name: 'seed-skill',
401
+ repo_url: null, // No repo URL (seed data)
402
+ },
403
+ }),
404
+ },
405
+ skillRepository: {
406
+ findById: vi.fn().mockReturnValue(null),
407
+ },
408
+ };
409
+ const result = await lookupSkillFromRegistry('seed/skill', mockContext);
410
+ expect(result).toBeNull();
411
+ });
412
+ });
413
+ describe('fetchFromGitHub', () => {
414
+ beforeEach(() => {
415
+ vi.clearAllMocks();
416
+ mockFetch.mockReset();
417
+ });
418
+ it('fetches file from main branch', async () => {
419
+ mockFetch.mockResolvedValueOnce({
420
+ ok: true,
421
+ text: () => Promise.resolve('# SKILL.md content'),
422
+ });
423
+ const result = await fetchFromGitHub('owner', 'repo', 'SKILL.md');
424
+ expect(result).toBe('# SKILL.md content');
425
+ expect(mockFetch).toHaveBeenCalledWith('https://raw.githubusercontent.com/owner/repo/main/SKILL.md');
426
+ });
427
+ it('fetches from specified branch', async () => {
428
+ mockFetch.mockResolvedValueOnce({
429
+ ok: true,
430
+ text: () => Promise.resolve('content from develop'),
431
+ });
432
+ const result = await fetchFromGitHub('owner', 'repo', 'file.md', 'develop');
433
+ expect(result).toBe('content from develop');
434
+ expect(mockFetch).toHaveBeenCalledWith('https://raw.githubusercontent.com/owner/repo/develop/file.md');
435
+ });
436
+ it('falls back to master when main fails', async () => {
437
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 }).mockResolvedValueOnce({
438
+ ok: true,
439
+ text: () => Promise.resolve('content from master'),
440
+ });
441
+ const result = await fetchFromGitHub('owner', 'repo', 'SKILL.md');
442
+ expect(result).toBe('content from master');
443
+ expect(mockFetch).toHaveBeenCalledTimes(2);
444
+ expect(mockFetch).toHaveBeenLastCalledWith('https://raw.githubusercontent.com/owner/repo/master/SKILL.md');
445
+ });
446
+ it('throws when both main and master fail', async () => {
447
+ mockFetch
448
+ .mockResolvedValueOnce({ ok: false, status: 404 })
449
+ .mockResolvedValueOnce({ ok: false, status: 404 });
450
+ await expect(fetchFromGitHub('owner', 'repo', 'SKILL.md')).rejects.toThrow('Failed to fetch SKILL.md: 404');
451
+ });
452
+ it('does not try master fallback for non-main branches', async () => {
453
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
454
+ await expect(fetchFromGitHub('owner', 'repo', 'SKILL.md', 'develop')).rejects.toThrow('Failed to fetch SKILL.md: 404');
455
+ // Should only call once, no master fallback
456
+ expect(mockFetch).toHaveBeenCalledTimes(1);
457
+ });
458
+ });
459
+ });
460
+ //# sourceMappingURL=install-helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-helpers.test.js","sourceRoot":"","sources":["../../../tests/unit/install-helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Tests for recommend.helpers.ts
3
+ * @module @skillsmith/mcp-server/tests/unit/recommend-helpers
4
+ *
5
+ * SMI-1719: Unit tests for extracted helper functions from Wave 3 refactor
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=recommend-helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend-helpers.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/recommend-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @fileoverview Tests for recommend.helpers.ts
3
+ * @module @skillsmith/mcp-server/tests/unit/recommend-helpers
4
+ *
5
+ * SMI-1719: Unit tests for extracted helper functions from Wave 3 refactor
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ import { inferRolesFromTags, isSkillCollection, COLLECTION_PATTERNS, } from '../../src/tools/recommend.helpers.js';
9
+ describe('recommend.helpers', () => {
10
+ describe('inferRolesFromTags', () => {
11
+ it('returns empty array for empty tags', () => {
12
+ expect(inferRolesFromTags([])).toEqual([]);
13
+ });
14
+ // SMI-1725: Defensive null/undefined handling
15
+ it('handles null input gracefully', () => {
16
+ expect(inferRolesFromTags(null)).toEqual([]);
17
+ });
18
+ it('handles undefined input gracefully', () => {
19
+ expect(inferRolesFromTags(undefined)).toEqual([]);
20
+ });
21
+ it('infers code-quality role from lint tags', () => {
22
+ const result = inferRolesFromTags(['lint', 'eslint']);
23
+ expect(result).toContain('code-quality');
24
+ });
25
+ it('infers testing role from test tags', () => {
26
+ const result = inferRolesFromTags(['testing', 'jest', 'vitest']);
27
+ expect(result).toContain('testing');
28
+ });
29
+ it('infers documentation role from docs tags', () => {
30
+ const result = inferRolesFromTags(['documentation', 'readme', 'jsdoc']);
31
+ expect(result).toContain('documentation');
32
+ });
33
+ it('infers workflow role from git/ci tags', () => {
34
+ const result = inferRolesFromTags(['git', 'commit', 'ci-cd']);
35
+ expect(result).toContain('workflow');
36
+ });
37
+ it('infers security role from security tags', () => {
38
+ const result = inferRolesFromTags(['security', 'audit', 'vulnerability']);
39
+ expect(result).toContain('security');
40
+ });
41
+ it('infers development-partner role from ai tags', () => {
42
+ const result = inferRolesFromTags(['ai', 'assistant', 'copilot']);
43
+ expect(result).toContain('development-partner');
44
+ });
45
+ it('handles multiple roles from mixed tags', () => {
46
+ const result = inferRolesFromTags(['eslint', 'jest', 'git']);
47
+ expect(result).toContain('code-quality');
48
+ expect(result).toContain('testing');
49
+ expect(result).toContain('workflow');
50
+ });
51
+ it('normalizes tags with hyphens and underscores', () => {
52
+ const result = inferRolesFromTags(['code-review', 'pull_request']);
53
+ expect(result).toContain('code-quality');
54
+ expect(result).toContain('workflow');
55
+ });
56
+ it('handles case insensitivity', () => {
57
+ const result = inferRolesFromTags(['ESLINT', 'Jest', 'GIT']);
58
+ expect(result).toContain('code-quality');
59
+ expect(result).toContain('testing');
60
+ expect(result).toContain('workflow');
61
+ });
62
+ it('does not duplicate roles', () => {
63
+ const result = inferRolesFromTags(['lint', 'linting', 'eslint', 'prettier']);
64
+ const codeQualityCount = result.filter((r) => r === 'code-quality').length;
65
+ expect(codeQualityCount).toBe(1);
66
+ });
67
+ it('returns empty for unrecognized tags', () => {
68
+ const result = inferRolesFromTags(['random', 'unknown', 'custom']);
69
+ expect(result).toEqual([]);
70
+ });
71
+ });
72
+ describe('isSkillCollection', () => {
73
+ it('returns true for skill names with -skills suffix', () => {
74
+ expect(isSkillCollection('my-skills', '')).toBe(true);
75
+ });
76
+ it('returns true for skill names with -collection suffix', () => {
77
+ expect(isSkillCollection('utils-collection', '')).toBe(true);
78
+ });
79
+ it('returns true for skill names with -pack suffix', () => {
80
+ expect(isSkillCollection('dev-pack', '')).toBe(true);
81
+ });
82
+ it('returns true for skill names with skill-collection', () => {
83
+ expect(isSkillCollection('my-skill-collection', '')).toBe(true);
84
+ });
85
+ it('returns true for skill names with skills-repo', () => {
86
+ expect(isSkillCollection('company-skills-repo', '')).toBe(true);
87
+ });
88
+ it('returns true when description contains collection of skills', () => {
89
+ expect(isSkillCollection('my-tool', 'A collection of useful skills for development')).toBe(true);
90
+ });
91
+ it('returns false for regular skill names', () => {
92
+ expect(isSkillCollection('commit-helper', '')).toBe(false);
93
+ });
94
+ it('returns false for skills with similar but not matching patterns', () => {
95
+ expect(isSkillCollection('skillful-coder', '')).toBe(false);
96
+ });
97
+ it('returns false when description mentions collection but not skill', () => {
98
+ expect(isSkillCollection('my-tool', 'A collection of utilities')).toBe(false);
99
+ });
100
+ it('handles empty inputs', () => {
101
+ expect(isSkillCollection('', '')).toBe(false);
102
+ });
103
+ });
104
+ describe('COLLECTION_PATTERNS', () => {
105
+ it('exports collection patterns constant', () => {
106
+ expect(COLLECTION_PATTERNS).toBeDefined();
107
+ expect(Array.isArray(COLLECTION_PATTERNS)).toBe(true);
108
+ expect(COLLECTION_PATTERNS.length).toBeGreaterThan(0);
109
+ });
110
+ it('contains expected patterns', () => {
111
+ expect(COLLECTION_PATTERNS).toContain('-skills');
112
+ expect(COLLECTION_PATTERNS).toContain('-collection');
113
+ expect(COLLECTION_PATTERNS).toContain('-pack');
114
+ });
115
+ });
116
+ });
117
+ //# sourceMappingURL=recommend-helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend-helpers.test.js","sourceRoot":"","sources":["../../../tests/unit/recommend-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,sCAAsC,CAAA;AAE7C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC5C,CAAC,CAAC,CAAA;QAEF,8CAA8C;QAC9C,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,kBAAkB,CAAC,IAA2B,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACrE,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,kBAAkB,CAAC,SAAgC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC1E,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;YACrD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;YAChE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACrC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;YACvE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;YAC7D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAA;YACzE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAA;YACjE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAA;QACjD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;YACxC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;YACnC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC,CAAA;YAClE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;YACxC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;YACxC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;YACnC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAA;YAC5E,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,MAAM,CAAA;YAC1E,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAClC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;YAClE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC5B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,CAAC,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;YAC9D,MAAM,CAAC,iBAAiB,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,CAAC,iBAAiB,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACtD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjE,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjE,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;YACrE,MAAM,CAAC,iBAAiB,CAAC,SAAS,EAAE,+CAA+C,CAAC,CAAC,CAAC,IAAI,CACxF,IAAI,CACL,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,CAAC,iBAAiB,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YACzE,MAAM,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;YAC1E,MAAM,CAAC,iBAAiB,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC/E,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;YAC9B,MAAM,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,CAAC,mBAAmB,CAAC,CAAC,WAAW,EAAE,CAAA;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrD,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;QACvD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;YAChD,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;YACpD,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QAChD,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Tests for validate.helpers.ts
3
+ * @module @skillsmith/mcp-server/tests/unit/validate-helpers
4
+ *
5
+ * SMI-1719: Unit tests for extracted helper functions from Wave 3 refactor
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=validate-helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-helpers.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/validate-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}