@skillsmith/mcp-server 0.4.13 → 0.5.0

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 (391) hide show
  1. package/CHANGELOG.md +32 -4
  2. package/README.md +89 -13
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/__tests__/compare.test.js +5 -5
  5. package/dist/src/__tests__/compare.test.js.map +1 -1
  6. package/dist/src/__tests__/context-listeners.test.d.ts +12 -0
  7. package/dist/src/__tests__/context-listeners.test.d.ts.map +1 -0
  8. package/dist/src/__tests__/context-listeners.test.js +87 -0
  9. package/dist/src/__tests__/context-listeners.test.js.map +1 -0
  10. package/dist/src/__tests__/context.test.js +84 -82
  11. package/dist/src/__tests__/context.test.js.map +1 -1
  12. package/dist/src/__tests__/get-skill.api-path.test.js +21 -14
  13. package/dist/src/__tests__/get-skill.api-path.test.js.map +1 -1
  14. package/dist/src/__tests__/get-skill.test.js +5 -5
  15. package/dist/src/__tests__/get-skill.test.js.map +1 -1
  16. package/dist/src/__tests__/index-local.test.js +5 -5
  17. package/dist/src/__tests__/index-local.test.js.map +1 -1
  18. package/dist/src/__tests__/recommend-online-path.test.js +5 -5
  19. package/dist/src/__tests__/recommend-online-path.test.js.map +1 -1
  20. package/dist/src/__tests__/recommend.test.js +9 -9
  21. package/dist/src/__tests__/recommend.test.js.map +1 -1
  22. package/dist/src/__tests__/search-compatible-with.test.d.ts +10 -0
  23. package/dist/src/__tests__/search-compatible-with.test.d.ts.map +1 -0
  24. package/dist/src/__tests__/search-compatible-with.test.js +96 -0
  25. package/dist/src/__tests__/search-compatible-with.test.js.map +1 -0
  26. package/dist/src/__tests__/search-online-path.test.js +5 -5
  27. package/dist/src/__tests__/search-online-path.test.js.map +1 -1
  28. package/dist/src/__tests__/search.test.js +16 -101
  29. package/dist/src/__tests__/search.test.js.map +1 -1
  30. package/dist/src/__tests__/test-utils.d.ts +18 -3
  31. package/dist/src/__tests__/test-utils.d.ts.map +1 -1
  32. package/dist/src/__tests__/test-utils.js +24 -7
  33. package/dist/src/__tests__/test-utils.js.map +1 -1
  34. package/dist/src/audit/audit-history.d.ts +77 -0
  35. package/dist/src/audit/audit-history.d.ts.map +1 -0
  36. package/dist/src/audit/audit-history.js +98 -0
  37. package/dist/src/audit/audit-history.js.map +1 -0
  38. package/dist/src/audit/audit-report-writer.d.ts +82 -0
  39. package/dist/src/audit/audit-report-writer.d.ts.map +1 -0
  40. package/dist/src/audit/audit-report-writer.js +241 -0
  41. package/dist/src/audit/audit-report-writer.js.map +1 -0
  42. package/dist/src/audit/audit-suggestions.d.ts +52 -0
  43. package/dist/src/audit/audit-suggestions.d.ts.map +1 -0
  44. package/dist/src/audit/audit-suggestions.js +90 -0
  45. package/dist/src/audit/audit-suggestions.js.map +1 -0
  46. package/dist/src/audit/bootstrap-unmanaged.d.ts +66 -0
  47. package/dist/src/audit/bootstrap-unmanaged.d.ts.map +1 -0
  48. package/dist/src/audit/bootstrap-unmanaged.js +91 -0
  49. package/dist/src/audit/bootstrap-unmanaged.js.map +1 -0
  50. package/dist/src/audit/collision-detector.d.ts +72 -0
  51. package/dist/src/audit/collision-detector.d.ts.map +1 -0
  52. package/dist/src/audit/collision-detector.helpers.d.ts +63 -0
  53. package/dist/src/audit/collision-detector.helpers.d.ts.map +1 -0
  54. package/dist/src/audit/collision-detector.helpers.js +141 -0
  55. package/dist/src/audit/collision-detector.helpers.js.map +1 -0
  56. package/dist/src/audit/collision-detector.js +172 -0
  57. package/dist/src/audit/collision-detector.js.map +1 -0
  58. package/dist/src/audit/collision-detector.semantic.helpers.d.ts +49 -0
  59. package/dist/src/audit/collision-detector.semantic.helpers.d.ts.map +1 -0
  60. package/dist/src/audit/collision-detector.semantic.helpers.js +121 -0
  61. package/dist/src/audit/collision-detector.semantic.helpers.js.map +1 -0
  62. package/dist/src/audit/collision-detector.types.d.ts +70 -0
  63. package/dist/src/audit/collision-detector.types.d.ts.map +1 -0
  64. package/dist/src/audit/collision-detector.types.js +9 -0
  65. package/dist/src/audit/collision-detector.types.js.map +1 -0
  66. package/dist/src/audit/edit-applier.d.ts +64 -0
  67. package/dist/src/audit/edit-applier.d.ts.map +1 -0
  68. package/dist/src/audit/edit-applier.js +233 -0
  69. package/dist/src/audit/edit-applier.js.map +1 -0
  70. package/dist/src/audit/edit-applier.types.d.ts +72 -0
  71. package/dist/src/audit/edit-applier.types.d.ts.map +1 -0
  72. package/dist/src/audit/edit-applier.types.js +13 -0
  73. package/dist/src/audit/edit-applier.types.js.map +1 -0
  74. package/dist/src/audit/edit-suggester.d.ts +63 -0
  75. package/dist/src/audit/edit-suggester.d.ts.map +1 -0
  76. package/dist/src/audit/edit-suggester.js +326 -0
  77. package/dist/src/audit/edit-suggester.js.map +1 -0
  78. package/dist/src/audit/edit-suggester.types.d.ts +148 -0
  79. package/dist/src/audit/edit-suggester.types.d.ts.map +1 -0
  80. package/dist/src/audit/edit-suggester.types.js +17 -0
  81. package/dist/src/audit/edit-suggester.types.js.map +1 -0
  82. package/dist/src/audit/framework-adapter.d.ts +54 -0
  83. package/dist/src/audit/framework-adapter.d.ts.map +1 -0
  84. package/dist/src/audit/framework-adapter.js +251 -0
  85. package/dist/src/audit/framework-adapter.js.map +1 -0
  86. package/dist/src/audit/framework-adapter.types.d.ts +162 -0
  87. package/dist/src/audit/framework-adapter.types.d.ts.map +1 -0
  88. package/dist/src/audit/framework-adapter.types.js +31 -0
  89. package/dist/src/audit/framework-adapter.types.js.map +1 -0
  90. package/dist/src/audit/index.d.ts +46 -0
  91. package/dist/src/audit/index.d.ts.map +1 -0
  92. package/dist/src/audit/index.js +44 -0
  93. package/dist/src/audit/index.js.map +1 -0
  94. package/dist/src/audit/install-preflight.d.ts +99 -0
  95. package/dist/src/audit/install-preflight.d.ts.map +1 -0
  96. package/dist/src/audit/install-preflight.js +320 -0
  97. package/dist/src/audit/install-preflight.js.map +1 -0
  98. package/dist/src/audit/namespace-audit.types.d.ts +100 -0
  99. package/dist/src/audit/namespace-audit.types.d.ts.map +1 -0
  100. package/dist/src/audit/namespace-audit.types.js +20 -0
  101. package/dist/src/audit/namespace-audit.types.js.map +1 -0
  102. package/dist/src/audit/namespace-overrides.d.ts +79 -0
  103. package/dist/src/audit/namespace-overrides.d.ts.map +1 -0
  104. package/dist/src/audit/namespace-overrides.js +228 -0
  105. package/dist/src/audit/namespace-overrides.js.map +1 -0
  106. package/dist/src/audit/namespace-overrides.types.d.ts +115 -0
  107. package/dist/src/audit/namespace-overrides.types.d.ts.map +1 -0
  108. package/dist/src/audit/namespace-overrides.types.js +26 -0
  109. package/dist/src/audit/namespace-overrides.types.js.map +1 -0
  110. package/dist/src/audit/rename-engine.apply-paths.d.ts +54 -0
  111. package/dist/src/audit/rename-engine.apply-paths.d.ts.map +1 -0
  112. package/dist/src/audit/rename-engine.apply-paths.js +126 -0
  113. package/dist/src/audit/rename-engine.apply-paths.js.map +1 -0
  114. package/dist/src/audit/rename-engine.d.ts +59 -0
  115. package/dist/src/audit/rename-engine.d.ts.map +1 -0
  116. package/dist/src/audit/rename-engine.helpers.d.ts +63 -0
  117. package/dist/src/audit/rename-engine.helpers.d.ts.map +1 -0
  118. package/dist/src/audit/rename-engine.helpers.js +224 -0
  119. package/dist/src/audit/rename-engine.helpers.js.map +1 -0
  120. package/dist/src/audit/rename-engine.js +393 -0
  121. package/dist/src/audit/rename-engine.js.map +1 -0
  122. package/dist/src/audit/rename-engine.types.d.ts +157 -0
  123. package/dist/src/audit/rename-engine.types.d.ts.map +1 -0
  124. package/dist/src/audit/rename-engine.types.js +15 -0
  125. package/dist/src/audit/rename-engine.types.js.map +1 -0
  126. package/dist/src/audit/run-inventory-audit.d.ts +95 -0
  127. package/dist/src/audit/run-inventory-audit.d.ts.map +1 -0
  128. package/dist/src/audit/run-inventory-audit.js +245 -0
  129. package/dist/src/audit/run-inventory-audit.js.map +1 -0
  130. package/dist/src/audit/suggestion-chain.d.ts +89 -0
  131. package/dist/src/audit/suggestion-chain.d.ts.map +1 -0
  132. package/dist/src/audit/suggestion-chain.js +121 -0
  133. package/dist/src/audit/suggestion-chain.js.map +1 -0
  134. package/dist/src/audit-tool-dispatch.d.ts +61 -0
  135. package/dist/src/audit-tool-dispatch.d.ts.map +1 -0
  136. package/dist/src/audit-tool-dispatch.js +114 -0
  137. package/dist/src/audit-tool-dispatch.js.map +1 -0
  138. package/dist/src/index.js +1 -1
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/indexer/LocalIndexer.d.ts +8 -1
  141. package/dist/src/indexer/LocalIndexer.d.ts.map +1 -1
  142. package/dist/src/indexer/LocalIndexer.js +42 -38
  143. package/dist/src/indexer/LocalIndexer.js.map +1 -1
  144. package/dist/src/middleware/__tests__/license.gate.test.js +21 -2
  145. package/dist/src/middleware/__tests__/license.gate.test.js.map +1 -1
  146. package/dist/src/middleware/license.gate.d.ts +14 -0
  147. package/dist/src/middleware/license.gate.d.ts.map +1 -1
  148. package/dist/src/middleware/license.gate.js +52 -1
  149. package/dist/src/middleware/license.gate.js.map +1 -1
  150. package/dist/src/onboarding/install-assets.d.ts.map +1 -1
  151. package/dist/src/onboarding/install-assets.js +11 -5
  152. package/dist/src/onboarding/install-assets.js.map +1 -1
  153. package/dist/src/tool-dispatch.d.ts.map +1 -1
  154. package/dist/src/tool-dispatch.js +6 -4
  155. package/dist/src/tool-dispatch.js.map +1 -1
  156. package/dist/src/tools/analytics.service.test.js +6 -4
  157. package/dist/src/tools/analytics.service.test.js.map +1 -1
  158. package/dist/src/tools/apply-namespace-rename.d.ts +71 -0
  159. package/dist/src/tools/apply-namespace-rename.d.ts.map +1 -0
  160. package/dist/src/tools/apply-namespace-rename.js +137 -0
  161. package/dist/src/tools/apply-namespace-rename.js.map +1 -0
  162. package/dist/src/tools/apply-namespace-rename.types.d.ts +46 -0
  163. package/dist/src/tools/apply-namespace-rename.types.d.ts.map +1 -0
  164. package/dist/src/tools/apply-namespace-rename.types.js +9 -0
  165. package/dist/src/tools/apply-namespace-rename.types.js.map +1 -0
  166. package/dist/src/tools/apply-recommended-edit.d.ts +50 -0
  167. package/dist/src/tools/apply-recommended-edit.d.ts.map +1 -0
  168. package/dist/src/tools/apply-recommended-edit.js +112 -0
  169. package/dist/src/tools/apply-recommended-edit.js.map +1 -0
  170. package/dist/src/tools/apply-recommended-edit.types.d.ts +49 -0
  171. package/dist/src/tools/apply-recommended-edit.types.d.ts.map +1 -0
  172. package/dist/src/tools/apply-recommended-edit.types.js +14 -0
  173. package/dist/src/tools/apply-recommended-edit.types.js.map +1 -0
  174. package/dist/src/tools/audit-tools.d.ts +4 -4
  175. package/dist/src/tools/compliance-tools.service.test.js +6 -4
  176. package/dist/src/tools/compliance-tools.service.test.js.map +1 -1
  177. package/dist/src/tools/install.backup-gc.d.ts +61 -0
  178. package/dist/src/tools/install.backup-gc.d.ts.map +1 -0
  179. package/dist/src/tools/install.backup-gc.js +166 -0
  180. package/dist/src/tools/install.backup-gc.js.map +1 -0
  181. package/dist/src/tools/install.conflict-helpers.d.ts +29 -2
  182. package/dist/src/tools/install.conflict-helpers.d.ts.map +1 -1
  183. package/dist/src/tools/install.conflict-helpers.js +37 -4
  184. package/dist/src/tools/install.conflict-helpers.js.map +1 -1
  185. package/dist/src/tools/install.d.ts +15 -1
  186. package/dist/src/tools/install.d.ts.map +1 -1
  187. package/dist/src/tools/install.js +162 -7
  188. package/dist/src/tools/install.js.map +1 -1
  189. package/dist/src/tools/install.ledger-replay.d.ts +52 -0
  190. package/dist/src/tools/install.ledger-replay.d.ts.map +1 -0
  191. package/dist/src/tools/install.ledger-replay.js +88 -0
  192. package/dist/src/tools/install.ledger-replay.js.map +1 -0
  193. package/dist/src/tools/install.namespace-gate.d.ts +68 -0
  194. package/dist/src/tools/install.namespace-gate.d.ts.map +1 -0
  195. package/dist/src/tools/install.namespace-gate.js +129 -0
  196. package/dist/src/tools/install.namespace-gate.js.map +1 -0
  197. package/dist/src/tools/install.test.js +71 -1
  198. package/dist/src/tools/install.test.js.map +1 -1
  199. package/dist/src/tools/install.tool.d.ts +17 -0
  200. package/dist/src/tools/install.tool.d.ts.map +1 -1
  201. package/dist/src/tools/install.tool.js +19 -1
  202. package/dist/src/tools/install.tool.js.map +1 -1
  203. package/dist/src/tools/install.types.d.ts +35 -1
  204. package/dist/src/tools/install.types.d.ts.map +1 -1
  205. package/dist/src/tools/install.types.js +24 -2
  206. package/dist/src/tools/install.types.js.map +1 -1
  207. package/dist/src/tools/namespace-audit/telemetry.d.ts +80 -0
  208. package/dist/src/tools/namespace-audit/telemetry.d.ts.map +1 -0
  209. package/dist/src/tools/namespace-audit/telemetry.js +129 -0
  210. package/dist/src/tools/namespace-audit/telemetry.js.map +1 -0
  211. package/dist/src/tools/outdated.test.js +2 -2
  212. package/dist/src/tools/outdated.test.js.map +1 -1
  213. package/dist/src/tools/publish-private.test.js +2 -2
  214. package/dist/src/tools/publish-private.test.js.map +1 -1
  215. package/dist/src/tools/recommend.types.d.ts +2 -2
  216. package/dist/src/tools/search.d.ts +2 -2
  217. package/dist/src/tools/search.js +3 -3
  218. package/dist/src/tools/search.js.map +1 -1
  219. package/dist/src/tools/skill-audit.test.js +2 -2
  220. package/dist/src/tools/skill-audit.test.js.map +1 -1
  221. package/dist/src/tools/skill-diff.d.ts +1 -1
  222. package/dist/src/tools/skill-inventory-audit.d.ts +67 -0
  223. package/dist/src/tools/skill-inventory-audit.d.ts.map +1 -0
  224. package/dist/src/tools/skill-inventory-audit.js +112 -0
  225. package/dist/src/tools/skill-inventory-audit.js.map +1 -0
  226. package/dist/src/tools/skill-inventory-audit.types.d.ts +67 -0
  227. package/dist/src/tools/skill-inventory-audit.types.d.ts.map +1 -0
  228. package/dist/src/tools/skill-inventory-audit.types.js +14 -0
  229. package/dist/src/tools/skill-inventory-audit.types.js.map +1 -0
  230. package/dist/src/tools/skill-pack-audit.d.ts.map +1 -1
  231. package/dist/src/tools/skill-pack-audit.helpers.d.ts.map +1 -1
  232. package/dist/src/tools/skill-pack-audit.helpers.js +15 -2
  233. package/dist/src/tools/skill-pack-audit.helpers.js.map +1 -1
  234. package/dist/src/tools/skill-pack-audit.js +15 -1
  235. package/dist/src/tools/skill-pack-audit.js.map +1 -1
  236. package/dist/src/tools/skill-rescan.d.ts.map +1 -1
  237. package/dist/src/tools/skill-rescan.js +4 -2
  238. package/dist/src/tools/skill-rescan.js.map +1 -1
  239. package/dist/src/tools/suggest.d.ts +2 -2
  240. package/dist/src/tools/uninstall.d.ts +1 -1
  241. package/dist/src/tools/uninstall.d.ts.map +1 -1
  242. package/dist/src/tools/uninstall.js +17 -3
  243. package/dist/src/tools/uninstall.js.map +1 -1
  244. package/dist/src/tools/validate.types.d.ts +10 -1
  245. package/dist/src/tools/validate.types.d.ts.map +1 -1
  246. package/dist/src/tools/validate.types.js +10 -1
  247. package/dist/src/tools/validate.types.js.map +1 -1
  248. package/dist/src/utils/installed-skills.d.ts.map +1 -1
  249. package/dist/src/utils/installed-skills.js +8 -6
  250. package/dist/src/utils/installed-skills.js.map +1 -1
  251. package/dist/src/utils/local-inventory.d.ts +29 -0
  252. package/dist/src/utils/local-inventory.d.ts.map +1 -0
  253. package/dist/src/utils/local-inventory.helpers.d.ts +96 -0
  254. package/dist/src/utils/local-inventory.helpers.d.ts.map +1 -0
  255. package/dist/src/utils/local-inventory.helpers.js +279 -0
  256. package/dist/src/utils/local-inventory.helpers.js.map +1 -0
  257. package/dist/src/utils/local-inventory.js +202 -0
  258. package/dist/src/utils/local-inventory.js.map +1 -0
  259. package/dist/src/utils/local-inventory.types.d.ts +100 -0
  260. package/dist/src/utils/local-inventory.types.d.ts.map +1 -0
  261. package/dist/src/utils/local-inventory.types.js +9 -0
  262. package/dist/src/utils/local-inventory.types.js.map +1 -0
  263. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts +12 -0
  264. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts.map +1 -1
  265. package/dist/src/webhooks/stripe-webhook-endpoint.js +30 -9
  266. package/dist/src/webhooks/stripe-webhook-endpoint.js.map +1 -1
  267. package/dist/src/webhooks/webhook-endpoint.d.ts +13 -0
  268. package/dist/src/webhooks/webhook-endpoint.d.ts.map +1 -1
  269. package/dist/src/webhooks/webhook-endpoint.js +31 -9
  270. package/dist/src/webhooks/webhook-endpoint.js.map +1 -1
  271. package/dist/tests/compare.test.js +5 -5
  272. package/dist/tests/compare.test.js.map +1 -1
  273. package/dist/tests/context-async-listeners.test.d.ts +12 -0
  274. package/dist/tests/context-async-listeners.test.d.ts.map +1 -0
  275. package/dist/tests/context-async-listeners.test.js +62 -0
  276. package/dist/tests/context-async-listeners.test.js.map +1 -0
  277. package/dist/tests/e2e/compare.e2e.test.js +9 -3
  278. package/dist/tests/e2e/compare.e2e.test.js.map +1 -1
  279. package/dist/tests/e2e/install-flow.e2e.test.js +9 -3
  280. package/dist/tests/e2e/install-flow.e2e.test.js.map +1 -1
  281. package/dist/tests/e2e/recommend.e2e.test.js +9 -3
  282. package/dist/tests/e2e/recommend.e2e.test.js.map +1 -1
  283. package/dist/tests/e2e/skill-flow.e2e.test.js +17 -5
  284. package/dist/tests/e2e/skill-flow.e2e.test.js.map +1 -1
  285. package/dist/tests/e2e/suggest.e2e.test.js +11 -3
  286. package/dist/tests/e2e/suggest.e2e.test.js.map +1 -1
  287. package/dist/tests/integration/audit-roundtrip.test.d.ts +29 -0
  288. package/dist/tests/integration/audit-roundtrip.test.d.ts.map +1 -0
  289. package/dist/tests/integration/audit-roundtrip.test.js +214 -0
  290. package/dist/tests/integration/audit-roundtrip.test.js.map +1 -0
  291. package/dist/tests/integration/install-namespace.integration.test.d.ts +14 -0
  292. package/dist/tests/integration/install-namespace.integration.test.d.ts.map +1 -0
  293. package/dist/tests/integration/install-namespace.integration.test.js +414 -0
  294. package/dist/tests/integration/install-namespace.integration.test.js.map +1 -0
  295. package/dist/tests/performance/search-performance.test.js +9 -3
  296. package/dist/tests/performance/search-performance.test.js.map +1 -1
  297. package/dist/tests/tools.test.js +5 -5
  298. package/dist/tests/tools.test.js.map +1 -1
  299. package/dist/tests/unit/apply-namespace-rename.test.d.ts +24 -0
  300. package/dist/tests/unit/apply-namespace-rename.test.d.ts.map +1 -0
  301. package/dist/tests/unit/apply-namespace-rename.test.js +205 -0
  302. package/dist/tests/unit/apply-namespace-rename.test.js.map +1 -0
  303. package/dist/tests/unit/apply-recommended-edit.test.d.ts +28 -0
  304. package/dist/tests/unit/apply-recommended-edit.test.d.ts.map +1 -0
  305. package/dist/tests/unit/apply-recommended-edit.test.js +229 -0
  306. package/dist/tests/unit/apply-recommended-edit.test.js.map +1 -0
  307. package/dist/tests/unit/audit-history.test.d.ts +11 -0
  308. package/dist/tests/unit/audit-history.test.d.ts.map +1 -0
  309. package/dist/tests/unit/audit-history.test.js +183 -0
  310. package/dist/tests/unit/audit-history.test.js.map +1 -0
  311. package/dist/tests/unit/audit-report-writer.test.d.ts +7 -0
  312. package/dist/tests/unit/audit-report-writer.test.d.ts.map +1 -0
  313. package/dist/tests/unit/audit-report-writer.test.js +249 -0
  314. package/dist/tests/unit/audit-report-writer.test.js.map +1 -0
  315. package/dist/tests/unit/audit-tool-dispatch.test.d.ts +17 -0
  316. package/dist/tests/unit/audit-tool-dispatch.test.d.ts.map +1 -0
  317. package/dist/tests/unit/audit-tool-dispatch.test.js +133 -0
  318. package/dist/tests/unit/audit-tool-dispatch.test.js.map +1 -0
  319. package/dist/tests/unit/collision-detector.semantic.test.d.ts +12 -0
  320. package/dist/tests/unit/collision-detector.semantic.test.d.ts.map +1 -0
  321. package/dist/tests/unit/collision-detector.semantic.test.js +281 -0
  322. package/dist/tests/unit/collision-detector.semantic.test.js.map +1 -0
  323. package/dist/tests/unit/collision-detector.test.d.ts +8 -0
  324. package/dist/tests/unit/collision-detector.test.d.ts.map +1 -0
  325. package/dist/tests/unit/collision-detector.test.js +266 -0
  326. package/dist/tests/unit/collision-detector.test.js.map +1 -0
  327. package/dist/tests/unit/edit-applier.test.d.ts +17 -0
  328. package/dist/tests/unit/edit-applier.test.d.ts.map +1 -0
  329. package/dist/tests/unit/edit-applier.test.js +165 -0
  330. package/dist/tests/unit/edit-applier.test.js.map +1 -0
  331. package/dist/tests/unit/edit-suggester.fixtures.d.ts +38 -0
  332. package/dist/tests/unit/edit-suggester.fixtures.d.ts.map +1 -0
  333. package/dist/tests/unit/edit-suggester.fixtures.js +84 -0
  334. package/dist/tests/unit/edit-suggester.fixtures.js.map +1 -0
  335. package/dist/tests/unit/edit-suggester.test.d.ts +17 -0
  336. package/dist/tests/unit/edit-suggester.test.d.ts.map +1 -0
  337. package/dist/tests/unit/edit-suggester.test.js +356 -0
  338. package/dist/tests/unit/edit-suggester.test.js.map +1 -0
  339. package/dist/tests/unit/framework-adapter.test.d.ts +30 -0
  340. package/dist/tests/unit/framework-adapter.test.d.ts.map +1 -0
  341. package/dist/tests/unit/framework-adapter.test.js +221 -0
  342. package/dist/tests/unit/framework-adapter.test.js.map +1 -0
  343. package/dist/tests/unit/install-preflight.test.d.ts +17 -0
  344. package/dist/tests/unit/install-preflight.test.d.ts.map +1 -0
  345. package/dist/tests/unit/install-preflight.test.js +270 -0
  346. package/dist/tests/unit/install-preflight.test.js.map +1 -0
  347. package/dist/tests/unit/install.backup-gc.test.d.ts +18 -0
  348. package/dist/tests/unit/install.backup-gc.test.d.ts.map +1 -0
  349. package/dist/tests/unit/install.backup-gc.test.js +177 -0
  350. package/dist/tests/unit/install.backup-gc.test.js.map +1 -0
  351. package/dist/tests/unit/install.ledger-replay.test.d.ts +12 -0
  352. package/dist/tests/unit/install.ledger-replay.test.d.ts.map +1 -0
  353. package/dist/tests/unit/install.ledger-replay.test.js +98 -0
  354. package/dist/tests/unit/install.ledger-replay.test.js.map +1 -0
  355. package/dist/tests/unit/local-inventory.test.d.ts +8 -0
  356. package/dist/tests/unit/local-inventory.test.d.ts.map +1 -0
  357. package/dist/tests/unit/local-inventory.test.js +165 -0
  358. package/dist/tests/unit/local-inventory.test.js.map +1 -0
  359. package/dist/tests/unit/namespace-audit-telemetry.test.d.ts +10 -0
  360. package/dist/tests/unit/namespace-audit-telemetry.test.d.ts.map +1 -0
  361. package/dist/tests/unit/namespace-audit-telemetry.test.js +215 -0
  362. package/dist/tests/unit/namespace-audit-telemetry.test.js.map +1 -0
  363. package/dist/tests/unit/namespace-overrides.test.d.ts +18 -0
  364. package/dist/tests/unit/namespace-overrides.test.d.ts.map +1 -0
  365. package/dist/tests/unit/namespace-overrides.test.js +210 -0
  366. package/dist/tests/unit/namespace-overrides.test.js.map +1 -0
  367. package/dist/tests/unit/rename-engine.test.d.ts +26 -0
  368. package/dist/tests/unit/rename-engine.test.d.ts.map +1 -0
  369. package/dist/tests/unit/rename-engine.test.js +367 -0
  370. package/dist/tests/unit/rename-engine.test.js.map +1 -0
  371. package/dist/tests/unit/skill-inventory-audit.test.d.ts +20 -0
  372. package/dist/tests/unit/skill-inventory-audit.test.d.ts.map +1 -0
  373. package/dist/tests/unit/skill-inventory-audit.test.js +299 -0
  374. package/dist/tests/unit/skill-inventory-audit.test.js.map +1 -0
  375. package/dist/tests/unit/skill-pack-audit.helpers.test.d.ts +11 -0
  376. package/dist/tests/unit/skill-pack-audit.helpers.test.d.ts.map +1 -0
  377. package/dist/tests/unit/skill-pack-audit.helpers.test.js +61 -0
  378. package/dist/tests/unit/skill-pack-audit.helpers.test.js.map +1 -0
  379. package/dist/tests/unit/skill-pack-audit.test.js +1 -1
  380. package/dist/tests/unit/skill-pack-audit.test.js.map +1 -1
  381. package/dist/tests/unit/suggestion-chain.test.d.ts +17 -0
  382. package/dist/tests/unit/suggestion-chain.test.d.ts.map +1 -0
  383. package/dist/tests/unit/suggestion-chain.test.js +191 -0
  384. package/dist/tests/unit/suggestion-chain.test.js.map +1 -0
  385. package/dist/tests/webhooks/standalone-shutdown.test.d.ts +12 -0
  386. package/dist/tests/webhooks/standalone-shutdown.test.d.ts.map +1 -0
  387. package/dist/tests/webhooks/standalone-shutdown.test.js +91 -0
  388. package/dist/tests/webhooks/standalone-shutdown.test.js.map +1 -0
  389. package/package.json +17 -4
  390. package/server.json +3 -3
  391. package/src/assets/skills/skillsmith/SKILL.md +2 -2
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Unit tests for SMI-4588 Wave 2 Step 5 — install pre-flight namespace check.
3
+ * PR #3 of the Wave 2 stack.
4
+ *
5
+ * Coverage (8+ cases per the work plan):
6
+ * 1. No collision → empty warnings, no pendingCollision, valid auditId.
7
+ * 2. Exact collision in `preventative` mode → returns pendingCollision
8
+ * with `chainExhausted: false`.
9
+ * 3. Exact collision in `power_user` mode → returns warnings[] (one entry).
10
+ * 4. Generic collision in `governance` mode → returns warnings[].
11
+ * 5. Pre-flight failure (bad inventory entry) → degraded shape (non-blocking).
12
+ * 6. `auditId` is bubbled and matches the audit-history entry written.
13
+ * 7. All 3 chain candidates collide → `chainExhausted: true`.
14
+ * 8. Audit-history persisted on every call (zero-flag and collision paths).
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=install-preflight.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-preflight.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/install-preflight.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Unit tests for SMI-4588 Wave 2 Step 5 — install pre-flight namespace check.
3
+ * PR #3 of the Wave 2 stack.
4
+ *
5
+ * Coverage (8+ cases per the work plan):
6
+ * 1. No collision → empty warnings, no pendingCollision, valid auditId.
7
+ * 2. Exact collision in `preventative` mode → returns pendingCollision
8
+ * with `chainExhausted: false`.
9
+ * 3. Exact collision in `power_user` mode → returns warnings[] (one entry).
10
+ * 4. Generic collision in `governance` mode → returns warnings[].
11
+ * 5. Pre-flight failure (bad inventory entry) → degraded shape (non-blocking).
12
+ * 6. `auditId` is bubbled and matches the audit-history entry written.
13
+ * 7. All 3 chain candidates collide → `chainExhausted: true`.
14
+ * 8. Audit-history persisted on every call (zero-flag and collision paths).
15
+ */
16
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
17
+ import * as fs from 'node:fs';
18
+ import * as os from 'node:os';
19
+ import * as path from 'node:path';
20
+ import { runInstallPreflight } from '../../src/audit/install-preflight.js';
21
+ import * as auditHistoryModule from '../../src/audit/audit-history.js';
22
+ let TEST_HOME;
23
+ let ORIGINAL_HOME;
24
+ beforeEach(() => {
25
+ TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'skillsmith-preflight-'));
26
+ ORIGINAL_HOME = process.env['HOME'];
27
+ process.env['HOME'] = TEST_HOME;
28
+ // Stub fetch — `detectCollisions` fires aggregate-only telemetry; tests
29
+ // never make network calls. Mirrors collision-detector.test.ts.
30
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { status: 200 })));
31
+ });
32
+ afterEach(() => {
33
+ if (ORIGINAL_HOME !== undefined) {
34
+ process.env['HOME'] = ORIGINAL_HOME;
35
+ }
36
+ else {
37
+ delete process.env['HOME'];
38
+ }
39
+ if (TEST_HOME && fs.existsSync(TEST_HOME)) {
40
+ fs.rmSync(TEST_HOME, { recursive: true, force: true });
41
+ }
42
+ vi.unstubAllGlobals();
43
+ vi.restoreAllMocks();
44
+ });
45
+ function entry(overrides) {
46
+ return {
47
+ kind: 'skill',
48
+ source_path: '/tmp/SKILL.md',
49
+ identifier: 'noop',
50
+ triggerSurface: ['noop'],
51
+ ...overrides,
52
+ };
53
+ }
54
+ function candidate(overrides = {}) {
55
+ return {
56
+ identifier: 'code-helper',
57
+ projectedSourcePath: path.join(TEST_HOME, '.claude', 'skills', 'code-helper'),
58
+ skillId: 'anthropic/code-helper',
59
+ author: 'anthropic',
60
+ ...overrides,
61
+ };
62
+ }
63
+ describe('runInstallPreflight', () => {
64
+ it('returns empty warnings + null pendingCollision when no collision (case 1)', async () => {
65
+ const result = await runInstallPreflight({
66
+ existingInventory: [entry({ identifier: 'unrelated' })],
67
+ candidate: candidate(),
68
+ mode: 'preventative',
69
+ tier: 'community',
70
+ });
71
+ expect(result.warnings).toEqual([]);
72
+ expect(result.pendingCollision).toBeNull();
73
+ // ULID shape — bubbled from the detector through the result.
74
+ expect(result.auditId).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/);
75
+ });
76
+ it('returns pendingCollision on exact collision in preventative mode (case 2)', async () => {
77
+ const c = candidate();
78
+ const existing = entry({
79
+ kind: 'skill',
80
+ identifier: 'code-helper',
81
+ source_path: path.join(TEST_HOME, '.claude', 'skills', 'other', 'SKILL.md'),
82
+ });
83
+ const result = await runInstallPreflight({
84
+ existingInventory: [existing],
85
+ candidate: c,
86
+ mode: 'preventative',
87
+ tier: 'community',
88
+ });
89
+ expect(result.pendingCollision).not.toBeNull();
90
+ const pending = result.pendingCollision;
91
+ expect(pending.auditId).toBe(result.auditId);
92
+ expect(pending.chainExhausted).toBe(false);
93
+ expect(pending.suggestionChain.length).toBeGreaterThan(0);
94
+ // First non-colliding candidate is `anthropic-code-helper` (chain tier 1).
95
+ expect(pending.suggestedRename.suggested).toBe('anthropic-code-helper');
96
+ expect(pending.suggestedRename.currentName).toBe('code-helper');
97
+ expect(pending.remediationHint).toMatch(/apply_namespace_rename/);
98
+ // `warnings[]` is also populated even in preventative — caller decides
99
+ // whether to surface them, but the shape is non-blocking either way.
100
+ expect(result.warnings.length).toBeGreaterThan(0);
101
+ expect(result.warnings[0]?.kind).toBe('exact');
102
+ });
103
+ it('returns warnings[] on exact collision in power_user mode (case 3)', async () => {
104
+ const existing = entry({
105
+ identifier: 'code-helper',
106
+ source_path: path.join(TEST_HOME, '.claude', 'skills', 'sibling', 'SKILL.md'),
107
+ });
108
+ const result = await runInstallPreflight({
109
+ existingInventory: [existing],
110
+ candidate: candidate(),
111
+ mode: 'power_user',
112
+ tier: 'team',
113
+ });
114
+ // The pre-flight returns BOTH shapes regardless of mode; the gate in
115
+ // install.ts decides which to surface. power_user mode in install
116
+ // surfaces only `warnings`.
117
+ expect(result.warnings.length).toBeGreaterThan(0);
118
+ expect(result.warnings[0]?.severity).toBe('warning');
119
+ expect(result.warnings[0]?.auditId).toBe(result.auditId);
120
+ expect(result.pendingCollision).not.toBeNull();
121
+ });
122
+ it('returns warnings[] on generic-token flag in governance mode (case 4)', async () => {
123
+ // The candidate `review` is in core's GENERIC_TRIGGERS list; planting
124
+ // it in the existing inventory ensures the generic-token pass fires
125
+ // for the candidate too.
126
+ const existing = entry({
127
+ identifier: 'review',
128
+ source_path: path.join(TEST_HOME, '.claude', 'skills', 'old-review', 'SKILL.md'),
129
+ });
130
+ const result = await runInstallPreflight({
131
+ existingInventory: [existing],
132
+ candidate: candidate({ identifier: 'review', skillId: 'anthropic/review' }),
133
+ mode: 'governance',
134
+ tier: 'enterprise',
135
+ });
136
+ // Either the exact pass OR the generic pass produces a warning. Both
137
+ // are valid shapes here — the candidate `review` is generic AND
138
+ // collides with the planted entry. Assert at least one warning came back.
139
+ expect(result.warnings.length).toBeGreaterThan(0);
140
+ const kinds = new Set(result.warnings.map((w) => w.kind));
141
+ expect(kinds.has('exact') || kinds.has('generic')).toBe(true);
142
+ });
143
+ it('degrades to non-blocking shape when scanLocalInventory throws (case 5)', async () => {
144
+ // Force the detector to throw by passing a non-array `existingInventory`
145
+ // through a deliberate cast. The pre-flight catches the throw and
146
+ // returns the degraded shape (Edit 2).
147
+ const result = await runInstallPreflight({
148
+ existingInventory: null,
149
+ candidate: candidate(),
150
+ mode: 'preventative',
151
+ tier: 'community',
152
+ });
153
+ expect(result.warnings).toEqual([]);
154
+ expect(result.pendingCollision).toBeNull();
155
+ expect(result.auditId).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/);
156
+ });
157
+ it('persists audit history with the bubbled auditId (case 6)', async () => {
158
+ // The audit-history module captures `os.homedir()` at module load, so
159
+ // we spy on the writer rather than asserting on a TEST_HOME path. The
160
+ // assertion is: a write was attempted with a result whose `auditId`
161
+ // matches the returned `auditId`.
162
+ //
163
+ // NOTE: install-preflight.ts imports `writeAuditHistory` directly via
164
+ // a static binding, which means a vi.spyOn on the re-export here is
165
+ // unreliable across ESM bundlers. The pragmatic test asserts on a
166
+ // throwable detector path instead — see case 8 for the alternate
167
+ // verification (writeAuditHistory cannot fail the install). Here we
168
+ // assert the auditId shape + bubbling, which is the load-bearing
169
+ // contract for the agent's later `apply_namespace_rename` lookup.
170
+ const result = await runInstallPreflight({
171
+ existingInventory: [entry({ identifier: 'unrelated' })],
172
+ candidate: candidate(),
173
+ mode: 'power_user',
174
+ tier: 'team',
175
+ });
176
+ expect(result.auditId).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/);
177
+ // The auditId on warnings (when present) must match the top-level
178
+ // auditId — this is the "bubbled explicitly" guarantee of Edit 7.
179
+ for (const w of result.warnings) {
180
+ expect(w.auditId).toBe(result.auditId);
181
+ }
182
+ });
183
+ it('flags chainExhausted when all chain candidates collide (case 7)', async () => {
184
+ // Plant the entire chain (tier 1, tier 2, tier 3) in the inventory so
185
+ // `generateSuggestionChain` exhausts every candidate. The shortHash is
186
+ // deterministic for a given (authorPath, token, packDomain), so we can
187
+ // pre-compute the tier-3 candidate and plant it.
188
+ const c = candidate({ identifier: 'ship', skillId: 'anthropic/ship', author: 'anthropic' });
189
+ // Tier 1 = 'anthropic-ship'. Tier 2/3 require packDomain — without it,
190
+ // the chain dedupes to a single candidate (tier 3 with no pack-domain).
191
+ // Plant the candidate plus the tier-1 collision to force exhaustion of
192
+ // the unique candidates.
193
+ const existing = [
194
+ // The collision with the candidate itself.
195
+ entry({
196
+ identifier: 'ship',
197
+ source_path: path.join(TEST_HOME, '.claude', 'skills', 'sibling', 'SKILL.md'),
198
+ }),
199
+ // tier 1 collision
200
+ entry({
201
+ identifier: 'anthropic-ship',
202
+ source_path: path.join(TEST_HOME, '.claude', 'skills', 'a1', 'SKILL.md'),
203
+ }),
204
+ ];
205
+ // Compute tier 3 hash (no packDomain → tier3 = `anthropic-ship-${hash4}`).
206
+ const crypto = await import('node:crypto');
207
+ const tokenInput = `${c.projectedSourcePath}/ship/`;
208
+ const hash4 = crypto.createHash('sha256').update(tokenInput).digest('hex').slice(0, 4);
209
+ existing.push(entry({
210
+ identifier: `anthropic-ship-${hash4}`,
211
+ source_path: path.join(TEST_HOME, '.claude', 'skills', 'a3', 'SKILL.md'),
212
+ }));
213
+ const result = await runInstallPreflight({
214
+ existingInventory: existing,
215
+ candidate: c,
216
+ mode: 'preventative',
217
+ tier: 'community',
218
+ });
219
+ expect(result.pendingCollision).not.toBeNull();
220
+ expect(result.pendingCollision?.chainExhausted).toBe(true);
221
+ });
222
+ it('does NOT flag a reinstall as a self-collision (governance-fix regression)', async () => {
223
+ // Reinstall scenario: the candidate skill is already installed at the
224
+ // projected location. `scanLocalInventory` returns an entry for it; the
225
+ // pre-flight must EXCLUDE that prior copy from the augmented inventory
226
+ // so `detectExactCollisions` doesn't surface a self-collision and
227
+ // block reinstall in `preventative` mode.
228
+ const c = candidate({ identifier: 'code-helper' });
229
+ const priorInstall = entry({
230
+ kind: 'skill',
231
+ identifier: 'code-helper',
232
+ // Mirrors the scanner shape: <projectedDir>/SKILL.md
233
+ source_path: path.join(c.projectedSourcePath, 'SKILL.md'),
234
+ });
235
+ const result = await runInstallPreflight({
236
+ existingInventory: [priorInstall],
237
+ candidate: c,
238
+ mode: 'preventative',
239
+ tier: 'community',
240
+ });
241
+ expect(result.warnings).toEqual([]);
242
+ expect(result.pendingCollision).toBeNull();
243
+ });
244
+ it('continues non-blocking when writeAuditHistory fails (case 8)', async () => {
245
+ // Edit 2 — pre-flight is non-blocking. A failed audit-history write
246
+ // (e.g. permissions-denied on `~/.skillsmith/audits/`) must not surface
247
+ // as a thrown error to the install caller. Spy on the writer and force
248
+ // it to throw; assert the result still resolves cleanly.
249
+ const writeSpy = vi
250
+ .spyOn(auditHistoryModule, 'writeAuditHistory')
251
+ .mockRejectedValue(new Error('EACCES'));
252
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
253
+ const result = await runInstallPreflight({
254
+ existingInventory: [],
255
+ candidate: candidate(),
256
+ mode: 'preventative',
257
+ tier: 'community',
258
+ });
259
+ expect(result.warnings).toEqual([]);
260
+ expect(result.pendingCollision).toBeNull();
261
+ expect(result.auditId).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/);
262
+ // Vitest spy may not intercept a static binding; the fallback assertion
263
+ // is that the runner did not throw. When the spy DID intercept, assert
264
+ // the warning was logged.
265
+ if (writeSpy.mock.calls.length > 0) {
266
+ expect(warnSpy).toHaveBeenCalled();
267
+ }
268
+ });
269
+ });
270
+ //# sourceMappingURL=install-preflight.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-preflight.test.js","sourceRoot":"","sources":["../../../tests/unit/install-preflight.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Unit tests for SMI-4588 Wave 2 Step 9 — backup garbage collector.
3
+ * PR #4 of the Wave 2 stack.
4
+ *
5
+ * Coverage (per plan §1 "decision #10" + Edit 4):
6
+ * 1. Old backup directory removed; recent backup retained.
7
+ * 2. Malformed-timestamp directory skipped (NOT removed) and surrounding
8
+ * valid-but-expired entries still GC'd.
9
+ * 3. Missing backups root → no-op success (no throw).
10
+ * 4. Concurrent runs idempotent — second invocation completes without
11
+ * throwing on already-removed directories.
12
+ * 5. `.original` directory is preserved (carve-out matching
13
+ * `cleanupOldBackups` in install.conflict-helpers.ts).
14
+ * 6. Retention env clamping — value > 365 clamped to 365; < 1 clamped to 1.
15
+ * 7. Custom `retentionDays` option overrides env.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=install.backup-gc.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.backup-gc.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/install.backup-gc.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Unit tests for SMI-4588 Wave 2 Step 9 — backup garbage collector.
3
+ * PR #4 of the Wave 2 stack.
4
+ *
5
+ * Coverage (per plan §1 "decision #10" + Edit 4):
6
+ * 1. Old backup directory removed; recent backup retained.
7
+ * 2. Malformed-timestamp directory skipped (NOT removed) and surrounding
8
+ * valid-but-expired entries still GC'd.
9
+ * 3. Missing backups root → no-op success (no throw).
10
+ * 4. Concurrent runs idempotent — second invocation completes without
11
+ * throwing on already-removed directories.
12
+ * 5. `.original` directory is preserved (carve-out matching
13
+ * `cleanupOldBackups` in install.conflict-helpers.ts).
14
+ * 6. Retention env clamping — value > 365 clamped to 365; < 1 clamped to 1.
15
+ * 7. Custom `retentionDays` option overrides env.
16
+ */
17
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
18
+ import * as fs from 'node:fs';
19
+ import * as fsp from 'node:fs/promises';
20
+ import * as os from 'node:os';
21
+ import * as path from 'node:path';
22
+ import { runBackupGC } from '../../src/tools/install.backup-gc.js';
23
+ let TEST_HOME;
24
+ let ORIGINAL_HOME;
25
+ let ORIGINAL_RETENTION;
26
+ let BACKUPS_ROOT;
27
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
28
+ beforeEach(() => {
29
+ TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'skillsmith-backup-gc-'));
30
+ ORIGINAL_HOME = process.env['HOME'];
31
+ ORIGINAL_RETENTION = process.env['SKILLSMITH_BACKUP_RETENTION_DAYS'];
32
+ process.env['HOME'] = TEST_HOME;
33
+ delete process.env['SKILLSMITH_BACKUP_RETENTION_DAYS'];
34
+ BACKUPS_ROOT = path.join(TEST_HOME, '.claude', 'skills', '.backups');
35
+ });
36
+ afterEach(() => {
37
+ if (ORIGINAL_HOME !== undefined) {
38
+ process.env['HOME'] = ORIGINAL_HOME;
39
+ }
40
+ else {
41
+ delete process.env['HOME'];
42
+ }
43
+ if (ORIGINAL_RETENTION !== undefined) {
44
+ process.env['SKILLSMITH_BACKUP_RETENTION_DAYS'] = ORIGINAL_RETENTION;
45
+ }
46
+ else {
47
+ delete process.env['SKILLSMITH_BACKUP_RETENTION_DAYS'];
48
+ }
49
+ if (TEST_HOME && fs.existsSync(TEST_HOME)) {
50
+ fs.rmSync(TEST_HOME, { recursive: true, force: true });
51
+ }
52
+ vi.restoreAllMocks();
53
+ });
54
+ /**
55
+ * Build a backup leaf directory matching `createSkillBackup`'s naming:
56
+ * `<getBackupsDir()>/<skillName>/<ISO-with-:.replaced-with->_<reason>/`.
57
+ */
58
+ function makeBackup(skillName, when, reason) {
59
+ const ts = when.toISOString().replace(/[:.]/g, '-');
60
+ const dir = path.join(BACKUPS_ROOT, skillName, `${ts}_${reason}`);
61
+ fs.mkdirSync(dir, { recursive: true });
62
+ fs.writeFileSync(path.join(dir, 'SKILL.md'), '# backed up\n', 'utf-8');
63
+ return dir;
64
+ }
65
+ describe('runBackupGC', () => {
66
+ it('removes old backup, retains recent backup (case 1)', async () => {
67
+ const now = new Date('2026-05-01T12:00:00.000Z');
68
+ const oldWhen = new Date(now.getTime() - 30 * MS_PER_DAY);
69
+ const recentWhen = new Date(now.getTime() - 1 * MS_PER_DAY);
70
+ const oldDir = makeBackup('anthropic-ship', oldWhen, 'namespace-rename');
71
+ const recentDir = makeBackup('anthropic-ship', recentWhen, 'namespace-rename');
72
+ const result = await runBackupGC({ backupsDir: BACKUPS_ROOT, retentionDays: 14, now });
73
+ expect(result.removed).toBe(1);
74
+ expect(result.kept).toBe(1);
75
+ expect(result.skipped).toBe(0);
76
+ expect(fs.existsSync(oldDir)).toBe(false);
77
+ expect(fs.existsSync(recentDir)).toBe(true);
78
+ });
79
+ it('skips malformed-timestamp directories without removing them; valid expired entries still GC (case 2 — Edit 4 explicit edge)', async () => {
80
+ const now = new Date('2026-05-01T12:00:00.000Z');
81
+ const oldWhen = new Date(now.getTime() - 60 * MS_PER_DAY);
82
+ const oldDir = makeBackup('anthropic-ship', oldWhen, 'namespace-rename');
83
+ // Malformed: no timestamp prefix at all.
84
+ const malformed1 = path.join(BACKUPS_ROOT, 'anthropic-ship', 'notatimestamp_reason');
85
+ fs.mkdirSync(malformed1, { recursive: true });
86
+ fs.writeFileSync(path.join(malformed1, 'SKILL.md'), 'manual user dir\n', 'utf-8');
87
+ // Malformed: empty timestamp prefix (`_no-timestamp`).
88
+ const malformed2 = path.join(BACKUPS_ROOT, 'anthropic-ship', '_no-timestamp');
89
+ fs.mkdirSync(malformed2, { recursive: true });
90
+ // Malformed: missing `_<reason>` separator entirely.
91
+ const malformed3 = path.join(BACKUPS_ROOT, 'anthropic-ship', '2026-04-01T15-42-18-331Z');
92
+ fs.mkdirSync(malformed3, { recursive: true });
93
+ // Suppress the expected console.warn lines.
94
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
95
+ const result = await runBackupGC({ backupsDir: BACKUPS_ROOT, retentionDays: 14, now });
96
+ expect(result.removed).toBe(1);
97
+ expect(result.skipped).toBe(3);
98
+ expect(result.kept).toBe(0);
99
+ expect(fs.existsSync(oldDir)).toBe(false);
100
+ expect(fs.existsSync(malformed1)).toBe(true);
101
+ expect(fs.existsSync(malformed2)).toBe(true);
102
+ expect(fs.existsSync(malformed3)).toBe(true);
103
+ // At least one warn fired per malformed dir.
104
+ expect(warnSpy).toHaveBeenCalled();
105
+ });
106
+ it('returns no-op success when backup root does not exist (case 3)', async () => {
107
+ // No filesystem state created — backups dir missing.
108
+ const missing = path.join(TEST_HOME, 'nope', '.backups');
109
+ const result = await runBackupGC({ backupsDir: missing, retentionDays: 14 });
110
+ expect(result).toEqual({ removed: 0, kept: 0, skipped: 0 });
111
+ });
112
+ it('is idempotent on repeat invocation (case 4)', async () => {
113
+ const now = new Date('2026-05-01T12:00:00.000Z');
114
+ const oldWhen = new Date(now.getTime() - 30 * MS_PER_DAY);
115
+ makeBackup('anthropic-ship', oldWhen, 'namespace-rename');
116
+ const first = await runBackupGC({ backupsDir: BACKUPS_ROOT, retentionDays: 14, now });
117
+ expect(first.removed).toBe(1);
118
+ // Second run finds no expired entries — clean state.
119
+ const second = await runBackupGC({ backupsDir: BACKUPS_ROOT, retentionDays: 14, now });
120
+ expect(second.removed).toBe(0);
121
+ expect(second.kept).toBe(0);
122
+ expect(second.skipped).toBe(0);
123
+ });
124
+ it('preserves the .original directory regardless of age (case 5)', async () => {
125
+ const now = new Date('2026-05-01T12:00:00.000Z');
126
+ const oldWhen = new Date(now.getTime() - 90 * MS_PER_DAY);
127
+ // Plant an `.original` directory (used by storeOriginal()).
128
+ const originalDir = path.join(BACKUPS_ROOT, 'anthropic-ship', '.original');
129
+ fs.mkdirSync(originalDir, { recursive: true });
130
+ fs.writeFileSync(path.join(originalDir, 'SKILL.md'), '# pristine\n');
131
+ // And one expired entry to confirm the sweep still runs.
132
+ const expired = makeBackup('anthropic-ship', oldWhen, 'namespace-rename');
133
+ const result = await runBackupGC({ backupsDir: BACKUPS_ROOT, retentionDays: 14, now });
134
+ expect(result.removed).toBe(1);
135
+ expect(result.kept).toBe(1); // .original counted as kept
136
+ expect(fs.existsSync(originalDir)).toBe(true);
137
+ expect(fs.existsSync(expired)).toBe(false);
138
+ });
139
+ it('clamps retention env value to [1, 365] (case 6)', async () => {
140
+ const now = new Date('2026-05-01T12:00:00.000Z');
141
+ // Backup is 400 days old — outside the upper bound (365).
142
+ const veryOld = new Date(now.getTime() - 400 * MS_PER_DAY);
143
+ const dir = makeBackup('anthropic-ship', veryOld, 'namespace-rename');
144
+ process.env['SKILLSMITH_BACKUP_RETENTION_DAYS'] = '99999';
145
+ const result = await runBackupGC({ backupsDir: BACKUPS_ROOT, now });
146
+ // Clamped to 365 days; backup is older → removed.
147
+ expect(result.removed).toBe(1);
148
+ expect(fs.existsSync(dir)).toBe(false);
149
+ });
150
+ it('respects custom `retentionDays` option over env (case 7)', async () => {
151
+ const now = new Date('2026-05-01T12:00:00.000Z');
152
+ const oldWhen = new Date(now.getTime() - 5 * MS_PER_DAY);
153
+ const dir = makeBackup('anthropic-ship', oldWhen, 'namespace-rename');
154
+ // Env says 30 days; option override says 1 day → 5-day-old should GC.
155
+ process.env['SKILLSMITH_BACKUP_RETENTION_DAYS'] = '30';
156
+ const result = await runBackupGC({ backupsDir: BACKUPS_ROOT, retentionDays: 1, now });
157
+ expect(result.removed).toBe(1);
158
+ expect(fs.existsSync(dir)).toBe(false);
159
+ });
160
+ it('uses default 14-day retention when env unset (case 8)', async () => {
161
+ const now = new Date('2026-05-01T12:00:00.000Z');
162
+ // 13 days old — within default window.
163
+ const within = makeBackup('anthropic-ship', new Date(now.getTime() - 13 * MS_PER_DAY), 'namespace-rename');
164
+ // 15 days old — outside default window.
165
+ const outside = makeBackup('anthropic-ship', new Date(now.getTime() - 15 * MS_PER_DAY), 'namespace-rename');
166
+ const result = await runBackupGC({ backupsDir: BACKUPS_ROOT, now });
167
+ expect(result.removed).toBe(1);
168
+ expect(result.kept).toBe(1);
169
+ expect(fs.existsSync(within)).toBe(true);
170
+ expect(fs.existsSync(outside)).toBe(false);
171
+ // Defensive: make sure the writer's path matches what the GC scans.
172
+ // (Plan §"Critical surface ground-truth": the canonical
173
+ // ~/.claude/skills/.backups path.)
174
+ await fsp.access(BACKUPS_ROOT);
175
+ });
176
+ });
177
+ //# sourceMappingURL=install.backup-gc.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.backup-gc.test.js","sourceRoot":"","sources":["../../../tests/unit/install.backup-gc.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unit tests for SMI-4588 Wave 2 Step 6 — install ledger-replay rewriter.
3
+ * PR #3 of the Wave 2 stack.
4
+ *
5
+ * Coverage:
6
+ * 1. Empty ledger → candidate returned unchanged (reference-equal).
7
+ * 2. No matching ledger entry → candidate returned unchanged.
8
+ * 3. Single matching entry → candidate identifier + path rewritten.
9
+ * 4. Multi-pass replay (chained renames) — applies until no more matches.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=install.ledger-replay.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.ledger-replay.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/install.ledger-replay.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Unit tests for SMI-4588 Wave 2 Step 6 — install ledger-replay rewriter.
3
+ * PR #3 of the Wave 2 stack.
4
+ *
5
+ * Coverage:
6
+ * 1. Empty ledger → candidate returned unchanged (reference-equal).
7
+ * 2. No matching ledger entry → candidate returned unchanged.
8
+ * 3. Single matching entry → candidate identifier + path rewritten.
9
+ * 4. Multi-pass replay (chained renames) — applies until no more matches.
10
+ */
11
+ import { describe, expect, it } from 'vitest';
12
+ import { applyLedgerReplay } from '../../src/tools/install.ledger-replay.js';
13
+ import { CURRENT_VERSION, } from '../../src/audit/namespace-overrides.types.js';
14
+ function emptyLedger() {
15
+ return { version: CURRENT_VERSION, overrides: [] };
16
+ }
17
+ function makeRecord(partial) {
18
+ return {
19
+ id: 'ovr_TEST00000000000000000000001',
20
+ skillId: 'anthropic/code-helper',
21
+ kind: 'skill',
22
+ originalIdentifier: 'code-helper',
23
+ renamedTo: 'anthropic-code-helper',
24
+ originalPath: '/home/test/.claude/skills/code-helper',
25
+ renamedPath: '/home/test/.claude/skills/anthropic-code-helper',
26
+ appliedAt: '2026-04-30T15:42:18.331Z',
27
+ auditId: '01HXYAUDIT00000000000000001',
28
+ reason: 'collision with skillsmith/foo',
29
+ ...partial,
30
+ };
31
+ }
32
+ function candidate(overrides = {}) {
33
+ return {
34
+ identifier: 'code-helper',
35
+ projectedSourcePath: '/home/test/.claude/skills/code-helper',
36
+ skillId: 'anthropic/code-helper',
37
+ author: 'anthropic',
38
+ ...overrides,
39
+ };
40
+ }
41
+ describe('applyLedgerReplay', () => {
42
+ it('returns the candidate unchanged when the ledger is empty (case 1)', () => {
43
+ const c = candidate();
44
+ const result = applyLedgerReplay(c, emptyLedger());
45
+ expect(result.candidate).toBe(c); // reference-equal
46
+ expect(result.applied).toEqual([]);
47
+ });
48
+ it('returns the candidate unchanged when no override matches (case 2)', () => {
49
+ const c = candidate({ identifier: 'unrelated', skillId: 'someone/unrelated' });
50
+ const ledger = {
51
+ version: CURRENT_VERSION,
52
+ overrides: [makeRecord({})],
53
+ };
54
+ const result = applyLedgerReplay(c, ledger);
55
+ expect(result.candidate).toBe(c);
56
+ expect(result.applied).toEqual([]);
57
+ });
58
+ it('rewrites the candidate when a matching override exists (case 3)', () => {
59
+ const c = candidate();
60
+ const record = makeRecord({});
61
+ const ledger = {
62
+ version: CURRENT_VERSION,
63
+ overrides: [record],
64
+ };
65
+ const result = applyLedgerReplay(c, ledger);
66
+ expect(result.candidate).not.toBe(c);
67
+ expect(result.candidate.identifier).toBe('anthropic-code-helper');
68
+ expect(result.candidate.projectedSourcePath).toBe('/home/test/.claude/skills/anthropic-code-helper');
69
+ // Identity-bearing fields preserved.
70
+ expect(result.candidate.skillId).toBe('anthropic/code-helper');
71
+ expect(result.candidate.author).toBe('anthropic');
72
+ expect(result.applied).toEqual([record]);
73
+ });
74
+ it('applies chained renames across multiple ledger entries (case 4)', () => {
75
+ // User renamed code-helper -> anthropic-code-helper -> ant-helper
76
+ // (e.g. they reverted then re-renamed to a custom name). Ledger
77
+ // contains both. Replay must follow the chain.
78
+ const r1 = makeRecord({
79
+ id: 'ovr_chain1',
80
+ originalIdentifier: 'code-helper',
81
+ renamedTo: 'anthropic-code-helper',
82
+ });
83
+ const r2 = makeRecord({
84
+ id: 'ovr_chain2',
85
+ originalIdentifier: 'anthropic-code-helper',
86
+ renamedTo: 'ant-helper',
87
+ });
88
+ const ledger = {
89
+ version: CURRENT_VERSION,
90
+ overrides: [r1, r2],
91
+ };
92
+ const result = applyLedgerReplay(candidate(), ledger);
93
+ expect(result.candidate.identifier).toBe('ant-helper');
94
+ expect(result.candidate.projectedSourcePath).toBe('/home/test/.claude/skills/ant-helper');
95
+ expect(result.applied.map((a) => a.id)).toEqual(['ovr_chain1', 'ovr_chain2']);
96
+ });
97
+ });
98
+ //# sourceMappingURL=install.ledger-replay.test.js.map