@djm204/agent-skills 1.0.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 (392) hide show
  1. package/README.md +597 -0
  2. package/bin/cli.js +8 -0
  3. package/package.json +55 -0
  4. package/src/index.js +1817 -0
  5. package/src/index.test.js +1264 -0
  6. package/templates/_shared/code-quality.mdc +52 -0
  7. package/templates/_shared/communication.mdc +43 -0
  8. package/templates/_shared/core-principles.mdc +67 -0
  9. package/templates/_shared/git-workflow.mdc +48 -0
  10. package/templates/_shared/security-fundamentals.mdc +41 -0
  11. package/templates/agents/utility-agent/.cursor/rules/action-control.mdc +71 -0
  12. package/templates/agents/utility-agent/.cursor/rules/context-management.mdc +61 -0
  13. package/templates/agents/utility-agent/.cursor/rules/hallucination-prevention.mdc +58 -0
  14. package/templates/agents/utility-agent/.cursor/rules/overview.mdc +34 -0
  15. package/templates/agents/utility-agent/.cursor/rules/token-optimization.mdc +71 -0
  16. package/templates/agents/utility-agent/CLAUDE.md +513 -0
  17. package/templates/business/market-intelligence/.cursor/rules/data-sources.mdc +62 -0
  18. package/templates/business/market-intelligence/.cursor/rules/overview.mdc +55 -0
  19. package/templates/business/market-intelligence/.cursor/rules/reporting.mdc +59 -0
  20. package/templates/business/market-intelligence/.cursor/rules/risk-signals.mdc +63 -0
  21. package/templates/business/market-intelligence/.cursor/rules/sentiment-analysis.mdc +70 -0
  22. package/templates/business/market-intelligence/.cursor/rules/trend-detection.mdc +72 -0
  23. package/templates/business/market-intelligence/CLAUDE.md +371 -0
  24. package/templates/business/marketing-expert/.cursor/rules/brand-strategy.mdc +74 -0
  25. package/templates/business/marketing-expert/.cursor/rules/campaign-planning.mdc +60 -0
  26. package/templates/business/marketing-expert/.cursor/rules/growth-frameworks.mdc +69 -0
  27. package/templates/business/marketing-expert/.cursor/rules/market-analysis.mdc +70 -0
  28. package/templates/business/marketing-expert/.cursor/rules/marketing-analytics.mdc +71 -0
  29. package/templates/business/marketing-expert/.cursor/rules/overview.mdc +56 -0
  30. package/templates/business/marketing-expert/CLAUDE.md +567 -0
  31. package/templates/business/predictive-maintenance/.cursor/rules/alerting.mdc +56 -0
  32. package/templates/business/predictive-maintenance/.cursor/rules/asset-lifecycle.mdc +71 -0
  33. package/templates/business/predictive-maintenance/.cursor/rules/failure-prediction.mdc +65 -0
  34. package/templates/business/predictive-maintenance/.cursor/rules/maintenance-scheduling.mdc +61 -0
  35. package/templates/business/predictive-maintenance/.cursor/rules/overview.mdc +55 -0
  36. package/templates/business/predictive-maintenance/.cursor/rules/sensor-analytics.mdc +66 -0
  37. package/templates/business/predictive-maintenance/CLAUDE.md +529 -0
  38. package/templates/business/product-manager/.cursor/rules/communication.mdc +77 -0
  39. package/templates/business/product-manager/.cursor/rules/discovery.mdc +79 -0
  40. package/templates/business/product-manager/.cursor/rules/metrics.mdc +75 -0
  41. package/templates/business/product-manager/.cursor/rules/overview.mdc +47 -0
  42. package/templates/business/product-manager/.cursor/rules/prioritization.mdc +66 -0
  43. package/templates/business/product-manager/.cursor/rules/requirements.mdc +79 -0
  44. package/templates/business/product-manager/CLAUDE.md +593 -0
  45. package/templates/business/project-manager/.cursor/rules/overview.mdc +53 -0
  46. package/templates/business/project-manager/.cursor/rules/reporting.mdc +68 -0
  47. package/templates/business/project-manager/.cursor/rules/risk-management.mdc +71 -0
  48. package/templates/business/project-manager/.cursor/rules/scheduling.mdc +67 -0
  49. package/templates/business/project-manager/.cursor/rules/scope-management.mdc +66 -0
  50. package/templates/business/project-manager/.cursor/rules/stakeholder-management.mdc +70 -0
  51. package/templates/business/project-manager/CLAUDE.md +540 -0
  52. package/templates/business/regulatory-sentinel/.cursor/rules/compliance-tracking.mdc +74 -0
  53. package/templates/business/regulatory-sentinel/.cursor/rules/impact-assessment.mdc +62 -0
  54. package/templates/business/regulatory-sentinel/.cursor/rules/monitoring.mdc +67 -0
  55. package/templates/business/regulatory-sentinel/.cursor/rules/overview.mdc +55 -0
  56. package/templates/business/regulatory-sentinel/.cursor/rules/reporting.mdc +61 -0
  57. package/templates/business/regulatory-sentinel/.cursor/rules/risk-classification.mdc +73 -0
  58. package/templates/business/regulatory-sentinel/CLAUDE.md +572 -0
  59. package/templates/business/resource-allocator/.cursor/rules/capacity-modeling.mdc +65 -0
  60. package/templates/business/resource-allocator/.cursor/rules/coordination.mdc +67 -0
  61. package/templates/business/resource-allocator/.cursor/rules/crisis-management.mdc +64 -0
  62. package/templates/business/resource-allocator/.cursor/rules/demand-prediction.mdc +52 -0
  63. package/templates/business/resource-allocator/.cursor/rules/overview.mdc +76 -0
  64. package/templates/business/resource-allocator/.cursor/rules/scheduling.mdc +63 -0
  65. package/templates/business/resource-allocator/CLAUDE.md +525 -0
  66. package/templates/business/strategic-negotiator/.cursor/rules/contract-analysis.mdc +60 -0
  67. package/templates/business/strategic-negotiator/.cursor/rules/deal-structuring.mdc +66 -0
  68. package/templates/business/strategic-negotiator/.cursor/rules/game-theory.mdc +64 -0
  69. package/templates/business/strategic-negotiator/.cursor/rules/overview.mdc +55 -0
  70. package/templates/business/strategic-negotiator/.cursor/rules/preparation.mdc +79 -0
  71. package/templates/business/strategic-negotiator/.cursor/rules/scenario-modeling.mdc +66 -0
  72. package/templates/business/strategic-negotiator/CLAUDE.md +640 -0
  73. package/templates/business/supply-chain/.cursor/rules/cost-modeling.mdc +67 -0
  74. package/templates/business/supply-chain/.cursor/rules/demand-forecasting.mdc +67 -0
  75. package/templates/business/supply-chain/.cursor/rules/inventory-management.mdc +69 -0
  76. package/templates/business/supply-chain/.cursor/rules/logistics.mdc +61 -0
  77. package/templates/business/supply-chain/.cursor/rules/overview.mdc +64 -0
  78. package/templates/business/supply-chain/.cursor/rules/supplier-evaluation.mdc +66 -0
  79. package/templates/business/supply-chain/CLAUDE.md +590 -0
  80. package/templates/business/supply-chain-harmonizer/.cursor/rules/disruption-response.mdc +67 -0
  81. package/templates/business/supply-chain-harmonizer/.cursor/rules/inventory-rebalancing.mdc +63 -0
  82. package/templates/business/supply-chain-harmonizer/.cursor/rules/overview.mdc +65 -0
  83. package/templates/business/supply-chain-harmonizer/.cursor/rules/rerouting.mdc +64 -0
  84. package/templates/business/supply-chain-harmonizer/.cursor/rules/scenario-simulation.mdc +68 -0
  85. package/templates/business/supply-chain-harmonizer/.cursor/rules/stakeholder-notifications.mdc +61 -0
  86. package/templates/business/supply-chain-harmonizer/CLAUDE.md +600 -0
  87. package/templates/creative/brand-guardian/.cursor/rules/brand-voice.mdc +64 -0
  88. package/templates/creative/brand-guardian/.cursor/rules/content-review.mdc +47 -0
  89. package/templates/creative/brand-guardian/.cursor/rules/ethical-guidelines.mdc +47 -0
  90. package/templates/creative/brand-guardian/.cursor/rules/multi-channel.mdc +49 -0
  91. package/templates/creative/brand-guardian/.cursor/rules/overview.mdc +58 -0
  92. package/templates/creative/brand-guardian/.cursor/rules/visual-identity.mdc +64 -0
  93. package/templates/creative/brand-guardian/CLAUDE.md +634 -0
  94. package/templates/creative/content-creation-expert/.cursor/rules/content-strategy.mdc +65 -0
  95. package/templates/creative/content-creation-expert/.cursor/rules/copywriting.mdc +59 -0
  96. package/templates/creative/content-creation-expert/.cursor/rules/editorial-operations.mdc +65 -0
  97. package/templates/creative/content-creation-expert/.cursor/rules/multimedia-production.mdc +64 -0
  98. package/templates/creative/content-creation-expert/.cursor/rules/overview.mdc +58 -0
  99. package/templates/creative/content-creation-expert/.cursor/rules/seo-content.mdc +75 -0
  100. package/templates/creative/content-creation-expert/CLAUDE.md +568 -0
  101. package/templates/creative/narrative-architect/.cursor/rules/collaboration.mdc +62 -0
  102. package/templates/creative/narrative-architect/.cursor/rules/continuity-tracking.mdc +56 -0
  103. package/templates/creative/narrative-architect/.cursor/rules/overview.mdc +68 -0
  104. package/templates/creative/narrative-architect/.cursor/rules/story-bible.mdc +77 -0
  105. package/templates/creative/narrative-architect/.cursor/rules/timeline-management.mdc +60 -0
  106. package/templates/creative/narrative-architect/.cursor/rules/world-building.mdc +78 -0
  107. package/templates/creative/narrative-architect/CLAUDE.md +737 -0
  108. package/templates/creative/social-media-expert/.cursor/rules/audience-growth.mdc +62 -0
  109. package/templates/creative/social-media-expert/.cursor/rules/community-management.mdc +67 -0
  110. package/templates/creative/social-media-expert/.cursor/rules/content-strategy.mdc +60 -0
  111. package/templates/creative/social-media-expert/.cursor/rules/overview.mdc +48 -0
  112. package/templates/creative/social-media-expert/.cursor/rules/platform-strategy.mdc +64 -0
  113. package/templates/creative/social-media-expert/.cursor/rules/social-analytics.mdc +64 -0
  114. package/templates/creative/social-media-expert/CLAUDE.md +624 -0
  115. package/templates/creative/trend-forecaster/.cursor/rules/cultural-analysis.mdc +59 -0
  116. package/templates/creative/trend-forecaster/.cursor/rules/forecasting-methods.mdc +63 -0
  117. package/templates/creative/trend-forecaster/.cursor/rules/overview.mdc +58 -0
  118. package/templates/creative/trend-forecaster/.cursor/rules/reporting.mdc +61 -0
  119. package/templates/creative/trend-forecaster/.cursor/rules/signal-analysis.mdc +74 -0
  120. package/templates/creative/trend-forecaster/.cursor/rules/trend-lifecycle.mdc +75 -0
  121. package/templates/creative/trend-forecaster/CLAUDE.md +717 -0
  122. package/templates/creative/ux-designer/.cursor/rules/accessibility.mdc +69 -0
  123. package/templates/creative/ux-designer/.cursor/rules/emotional-design.mdc +59 -0
  124. package/templates/creative/ux-designer/.cursor/rules/handoff.mdc +73 -0
  125. package/templates/creative/ux-designer/.cursor/rules/information-architecture.mdc +62 -0
  126. package/templates/creative/ux-designer/.cursor/rules/interaction-design.mdc +66 -0
  127. package/templates/creative/ux-designer/.cursor/rules/overview.mdc +61 -0
  128. package/templates/creative/ux-designer/.cursor/rules/research.mdc +61 -0
  129. package/templates/creative/ux-designer/.cursor/rules/visual-design.mdc +68 -0
  130. package/templates/creative/ux-designer/CLAUDE.md +124 -0
  131. package/templates/dogfood/project-overview.mdc +12 -0
  132. package/templates/dogfood/project-structure.mdc +82 -0
  133. package/templates/dogfood/rules-creation-best-practices.mdc +45 -0
  134. package/templates/education/educator/.cursor/rules/accessibility.mdc +67 -0
  135. package/templates/education/educator/.cursor/rules/assessment.mdc +68 -0
  136. package/templates/education/educator/.cursor/rules/curriculum.mdc +57 -0
  137. package/templates/education/educator/.cursor/rules/engagement.mdc +65 -0
  138. package/templates/education/educator/.cursor/rules/instructional-design.mdc +69 -0
  139. package/templates/education/educator/.cursor/rules/overview.mdc +57 -0
  140. package/templates/education/educator/.cursor/rules/retention.mdc +64 -0
  141. package/templates/education/educator/CLAUDE.md +338 -0
  142. package/templates/engineering/blockchain/.cursor/rules/defi-patterns.mdc +48 -0
  143. package/templates/engineering/blockchain/.cursor/rules/gas-optimization.mdc +77 -0
  144. package/templates/engineering/blockchain/.cursor/rules/overview.mdc +41 -0
  145. package/templates/engineering/blockchain/.cursor/rules/security.mdc +61 -0
  146. package/templates/engineering/blockchain/.cursor/rules/smart-contracts.mdc +64 -0
  147. package/templates/engineering/blockchain/.cursor/rules/testing.mdc +77 -0
  148. package/templates/engineering/blockchain/.cursor/rules/web3-integration.mdc +47 -0
  149. package/templates/engineering/blockchain/CLAUDE.md +389 -0
  150. package/templates/engineering/cli-tools/.cursor/rules/architecture.mdc +76 -0
  151. package/templates/engineering/cli-tools/.cursor/rules/arguments.mdc +65 -0
  152. package/templates/engineering/cli-tools/.cursor/rules/distribution.mdc +40 -0
  153. package/templates/engineering/cli-tools/.cursor/rules/error-handling.mdc +67 -0
  154. package/templates/engineering/cli-tools/.cursor/rules/overview.mdc +58 -0
  155. package/templates/engineering/cli-tools/.cursor/rules/testing.mdc +42 -0
  156. package/templates/engineering/cli-tools/.cursor/rules/user-experience.mdc +43 -0
  157. package/templates/engineering/cli-tools/CLAUDE.md +356 -0
  158. package/templates/engineering/data-engineering/.cursor/rules/data-modeling.mdc +71 -0
  159. package/templates/engineering/data-engineering/.cursor/rules/data-quality.mdc +78 -0
  160. package/templates/engineering/data-engineering/.cursor/rules/overview.mdc +49 -0
  161. package/templates/engineering/data-engineering/.cursor/rules/performance.mdc +71 -0
  162. package/templates/engineering/data-engineering/.cursor/rules/pipeline-design.mdc +79 -0
  163. package/templates/engineering/data-engineering/.cursor/rules/security.mdc +79 -0
  164. package/templates/engineering/data-engineering/.cursor/rules/testing.mdc +75 -0
  165. package/templates/engineering/data-engineering/CLAUDE.md +974 -0
  166. package/templates/engineering/devops-sre/.cursor/rules/capacity-planning.mdc +49 -0
  167. package/templates/engineering/devops-sre/.cursor/rules/change-management.mdc +51 -0
  168. package/templates/engineering/devops-sre/.cursor/rules/chaos-engineering.mdc +50 -0
  169. package/templates/engineering/devops-sre/.cursor/rules/disaster-recovery.mdc +54 -0
  170. package/templates/engineering/devops-sre/.cursor/rules/incident-management.mdc +56 -0
  171. package/templates/engineering/devops-sre/.cursor/rules/observability.mdc +50 -0
  172. package/templates/engineering/devops-sre/.cursor/rules/overview.mdc +76 -0
  173. package/templates/engineering/devops-sre/.cursor/rules/postmortems.mdc +49 -0
  174. package/templates/engineering/devops-sre/.cursor/rules/runbooks.mdc +49 -0
  175. package/templates/engineering/devops-sre/.cursor/rules/slo-sli.mdc +46 -0
  176. package/templates/engineering/devops-sre/.cursor/rules/toil-reduction.mdc +52 -0
  177. package/templates/engineering/devops-sre/CLAUDE.md +1007 -0
  178. package/templates/engineering/fullstack/.cursor/rules/api-contracts.mdc +79 -0
  179. package/templates/engineering/fullstack/.cursor/rules/architecture.mdc +79 -0
  180. package/templates/engineering/fullstack/.cursor/rules/overview.mdc +61 -0
  181. package/templates/engineering/fullstack/.cursor/rules/shared-types.mdc +77 -0
  182. package/templates/engineering/fullstack/.cursor/rules/testing.mdc +72 -0
  183. package/templates/engineering/fullstack/CLAUDE.md +349 -0
  184. package/templates/engineering/ml-ai/.cursor/rules/data-engineering.mdc +71 -0
  185. package/templates/engineering/ml-ai/.cursor/rules/deployment.mdc +43 -0
  186. package/templates/engineering/ml-ai/.cursor/rules/model-development.mdc +44 -0
  187. package/templates/engineering/ml-ai/.cursor/rules/monitoring.mdc +45 -0
  188. package/templates/engineering/ml-ai/.cursor/rules/overview.mdc +42 -0
  189. package/templates/engineering/ml-ai/.cursor/rules/security.mdc +51 -0
  190. package/templates/engineering/ml-ai/.cursor/rules/testing.mdc +44 -0
  191. package/templates/engineering/ml-ai/CLAUDE.md +1136 -0
  192. package/templates/engineering/mobile/.cursor/rules/navigation.mdc +75 -0
  193. package/templates/engineering/mobile/.cursor/rules/offline-first.mdc +68 -0
  194. package/templates/engineering/mobile/.cursor/rules/overview.mdc +76 -0
  195. package/templates/engineering/mobile/.cursor/rules/performance.mdc +78 -0
  196. package/templates/engineering/mobile/.cursor/rules/testing.mdc +77 -0
  197. package/templates/engineering/mobile/CLAUDE.md +233 -0
  198. package/templates/engineering/platform-engineering/.cursor/rules/ci-cd.mdc +51 -0
  199. package/templates/engineering/platform-engineering/.cursor/rules/developer-experience.mdc +48 -0
  200. package/templates/engineering/platform-engineering/.cursor/rules/infrastructure-as-code.mdc +62 -0
  201. package/templates/engineering/platform-engineering/.cursor/rules/kubernetes.mdc +51 -0
  202. package/templates/engineering/platform-engineering/.cursor/rules/observability.mdc +52 -0
  203. package/templates/engineering/platform-engineering/.cursor/rules/overview.mdc +44 -0
  204. package/templates/engineering/platform-engineering/.cursor/rules/security.mdc +74 -0
  205. package/templates/engineering/platform-engineering/.cursor/rules/testing.mdc +59 -0
  206. package/templates/engineering/platform-engineering/CLAUDE.md +850 -0
  207. package/templates/engineering/qa-engineering/.cursor/rules/automation.mdc +71 -0
  208. package/templates/engineering/qa-engineering/.cursor/rules/metrics.mdc +68 -0
  209. package/templates/engineering/qa-engineering/.cursor/rules/overview.mdc +45 -0
  210. package/templates/engineering/qa-engineering/.cursor/rules/quality-gates.mdc +54 -0
  211. package/templates/engineering/qa-engineering/.cursor/rules/test-design.mdc +59 -0
  212. package/templates/engineering/qa-engineering/.cursor/rules/test-strategy.mdc +62 -0
  213. package/templates/engineering/qa-engineering/CLAUDE.md +726 -0
  214. package/templates/engineering/testing/.cursor/rules/advanced-techniques.mdc +44 -0
  215. package/templates/engineering/testing/.cursor/rules/ci-cd-integration.mdc +43 -0
  216. package/templates/engineering/testing/.cursor/rules/overview.mdc +61 -0
  217. package/templates/engineering/testing/.cursor/rules/performance-testing.mdc +39 -0
  218. package/templates/engineering/testing/.cursor/rules/quality-metrics.mdc +74 -0
  219. package/templates/engineering/testing/.cursor/rules/reliability.mdc +39 -0
  220. package/templates/engineering/testing/.cursor/rules/tdd-methodology.mdc +52 -0
  221. package/templates/engineering/testing/.cursor/rules/test-data.mdc +46 -0
  222. package/templates/engineering/testing/.cursor/rules/test-design.mdc +45 -0
  223. package/templates/engineering/testing/.cursor/rules/test-types.mdc +71 -0
  224. package/templates/engineering/testing/CLAUDE.md +1134 -0
  225. package/templates/engineering/unity-dev-expert/.cursor/rules/csharp-architecture.mdc +61 -0
  226. package/templates/engineering/unity-dev-expert/.cursor/rules/multiplayer-networking.mdc +67 -0
  227. package/templates/engineering/unity-dev-expert/.cursor/rules/overview.mdc +56 -0
  228. package/templates/engineering/unity-dev-expert/.cursor/rules/performance-optimization.mdc +76 -0
  229. package/templates/engineering/unity-dev-expert/.cursor/rules/physics-rendering.mdc +59 -0
  230. package/templates/engineering/unity-dev-expert/.cursor/rules/ui-systems.mdc +59 -0
  231. package/templates/engineering/unity-dev-expert/CLAUDE.md +534 -0
  232. package/templates/engineering/web-backend/.cursor/rules/api-design.mdc +64 -0
  233. package/templates/engineering/web-backend/.cursor/rules/authentication.mdc +69 -0
  234. package/templates/engineering/web-backend/.cursor/rules/database-patterns.mdc +73 -0
  235. package/templates/engineering/web-backend/.cursor/rules/error-handling.mdc +66 -0
  236. package/templates/engineering/web-backend/.cursor/rules/overview.mdc +74 -0
  237. package/templates/engineering/web-backend/.cursor/rules/security.mdc +60 -0
  238. package/templates/engineering/web-backend/.cursor/rules/testing.mdc +74 -0
  239. package/templates/engineering/web-backend/CLAUDE.md +366 -0
  240. package/templates/engineering/web-frontend/.cursor/rules/accessibility.mdc +75 -0
  241. package/templates/engineering/web-frontend/.cursor/rules/component-patterns.mdc +76 -0
  242. package/templates/engineering/web-frontend/.cursor/rules/overview.mdc +77 -0
  243. package/templates/engineering/web-frontend/.cursor/rules/performance.mdc +73 -0
  244. package/templates/engineering/web-frontend/.cursor/rules/state-management.mdc +71 -0
  245. package/templates/engineering/web-frontend/.cursor/rules/styling.mdc +69 -0
  246. package/templates/engineering/web-frontend/.cursor/rules/testing.mdc +75 -0
  247. package/templates/engineering/web-frontend/CLAUDE.md +399 -0
  248. package/templates/languages/cpp-expert/.cursor/rules/concurrency.mdc +68 -0
  249. package/templates/languages/cpp-expert/.cursor/rules/error-handling.mdc +65 -0
  250. package/templates/languages/cpp-expert/.cursor/rules/memory-and-ownership.mdc +68 -0
  251. package/templates/languages/cpp-expert/.cursor/rules/modern-cpp.mdc +75 -0
  252. package/templates/languages/cpp-expert/.cursor/rules/overview.mdc +37 -0
  253. package/templates/languages/cpp-expert/.cursor/rules/performance.mdc +74 -0
  254. package/templates/languages/cpp-expert/.cursor/rules/testing.mdc +70 -0
  255. package/templates/languages/cpp-expert/.cursor/rules/tooling.mdc +77 -0
  256. package/templates/languages/cpp-expert/CLAUDE.md +242 -0
  257. package/templates/languages/csharp-expert/.cursor/rules/aspnet-core.mdc +78 -0
  258. package/templates/languages/csharp-expert/.cursor/rules/async-patterns.mdc +71 -0
  259. package/templates/languages/csharp-expert/.cursor/rules/dependency-injection.mdc +76 -0
  260. package/templates/languages/csharp-expert/.cursor/rules/error-handling.mdc +65 -0
  261. package/templates/languages/csharp-expert/.cursor/rules/language-features.mdc +74 -0
  262. package/templates/languages/csharp-expert/.cursor/rules/overview.mdc +47 -0
  263. package/templates/languages/csharp-expert/.cursor/rules/performance.mdc +66 -0
  264. package/templates/languages/csharp-expert/.cursor/rules/testing.mdc +78 -0
  265. package/templates/languages/csharp-expert/.cursor/rules/tooling.mdc +78 -0
  266. package/templates/languages/csharp-expert/CLAUDE.md +360 -0
  267. package/templates/languages/golang-expert/.cursor/rules/concurrency.mdc +79 -0
  268. package/templates/languages/golang-expert/.cursor/rules/error-handling.mdc +77 -0
  269. package/templates/languages/golang-expert/.cursor/rules/interfaces-and-types.mdc +77 -0
  270. package/templates/languages/golang-expert/.cursor/rules/overview.mdc +74 -0
  271. package/templates/languages/golang-expert/.cursor/rules/performance.mdc +76 -0
  272. package/templates/languages/golang-expert/.cursor/rules/production-patterns.mdc +76 -0
  273. package/templates/languages/golang-expert/.cursor/rules/stdlib-and-tooling.mdc +68 -0
  274. package/templates/languages/golang-expert/.cursor/rules/testing.mdc +77 -0
  275. package/templates/languages/golang-expert/CLAUDE.md +361 -0
  276. package/templates/languages/java-expert/.cursor/rules/concurrency.mdc +69 -0
  277. package/templates/languages/java-expert/.cursor/rules/error-handling.mdc +70 -0
  278. package/templates/languages/java-expert/.cursor/rules/modern-java.mdc +74 -0
  279. package/templates/languages/java-expert/.cursor/rules/overview.mdc +42 -0
  280. package/templates/languages/java-expert/.cursor/rules/performance.mdc +69 -0
  281. package/templates/languages/java-expert/.cursor/rules/persistence.mdc +74 -0
  282. package/templates/languages/java-expert/.cursor/rules/spring-boot.mdc +73 -0
  283. package/templates/languages/java-expert/.cursor/rules/testing.mdc +79 -0
  284. package/templates/languages/java-expert/.cursor/rules/tooling.mdc +76 -0
  285. package/templates/languages/java-expert/CLAUDE.md +325 -0
  286. package/templates/languages/javascript-expert/.cursor/rules/language-deep-dive.mdc +74 -0
  287. package/templates/languages/javascript-expert/.cursor/rules/node-patterns.mdc +77 -0
  288. package/templates/languages/javascript-expert/.cursor/rules/overview.mdc +66 -0
  289. package/templates/languages/javascript-expert/.cursor/rules/performance.mdc +64 -0
  290. package/templates/languages/javascript-expert/.cursor/rules/react-patterns.mdc +70 -0
  291. package/templates/languages/javascript-expert/.cursor/rules/testing.mdc +76 -0
  292. package/templates/languages/javascript-expert/.cursor/rules/tooling.mdc +72 -0
  293. package/templates/languages/javascript-expert/.cursor/rules/typescript-deep-dive.mdc +77 -0
  294. package/templates/languages/javascript-expert/CLAUDE.md +479 -0
  295. package/templates/languages/kotlin-expert/.cursor/rules/coroutines.mdc +75 -0
  296. package/templates/languages/kotlin-expert/.cursor/rules/error-handling.mdc +69 -0
  297. package/templates/languages/kotlin-expert/.cursor/rules/frameworks.mdc +76 -0
  298. package/templates/languages/kotlin-expert/.cursor/rules/language-features.mdc +78 -0
  299. package/templates/languages/kotlin-expert/.cursor/rules/overview.mdc +38 -0
  300. package/templates/languages/kotlin-expert/.cursor/rules/performance.mdc +73 -0
  301. package/templates/languages/kotlin-expert/.cursor/rules/testing.mdc +70 -0
  302. package/templates/languages/kotlin-expert/.cursor/rules/tooling.mdc +67 -0
  303. package/templates/languages/kotlin-expert/CLAUDE.md +276 -0
  304. package/templates/languages/python-expert/.cursor/rules/async-python.mdc +71 -0
  305. package/templates/languages/python-expert/.cursor/rules/overview.mdc +76 -0
  306. package/templates/languages/python-expert/.cursor/rules/patterns-and-idioms.mdc +77 -0
  307. package/templates/languages/python-expert/.cursor/rules/performance.mdc +74 -0
  308. package/templates/languages/python-expert/.cursor/rules/testing.mdc +77 -0
  309. package/templates/languages/python-expert/.cursor/rules/tooling.mdc +77 -0
  310. package/templates/languages/python-expert/.cursor/rules/type-system.mdc +77 -0
  311. package/templates/languages/python-expert/.cursor/rules/web-and-apis.mdc +76 -0
  312. package/templates/languages/python-expert/CLAUDE.md +264 -0
  313. package/templates/languages/ruby-expert/.cursor/rules/concurrency-and-threading.mdc +65 -0
  314. package/templates/languages/ruby-expert/.cursor/rules/error-handling.mdc +69 -0
  315. package/templates/languages/ruby-expert/.cursor/rules/idioms-and-style.mdc +76 -0
  316. package/templates/languages/ruby-expert/.cursor/rules/overview.mdc +60 -0
  317. package/templates/languages/ruby-expert/.cursor/rules/performance.mdc +68 -0
  318. package/templates/languages/ruby-expert/.cursor/rules/rails-and-frameworks.mdc +60 -0
  319. package/templates/languages/ruby-expert/.cursor/rules/testing.mdc +56 -0
  320. package/templates/languages/ruby-expert/.cursor/rules/tooling.mdc +52 -0
  321. package/templates/languages/ruby-expert/CLAUDE.md +102 -0
  322. package/templates/languages/rust-expert/.cursor/rules/concurrency.mdc +69 -0
  323. package/templates/languages/rust-expert/.cursor/rules/ecosystem-and-tooling.mdc +76 -0
  324. package/templates/languages/rust-expert/.cursor/rules/error-handling.mdc +76 -0
  325. package/templates/languages/rust-expert/.cursor/rules/overview.mdc +62 -0
  326. package/templates/languages/rust-expert/.cursor/rules/ownership-and-borrowing.mdc +70 -0
  327. package/templates/languages/rust-expert/.cursor/rules/performance-and-unsafe.mdc +70 -0
  328. package/templates/languages/rust-expert/.cursor/rules/testing.mdc +73 -0
  329. package/templates/languages/rust-expert/.cursor/rules/traits-and-generics.mdc +76 -0
  330. package/templates/languages/rust-expert/CLAUDE.md +283 -0
  331. package/templates/languages/swift-expert/.cursor/rules/concurrency.mdc +77 -0
  332. package/templates/languages/swift-expert/.cursor/rules/error-handling.mdc +76 -0
  333. package/templates/languages/swift-expert/.cursor/rules/language-features.mdc +78 -0
  334. package/templates/languages/swift-expert/.cursor/rules/overview.mdc +46 -0
  335. package/templates/languages/swift-expert/.cursor/rules/performance.mdc +69 -0
  336. package/templates/languages/swift-expert/.cursor/rules/swiftui.mdc +77 -0
  337. package/templates/languages/swift-expert/.cursor/rules/testing.mdc +75 -0
  338. package/templates/languages/swift-expert/.cursor/rules/tooling.mdc +77 -0
  339. package/templates/languages/swift-expert/CLAUDE.md +275 -0
  340. package/templates/professional/documentation/.cursor/rules/adr.mdc +65 -0
  341. package/templates/professional/documentation/.cursor/rules/api-documentation.mdc +64 -0
  342. package/templates/professional/documentation/.cursor/rules/code-comments.mdc +75 -0
  343. package/templates/professional/documentation/.cursor/rules/maintenance.mdc +58 -0
  344. package/templates/professional/documentation/.cursor/rules/overview.mdc +48 -0
  345. package/templates/professional/documentation/.cursor/rules/readme-standards.mdc +70 -0
  346. package/templates/professional/documentation/CLAUDE.md +120 -0
  347. package/templates/professional/executive-assistant/.cursor/rules/calendar.mdc +51 -0
  348. package/templates/professional/executive-assistant/.cursor/rules/confidentiality.mdc +53 -0
  349. package/templates/professional/executive-assistant/.cursor/rules/email.mdc +49 -0
  350. package/templates/professional/executive-assistant/.cursor/rules/meetings.mdc +39 -0
  351. package/templates/professional/executive-assistant/.cursor/rules/overview.mdc +42 -0
  352. package/templates/professional/executive-assistant/.cursor/rules/prioritization.mdc +48 -0
  353. package/templates/professional/executive-assistant/.cursor/rules/stakeholder-management.mdc +50 -0
  354. package/templates/professional/executive-assistant/.cursor/rules/travel.mdc +43 -0
  355. package/templates/professional/executive-assistant/CLAUDE.md +620 -0
  356. package/templates/professional/grant-writer/.cursor/rules/budgets.mdc +55 -0
  357. package/templates/professional/grant-writer/.cursor/rules/compliance.mdc +47 -0
  358. package/templates/professional/grant-writer/.cursor/rules/funding-research.mdc +47 -0
  359. package/templates/professional/grant-writer/.cursor/rules/narrative.mdc +58 -0
  360. package/templates/professional/grant-writer/.cursor/rules/overview.mdc +68 -0
  361. package/templates/professional/grant-writer/.cursor/rules/post-award.mdc +59 -0
  362. package/templates/professional/grant-writer/.cursor/rules/review-criteria.mdc +51 -0
  363. package/templates/professional/grant-writer/.cursor/rules/sustainability.mdc +48 -0
  364. package/templates/professional/grant-writer/CLAUDE.md +577 -0
  365. package/templates/professional/knowledge-synthesis/.cursor/rules/document-management.mdc +51 -0
  366. package/templates/professional/knowledge-synthesis/.cursor/rules/knowledge-graphs.mdc +63 -0
  367. package/templates/professional/knowledge-synthesis/.cursor/rules/overview.mdc +74 -0
  368. package/templates/professional/knowledge-synthesis/.cursor/rules/research-workflow.mdc +50 -0
  369. package/templates/professional/knowledge-synthesis/.cursor/rules/search-retrieval.mdc +62 -0
  370. package/templates/professional/knowledge-synthesis/.cursor/rules/summarization.mdc +61 -0
  371. package/templates/professional/knowledge-synthesis/CLAUDE.md +593 -0
  372. package/templates/professional/life-logistics/.cursor/rules/financial-optimization.mdc +78 -0
  373. package/templates/professional/life-logistics/.cursor/rules/negotiation.mdc +68 -0
  374. package/templates/professional/life-logistics/.cursor/rules/overview.mdc +75 -0
  375. package/templates/professional/life-logistics/.cursor/rules/research-methodology.mdc +76 -0
  376. package/templates/professional/life-logistics/.cursor/rules/scheduling.mdc +68 -0
  377. package/templates/professional/life-logistics/.cursor/rules/task-management.mdc +47 -0
  378. package/templates/professional/life-logistics/CLAUDE.md +601 -0
  379. package/templates/professional/research-assistant/.cursor/rules/citation-attribution.mdc +61 -0
  380. package/templates/professional/research-assistant/.cursor/rules/information-synthesis.mdc +65 -0
  381. package/templates/professional/research-assistant/.cursor/rules/overview.mdc +56 -0
  382. package/templates/professional/research-assistant/.cursor/rules/research-methodologies.mdc +54 -0
  383. package/templates/professional/research-assistant/.cursor/rules/search-strategies.mdc +57 -0
  384. package/templates/professional/research-assistant/.cursor/rules/source-evaluation.mdc +59 -0
  385. package/templates/professional/research-assistant/CLAUDE.md +318 -0
  386. package/templates/professional/wellness-orchestrator/.cursor/rules/adaptive-planning.mdc +69 -0
  387. package/templates/professional/wellness-orchestrator/.cursor/rules/data-integration.mdc +60 -0
  388. package/templates/professional/wellness-orchestrator/.cursor/rules/fitness-programming.mdc +66 -0
  389. package/templates/professional/wellness-orchestrator/.cursor/rules/nutrition-planning.mdc +57 -0
  390. package/templates/professional/wellness-orchestrator/.cursor/rules/overview.mdc +76 -0
  391. package/templates/professional/wellness-orchestrator/.cursor/rules/sleep-optimization.mdc +68 -0
  392. package/templates/professional/wellness-orchestrator/CLAUDE.md +573 -0
@@ -0,0 +1,1264 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { run, _internals } from './index.js';
6
+
7
+ const {
8
+ PACKAGE_NAME,
9
+ CURRENT_VERSION,
10
+ CURSOR_RULES_DIR,
11
+ LEGACY_CURSORRULES_DIR,
12
+ CATEGORIES,
13
+ TEMPLATES,
14
+ TEMPLATE_ALIASES,
15
+ SHARED_RULES,
16
+ SUPPORTED_IDES,
17
+ DEFAULT_IDES,
18
+ compareVersions,
19
+ resolveTemplateAlias,
20
+ getTemplateRulePath,
21
+ filesMatch,
22
+ parseMarkdownSections,
23
+ generateSectionSignature,
24
+ findMissingSections,
25
+ mergeClaudeContent,
26
+ getAlternateFilename,
27
+ copyFile,
28
+ generateClaudeMdContent,
29
+ generateCopilotInstructionsContent,
30
+ isOurFile,
31
+ install,
32
+ remove,
33
+ reset,
34
+ } = _internals;
35
+
36
+ // ============================================================================
37
+ // Constants & Configuration Tests
38
+ // ============================================================================
39
+
40
+ describe('Package Info', () => {
41
+ it('should have a valid package name', () => {
42
+ expect(PACKAGE_NAME).toBe('@djm204/agent-skills');
43
+ });
44
+
45
+ it('should have a valid semver version', () => {
46
+ expect(CURRENT_VERSION).toMatch(/^\d+\.\d+\.\d+/);
47
+ });
48
+ });
49
+
50
+ describe('Version Comparison', () => {
51
+ describe('compareVersions', () => {
52
+ it('should return 0 for equal versions', () => {
53
+ expect(compareVersions('1.0.0', '1.0.0')).toBe(0);
54
+ expect(compareVersions('2.5.3', '2.5.3')).toBe(0);
55
+ });
56
+
57
+ it('should return -1 when first version is lower', () => {
58
+ expect(compareVersions('1.0.0', '1.0.1')).toBe(-1);
59
+ expect(compareVersions('1.0.0', '1.1.0')).toBe(-1);
60
+ expect(compareVersions('1.0.0', '2.0.0')).toBe(-1);
61
+ expect(compareVersions('0.6.1', '0.7.0')).toBe(-1);
62
+ });
63
+
64
+ it('should return 1 when first version is higher', () => {
65
+ expect(compareVersions('1.0.1', '1.0.0')).toBe(1);
66
+ expect(compareVersions('1.1.0', '1.0.0')).toBe(1);
67
+ expect(compareVersions('2.0.0', '1.0.0')).toBe(1);
68
+ expect(compareVersions('0.7.0', '0.6.1')).toBe(1);
69
+ });
70
+
71
+ it('should handle missing patch versions', () => {
72
+ expect(compareVersions('1.0', '1.0.0')).toBe(0);
73
+ expect(compareVersions('1.0.0', '1.0')).toBe(0);
74
+ });
75
+ });
76
+ });
77
+
78
+ describe('Constants', () => {
79
+ describe('TEMPLATES', () => {
80
+ it('should have all expected templates', () => {
81
+ const expectedTemplates = [
82
+ 'blockchain',
83
+ 'brand-guardian',
84
+ 'content-creation-expert',
85
+ 'cpp-expert',
86
+ 'csharp-expert',
87
+ 'cli-tools',
88
+ 'data-engineering',
89
+ 'devops-sre',
90
+ 'documentation',
91
+ 'educator',
92
+ 'executive-assistant',
93
+ 'fullstack',
94
+ 'golang-expert',
95
+ 'grant-writer',
96
+ 'java-expert',
97
+ 'javascript-expert',
98
+ 'knowledge-synthesis',
99
+ 'kotlin-expert',
100
+ 'life-logistics',
101
+ 'market-intelligence',
102
+ 'marketing-expert',
103
+ 'ml-ai',
104
+ 'mobile',
105
+ 'narrative-architect',
106
+ 'platform-engineering',
107
+ 'predictive-maintenance',
108
+ 'product-manager',
109
+ 'project-manager',
110
+ 'python-expert',
111
+ 'qa-engineering',
112
+ 'regulatory-sentinel',
113
+ 'research-assistant',
114
+ 'resource-allocator',
115
+ 'ruby-expert',
116
+ 'rust-expert',
117
+ 'social-media-expert',
118
+ 'strategic-negotiator',
119
+ 'supply-chain-harmonizer',
120
+ 'supply-chain',
121
+ 'swift-expert',
122
+ 'testing',
123
+ 'trend-forecaster',
124
+ 'unity-dev-expert',
125
+ 'utility-agent',
126
+ 'ux-designer',
127
+ 'web-backend',
128
+ 'web-frontend',
129
+ 'wellness-orchestrator',
130
+ ];
131
+
132
+ expect(Object.keys(TEMPLATES).sort()).toEqual(expectedTemplates.sort());
133
+ });
134
+
135
+ it('each template should have category, description, and rules array', () => {
136
+ for (const [name, template] of Object.entries(TEMPLATES)) {
137
+ expect(template).toHaveProperty('category');
138
+ expect(CATEGORIES).toContain(template.category);
139
+
140
+ expect(template).toHaveProperty('description');
141
+ expect(typeof template.description).toBe('string');
142
+ expect(template.description.length).toBeGreaterThan(0);
143
+
144
+ expect(template).toHaveProperty('rules');
145
+ expect(Array.isArray(template.rules)).toBe(true);
146
+ expect(template.rules.length).toBeGreaterThan(0);
147
+ }
148
+ });
149
+
150
+ it('each template should have overview.mdc in rules', () => {
151
+ for (const [name, template] of Object.entries(TEMPLATES)) {
152
+ expect(template.rules).toContain('overview.mdc');
153
+ }
154
+ });
155
+
156
+ it('each template rule file should exist on disk', () => {
157
+ for (const [name, template] of Object.entries(TEMPLATES)) {
158
+ for (const rule of template.rules) {
159
+ const rulePath = getTemplateRulePath(name, rule);
160
+ expect(fs.existsSync(rulePath), `Missing: ${rulePath}`).toBe(true);
161
+ }
162
+ }
163
+ });
164
+ });
165
+
166
+ describe('SHARED_RULES', () => {
167
+ it('should have expected shared rules', () => {
168
+ expect(SHARED_RULES).toContain('code-quality.mdc');
169
+ expect(SHARED_RULES).toContain('communication.mdc');
170
+ expect(SHARED_RULES).toContain('core-principles.mdc');
171
+ expect(SHARED_RULES).toContain('git-workflow.mdc');
172
+ expect(SHARED_RULES).toContain('security-fundamentals.mdc');
173
+ });
174
+
175
+ it('all rules should end with .mdc', () => {
176
+ for (const rule of SHARED_RULES) {
177
+ expect(rule).toMatch(/\.mdc$/);
178
+ }
179
+ });
180
+ });
181
+
182
+ describe('SUPPORTED_IDES', () => {
183
+ it('should contain cursor, claude, and codex', () => {
184
+ expect(SUPPORTED_IDES).toContain('cursor');
185
+ expect(SUPPORTED_IDES).toContain('claude');
186
+ expect(SUPPORTED_IDES).toContain('codex');
187
+ });
188
+ });
189
+
190
+ describe('DEFAULT_IDES', () => {
191
+ it('should default to all supported IDEs', () => {
192
+ expect(DEFAULT_IDES).toEqual(SUPPORTED_IDES);
193
+ });
194
+ });
195
+
196
+ describe('TEMPLATE_ALIASES', () => {
197
+ it('all alias values should be valid TEMPLATES keys', () => {
198
+ for (const [alias, canonical] of Object.entries(TEMPLATE_ALIASES)) {
199
+ expect(TEMPLATES).toHaveProperty(canonical,
200
+ expect.anything(),
201
+ );
202
+ }
203
+ });
204
+
205
+ it('should include expected shorthand aliases', () => {
206
+ expect(TEMPLATE_ALIASES['js']).toBe('javascript-expert');
207
+ expect(TEMPLATE_ALIASES['ts']).toBe('javascript-expert');
208
+ expect(TEMPLATE_ALIASES['go']).toBe('golang-expert');
209
+ expect(TEMPLATE_ALIASES['py']).toBe('python-expert');
210
+ expect(TEMPLATE_ALIASES['rs']).toBe('rust-expert');
211
+ expect(TEMPLATE_ALIASES['ruby']).toBe('ruby-expert');
212
+ expect(TEMPLATE_ALIASES['rb']).toBe('ruby-expert');
213
+ expect(TEMPLATE_ALIASES['kt']).toBe('kotlin-expert');
214
+ expect(TEMPLATE_ALIASES['frontend']).toBe('web-frontend');
215
+ expect(TEMPLATE_ALIASES['backend']).toBe('web-backend');
216
+ expect(TEMPLATE_ALIASES['docs']).toBe('documentation');
217
+ expect(TEMPLATE_ALIASES['devops']).toBe('devops-sre');
218
+ expect(TEMPLATE_ALIASES['research']).toBe('research-assistant');
219
+ expect(TEMPLATE_ALIASES['agent']).toBe('utility-agent');
220
+ });
221
+ });
222
+
223
+ describe('resolveTemplateAlias', () => {
224
+ it('should resolve known aliases to canonical names', () => {
225
+ expect(resolveTemplateAlias('js')).toBe('javascript-expert');
226
+ expect(resolveTemplateAlias('typescript')).toBe('javascript-expert');
227
+ expect(resolveTemplateAlias('go')).toBe('golang-expert');
228
+ expect(resolveTemplateAlias('golang')).toBe('golang-expert');
229
+ expect(resolveTemplateAlias('py')).toBe('python-expert');
230
+ expect(resolveTemplateAlias('rs')).toBe('rust-expert');
231
+ expect(resolveTemplateAlias('ruby')).toBe('ruby-expert');
232
+ expect(resolveTemplateAlias('rb')).toBe('ruby-expert');
233
+ expect(resolveTemplateAlias('kotlin')).toBe('kotlin-expert');
234
+ expect(resolveTemplateAlias('kt')).toBe('kotlin-expert');
235
+ });
236
+
237
+ it('should pass through unknown names unchanged', () => {
238
+ expect(resolveTemplateAlias('web-frontend')).toBe('web-frontend');
239
+ expect(resolveTemplateAlias('blockchain')).toBe('blockchain');
240
+ expect(resolveTemplateAlias('nonexistent')).toBe('nonexistent');
241
+ });
242
+
243
+ it('should pass through canonical template names unchanged', () => {
244
+ expect(resolveTemplateAlias('javascript-expert')).toBe('javascript-expert');
245
+ expect(resolveTemplateAlias('golang-expert')).toBe('golang-expert');
246
+ expect(resolveTemplateAlias('python-expert')).toBe('python-expert');
247
+ });
248
+
249
+ it('should resolve engineering and professional shorthands', () => {
250
+ expect(resolveTemplateAlias('frontend')).toBe('web-frontend');
251
+ expect(resolveTemplateAlias('backend')).toBe('web-backend');
252
+ expect(resolveTemplateAlias('docs')).toBe('documentation');
253
+ expect(resolveTemplateAlias('devops')).toBe('devops-sre');
254
+ expect(resolveTemplateAlias('agent')).toBe('utility-agent');
255
+ });
256
+ });
257
+ });
258
+
259
+ // ============================================================================
260
+ // Utility Functions Tests
261
+ // ============================================================================
262
+
263
+ describe('Utility Functions', () => {
264
+ describe('getAlternateFilename', () => {
265
+ it('should add -1 suffix before extension', () => {
266
+ expect(getAlternateFilename('/path/to/file.mdc')).toBe('/path/to/file-1.mdc');
267
+ expect(getAlternateFilename('/path/to/code-quality.mdc')).toBe('/path/to/code-quality-1.mdc');
268
+ });
269
+
270
+ it('should handle files without directory', () => {
271
+ expect(getAlternateFilename('file.mdc')).toBe('file-1.mdc');
272
+ });
273
+
274
+ it('should handle different extensions', () => {
275
+ expect(getAlternateFilename('/path/to/file.txt')).toBe('/path/to/file-1.txt');
276
+ expect(getAlternateFilename('/path/to/file.json')).toBe('/path/to/file-1.json');
277
+ });
278
+ });
279
+
280
+ describe('filesMatch', () => {
281
+ let tempDir;
282
+
283
+ beforeEach(() => {
284
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-test-'));
285
+ });
286
+
287
+ afterEach(() => {
288
+ fs.rmSync(tempDir, { recursive: true, force: true });
289
+ });
290
+
291
+ it('should return true for identical files', () => {
292
+ const file1 = path.join(tempDir, 'file1.md');
293
+ const file2 = path.join(tempDir, 'file2.md');
294
+ const content = '# Test Content\n\nSome text here.';
295
+
296
+ fs.writeFileSync(file1, content);
297
+ fs.writeFileSync(file2, content);
298
+
299
+ expect(filesMatch(file1, file2)).toBe(true);
300
+ });
301
+
302
+ it('should return false for different files', () => {
303
+ const file1 = path.join(tempDir, 'file1.md');
304
+ const file2 = path.join(tempDir, 'file2.md');
305
+
306
+ fs.writeFileSync(file1, '# Content A');
307
+ fs.writeFileSync(file2, '# Content B');
308
+
309
+ expect(filesMatch(file1, file2)).toBe(false);
310
+ });
311
+
312
+ it('should return false if file does not exist', () => {
313
+ const file1 = path.join(tempDir, 'exists.md');
314
+ const file2 = path.join(tempDir, 'not-exists.md');
315
+
316
+ fs.writeFileSync(file1, '# Content');
317
+
318
+ expect(filesMatch(file1, file2)).toBe(false);
319
+ });
320
+ });
321
+
322
+ describe('isOurFile', () => {
323
+ let tempDir;
324
+
325
+ beforeEach(() => {
326
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-test-'));
327
+ });
328
+
329
+ afterEach(() => {
330
+ fs.rmSync(tempDir, { recursive: true, force: true });
331
+ });
332
+
333
+ it('should return false if file does not exist', () => {
334
+ const filePath = path.join(tempDir, 'nonexistent.md');
335
+ const templatePath = path.join(tempDir, 'template.md');
336
+
337
+ expect(isOurFile(filePath, templatePath)).toBe(false);
338
+ });
339
+
340
+ it('should return true if template does not exist (assume ours)', () => {
341
+ const filePath = path.join(tempDir, 'file.md');
342
+ const templatePath = path.join(tempDir, 'nonexistent-template.md');
343
+
344
+ fs.writeFileSync(filePath, '# Content');
345
+
346
+ expect(isOurFile(filePath, templatePath)).toBe(true);
347
+ });
348
+
349
+ it('should return true if files match', () => {
350
+ const filePath = path.join(tempDir, 'file.md');
351
+ const templatePath = path.join(tempDir, 'template.md');
352
+ const content = '# Same Content';
353
+
354
+ fs.writeFileSync(filePath, content);
355
+ fs.writeFileSync(templatePath, content);
356
+
357
+ expect(isOurFile(filePath, templatePath)).toBe(true);
358
+ });
359
+
360
+ it('should return false if files differ', () => {
361
+ const filePath = path.join(tempDir, 'file.md');
362
+ const templatePath = path.join(tempDir, 'template.md');
363
+
364
+ fs.writeFileSync(filePath, '# Modified Content');
365
+ fs.writeFileSync(templatePath, '# Original Template');
366
+
367
+ expect(isOurFile(filePath, templatePath)).toBe(false);
368
+ });
369
+ });
370
+ });
371
+
372
+ // ============================================================================
373
+ // Markdown Parsing Tests
374
+ // ============================================================================
375
+
376
+ describe('Markdown Parsing', () => {
377
+ describe('parseMarkdownSections', () => {
378
+ it('should parse sections with ## headings', () => {
379
+ const content = `# Title
380
+
381
+ Some preamble text.
382
+
383
+ ## Section One
384
+
385
+ Content for section one.
386
+
387
+ ## Section Two
388
+
389
+ Content for section two.
390
+ `;
391
+ const result = parseMarkdownSections(content);
392
+
393
+ expect(result.preamble).toContain('# Title');
394
+ expect(result.preamble).toContain('Some preamble text.');
395
+ expect(result.sections).toHaveLength(2);
396
+ expect(result.sections[0].heading).toBe('Section One');
397
+ expect(result.sections[1].heading).toBe('Section Two');
398
+ });
399
+
400
+ it('should handle content with no sections', () => {
401
+ const content = '# Just a title\n\nSome content without sections.';
402
+ const result = parseMarkdownSections(content);
403
+
404
+ expect(result.sections).toHaveLength(0);
405
+ expect(result.preamble).toContain('# Just a title');
406
+ });
407
+
408
+ it('should preserve section content', () => {
409
+ const content = `## My Section
410
+
411
+ Line 1
412
+ Line 2
413
+ Line 3
414
+ `;
415
+ const result = parseMarkdownSections(content);
416
+
417
+ expect(result.sections[0].content).toContain('Line 1');
418
+ expect(result.sections[0].content).toContain('Line 2');
419
+ expect(result.sections[0].content).toContain('Line 3');
420
+ });
421
+
422
+ it('should generate signatures for sections', () => {
423
+ const content = `## Test Section
424
+
425
+ Some meaningful content here.
426
+ `;
427
+ const result = parseMarkdownSections(content);
428
+
429
+ expect(result.sections[0]).toHaveProperty('signature');
430
+ expect(typeof result.sections[0].signature).toBe('string');
431
+ expect(result.sections[0].signature.length).toBeGreaterThan(0);
432
+ });
433
+ });
434
+
435
+ describe('generateSectionSignature', () => {
436
+ it('should normalize heading to lowercase', () => {
437
+ const sig1 = generateSectionSignature('My Heading', ['content']);
438
+ const sig2 = generateSectionSignature('my heading', ['content']);
439
+
440
+ expect(sig1).toBe(sig2);
441
+ });
442
+
443
+ it('should remove special characters from heading', () => {
444
+ const sig1 = generateSectionSignature('Heading: With (Special) Chars!', ['content']);
445
+ const sig2 = generateSectionSignature('Heading With Special Chars', ['content']);
446
+
447
+ expect(sig1).toBe(sig2);
448
+ });
449
+
450
+ it('should include content in signature', () => {
451
+ const sig1 = generateSectionSignature('Heading', ['Line A']);
452
+ const sig2 = generateSectionSignature('Heading', ['Line B']);
453
+
454
+ expect(sig1).not.toBe(sig2);
455
+ });
456
+
457
+ it('should filter out empty lines and special lines', () => {
458
+ const sig = generateSectionSignature('Heading', [
459
+ '',
460
+ '# Subheading',
461
+ '| table |',
462
+ '- list item',
463
+ 'Meaningful content',
464
+ ]);
465
+
466
+ expect(sig).toContain('meaningful content');
467
+ });
468
+ });
469
+
470
+ describe('findMissingSections', () => {
471
+ it('should find sections in template that are missing from existing', () => {
472
+ const existing = `## Section A
473
+
474
+ Content A
475
+
476
+ ## Section B
477
+
478
+ Content B
479
+ `;
480
+ const template = `## Section A
481
+
482
+ Content A
483
+
484
+ ## Section B
485
+
486
+ Content B
487
+
488
+ ## Section C
489
+
490
+ Content C
491
+ `;
492
+ const result = findMissingSections(existing, template);
493
+
494
+ expect(result.missing).toHaveLength(1);
495
+ expect(result.missing[0].heading).toBe('Section C');
496
+ expect(result.matchedCount).toBe(2);
497
+ });
498
+
499
+ it('should return empty array when all sections present', () => {
500
+ const content = `## Section A
501
+
502
+ Content
503
+
504
+ ## Section B
505
+
506
+ Content
507
+ `;
508
+ const result = findMissingSections(content, content);
509
+
510
+ expect(result.missing).toHaveLength(0);
511
+ expect(result.matchedCount).toBe(2);
512
+ });
513
+
514
+ it('should match by heading regardless of case', () => {
515
+ const existing = '## SECTION A\n\nContent';
516
+ const template = '## Section A\n\nContent';
517
+
518
+ const result = findMissingSections(existing, template);
519
+
520
+ expect(result.missing).toHaveLength(0);
521
+ expect(result.matchedCount).toBe(1);
522
+ });
523
+ });
524
+
525
+ describe('mergeClaudeContent', () => {
526
+ it('should merge missing sections into existing content', () => {
527
+ const existing = `# Title
528
+
529
+ ## Section A
530
+
531
+ Content A
532
+ `;
533
+ const template = `# Title
534
+
535
+ ## Section A
536
+
537
+ Content A
538
+
539
+ ## Section B
540
+
541
+ Content B
542
+ `;
543
+ const result = mergeClaudeContent(existing, template);
544
+
545
+ expect(result.merged).toContain('## Section A');
546
+ expect(result.merged).toContain('## Section B');
547
+ expect(result.addedSections).toContain('Section B');
548
+ });
549
+
550
+ it('should return unchanged content when nothing to merge', () => {
551
+ const content = `## Section A
552
+
553
+ Content
554
+ `;
555
+ const result = mergeClaudeContent(content, content);
556
+
557
+ expect(result.merged).toBe(content);
558
+ expect(result.addedSections).toHaveLength(0);
559
+ });
560
+
561
+ it('should preserve preamble', () => {
562
+ const existing = `# My Title
563
+
564
+ Introduction paragraph.
565
+
566
+ ## Existing Section
567
+
568
+ Content
569
+ `;
570
+ const template = `## Existing Section
571
+
572
+ Content
573
+
574
+ ## New Section
575
+
576
+ New content
577
+ `;
578
+ const result = mergeClaudeContent(existing, template);
579
+
580
+ expect(result.merged).toContain('# My Title');
581
+ expect(result.merged).toContain('Introduction paragraph');
582
+ });
583
+ });
584
+ });
585
+
586
+ // ============================================================================
587
+ // Content Generation Tests
588
+ // ============================================================================
589
+
590
+ describe('Content Generation', () => {
591
+ describe('generateClaudeMdContent', () => {
592
+ it('should generate valid markdown', () => {
593
+ const content = generateClaudeMdContent(['web-frontend']);
594
+
595
+ expect(content).toContain('# CLAUDE.md - Development Guide');
596
+ expect(content).toContain('web-frontend');
597
+ });
598
+
599
+ it('should include template description', () => {
600
+ const content = generateClaudeMdContent(['web-frontend']);
601
+
602
+ expect(content).toContain(TEMPLATES['web-frontend'].description);
603
+ });
604
+
605
+ it('should include multiple templates', () => {
606
+ const content = generateClaudeMdContent(['web-frontend', 'web-backend']);
607
+
608
+ expect(content).toContain('web-frontend');
609
+ expect(content).toContain('web-backend');
610
+ expect(content).toContain(TEMPLATES['web-frontend'].description);
611
+ expect(content).toContain(TEMPLATES['web-backend'].description);
612
+ });
613
+
614
+ it('should include shared rules table', () => {
615
+ const content = generateClaudeMdContent(['web-frontend']);
616
+
617
+ expect(content).toContain('core-principles.mdc');
618
+ expect(content).toContain('code-quality.mdc');
619
+ expect(content).toContain('security-fundamentals.mdc');
620
+ });
621
+
622
+ it('should include customization section', () => {
623
+ const content = generateClaudeMdContent(['web-frontend']);
624
+
625
+ expect(content).toContain('Customization');
626
+ expect(content).toContain('.mdc');
627
+ expect(content).toContain('npx @djm204/agent-skills');
628
+ });
629
+
630
+ it('should not include duplicated principles or definition of done', () => {
631
+ const content = generateClaudeMdContent(['web-frontend']);
632
+
633
+ // These were removed to avoid duplicating shared rules
634
+ expect(content).not.toContain('Development Principles');
635
+ expect(content).not.toContain('Definition of Done');
636
+ });
637
+ });
638
+
639
+ describe('generateCopilotInstructionsContent', () => {
640
+ it('should generate valid markdown', () => {
641
+ const content = generateCopilotInstructionsContent(['web-frontend']);
642
+
643
+ expect(content).toContain('# Copilot Instructions');
644
+ expect(content).toContain('web-frontend');
645
+ });
646
+
647
+ it('should include installed templates list', () => {
648
+ const content = generateCopilotInstructionsContent(['web-frontend', 'web-backend']);
649
+
650
+ expect(content).toContain('**Installed Templates:** web-frontend, web-backend');
651
+ });
652
+
653
+ it('should include compact core principles', () => {
654
+ const content = generateCopilotInstructionsContent(['web-frontend']);
655
+
656
+ expect(content).toContain('Honesty over output');
657
+ expect(content).toContain('Security first');
658
+ expect(content).toContain('Tests required');
659
+ });
660
+
661
+ it('should use description tables instead of full content', () => {
662
+ const content = generateCopilotInstructionsContent(['web-frontend']);
663
+
664
+ // Should have description-based tables
665
+ expect(content).toContain('Shared Rules');
666
+ expect(content).toContain('Template Rules');
667
+ expect(content).toContain('| Rule | Guidance |');
668
+ // Should NOT contain full rule file content (no YAML front matter)
669
+ expect(content).not.toContain('alwaysApply:');
670
+ });
671
+ });
672
+ });
673
+
674
+ // ============================================================================
675
+ // File Operations Tests
676
+ // ============================================================================
677
+
678
+ describe('File Operations', () => {
679
+ let tempDir;
680
+
681
+ beforeEach(() => {
682
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-test-'));
683
+ });
684
+
685
+ afterEach(() => {
686
+ fs.rmSync(tempDir, { recursive: true, force: true });
687
+ });
688
+
689
+ describe('copyFile', () => {
690
+ it('should copy file to new location', () => {
691
+ const src = path.join(tempDir, 'source.md');
692
+ const dest = path.join(tempDir, 'dest.md');
693
+ const content = '# Test Content';
694
+
695
+ fs.writeFileSync(src, content);
696
+
697
+ const result = copyFile(src, dest);
698
+
699
+ expect(result.status).toBe('copied');
700
+ expect(result.destFile).toBe(dest);
701
+ expect(fs.readFileSync(dest, 'utf8')).toBe(content);
702
+ });
703
+
704
+ it('should create destination directory if needed', () => {
705
+ const src = path.join(tempDir, 'source.md');
706
+ const dest = path.join(tempDir, 'subdir', 'dest.md');
707
+
708
+ fs.writeFileSync(src, '# Content');
709
+
710
+ const result = copyFile(src, dest);
711
+
712
+ expect(result.status).toBe('copied');
713
+ expect(fs.existsSync(dest)).toBe(true);
714
+ });
715
+
716
+ it('should skip identical files', () => {
717
+ const src = path.join(tempDir, 'source.md');
718
+ const dest = path.join(tempDir, 'dest.md');
719
+ const content = '# Same Content';
720
+
721
+ fs.writeFileSync(src, content);
722
+ fs.writeFileSync(dest, content);
723
+
724
+ const result = copyFile(src, dest);
725
+
726
+ expect(result.status).toBe('skipped');
727
+ expect(result.destFile).toBe(dest);
728
+ });
729
+
730
+ it('should rename to -1 when destination differs', () => {
731
+ const src = path.join(tempDir, 'source.md');
732
+ const dest = path.join(tempDir, 'dest.md');
733
+
734
+ fs.writeFileSync(src, '# New Content');
735
+ fs.writeFileSync(dest, '# Existing Different Content');
736
+
737
+ const result = copyFile(src, dest, false);
738
+
739
+ expect(result.status).toBe('renamed');
740
+ expect(result.destFile).toBe(path.join(tempDir, 'dest-1.md'));
741
+ expect(fs.existsSync(path.join(tempDir, 'dest-1.md'))).toBe(true);
742
+ // Original should be preserved
743
+ expect(fs.readFileSync(dest, 'utf8')).toBe('# Existing Different Content');
744
+ });
745
+
746
+ it('should overwrite with force flag', () => {
747
+ const src = path.join(tempDir, 'source.md');
748
+ const dest = path.join(tempDir, 'dest.md');
749
+
750
+ fs.writeFileSync(src, '# New Content');
751
+ fs.writeFileSync(dest, '# Old Content');
752
+
753
+ const result = copyFile(src, dest, true);
754
+
755
+ expect(result.status).toBe('updated');
756
+ expect(fs.readFileSync(dest, 'utf8')).toBe('# New Content');
757
+ });
758
+ });
759
+ });
760
+
761
+ // ============================================================================
762
+ // Install/Remove/Reset Integration Tests
763
+ // ============================================================================
764
+
765
+ describe('Install/Remove/Reset Operations', () => {
766
+ let tempDir;
767
+ let originalCwd;
768
+ let consoleLogSpy;
769
+
770
+ beforeEach(() => {
771
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-test-'));
772
+ originalCwd = process.cwd();
773
+ // Suppress console output during tests
774
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
775
+ vi.spyOn(console, 'error').mockImplementation(() => {});
776
+ });
777
+
778
+ afterEach(() => {
779
+ process.chdir(originalCwd);
780
+ fs.rmSync(tempDir, { recursive: true, force: true });
781
+ vi.restoreAllMocks();
782
+ });
783
+
784
+ describe('install', () => {
785
+ it('should create .cursor/rules directory', async () => {
786
+ await install(tempDir, ['web-frontend'], false, false, ['cursor']);
787
+
788
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules'))).toBe(true);
789
+ });
790
+
791
+ it('should install shared rules', async () => {
792
+ await install(tempDir, ['web-frontend'], false, false, ['cursor']);
793
+
794
+ for (const rule of SHARED_RULES) {
795
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', rule))).toBe(true);
796
+ }
797
+ });
798
+
799
+ it('should install template-specific rules with prefix', async () => {
800
+ await install(tempDir, ['web-frontend'], false, false, ['cursor']);
801
+
802
+ for (const rule of TEMPLATES['web-frontend'].rules) {
803
+ const prefixedName = `web-frontend-${rule}`;
804
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', prefixedName))).toBe(true);
805
+ }
806
+ });
807
+
808
+ it('should create CLAUDE.md for claude IDE', async () => {
809
+ await install(tempDir, ['web-frontend'], false, false, ['claude']);
810
+
811
+ expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
812
+ const content = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf8');
813
+ expect(content).toContain('# CLAUDE.md - Development Guide');
814
+ });
815
+
816
+ it('should create copilot-instructions.md for codex IDE', async () => {
817
+ await install(tempDir, ['web-frontend'], false, false, ['codex']);
818
+
819
+ expect(fs.existsSync(path.join(tempDir, '.github', 'copilot-instructions.md'))).toBe(true);
820
+ });
821
+
822
+ it('should install for all IDEs by default', async () => {
823
+ await install(tempDir, ['web-frontend'], false, false, DEFAULT_IDES);
824
+
825
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules'))).toBe(true);
826
+ expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
827
+ expect(fs.existsSync(path.join(tempDir, '.github', 'copilot-instructions.md'))).toBe(true);
828
+ });
829
+
830
+ it('should not write files in dry-run mode', async () => {
831
+ await install(tempDir, ['web-frontend'], true, false, ['cursor']);
832
+
833
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules'))).toBe(false);
834
+ });
835
+
836
+ it('should install multiple templates', async () => {
837
+ await install(tempDir, ['web-frontend', 'web-backend'], false, false, ['cursor']);
838
+
839
+ // Check web-frontend rules
840
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc'))).toBe(true);
841
+ // Check web-backend rules
842
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-backend-overview.mdc'))).toBe(true);
843
+ });
844
+
845
+ it('should copy legacy .cursorrules/ files to .cursor/rules/ then remove legacy dir when cleanup confirmed', async () => {
846
+ const legacyDir = path.join(tempDir, LEGACY_CURSORRULES_DIR);
847
+ const cursorRulesDir = path.join(tempDir, '.cursor', 'rules');
848
+ fs.mkdirSync(legacyDir, { recursive: true });
849
+ fs.writeFileSync(path.join(legacyDir, 'old-rule.md'), '# Old rule');
850
+ fs.writeFileSync(path.join(legacyDir, 'custom-guide.md'), '# Custom guide');
851
+
852
+ await install(tempDir, ['web-frontend'], false, false, ['cursor'], true);
853
+
854
+ expect(fs.existsSync(legacyDir)).toBe(false);
855
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc'))).toBe(true);
856
+ expect(fs.existsSync(path.join(cursorRulesDir, 'old-rule.md'))).toBe(true);
857
+ expect(fs.readFileSync(path.join(cursorRulesDir, 'old-rule.md'), 'utf8')).toBe('# Old rule');
858
+ expect(fs.existsSync(path.join(cursorRulesDir, 'custom-guide.md'))).toBe(true);
859
+ expect(fs.readFileSync(path.join(cursorRulesDir, 'custom-guide.md'), 'utf8')).toBe('# Custom guide');
860
+ });
861
+
862
+ it('should not overwrite existing .cursor/rules/ files when migrating legacy', async () => {
863
+ const legacyDir = path.join(tempDir, LEGACY_CURSORRULES_DIR);
864
+ const cursorRulesDir = path.join(tempDir, '.cursor', 'rules');
865
+ fs.mkdirSync(cursorRulesDir, { recursive: true });
866
+ fs.writeFileSync(path.join(cursorRulesDir, 'my-rule.md'), '# New structure content');
867
+ fs.mkdirSync(legacyDir, { recursive: true });
868
+ fs.writeFileSync(path.join(legacyDir, 'my-rule.md'), '# Legacy content');
869
+
870
+ await install(tempDir, ['web-frontend'], false, false, ['cursor'], true);
871
+
872
+ expect(fs.readFileSync(path.join(cursorRulesDir, 'my-rule.md'), 'utf8')).toBe('# New structure content');
873
+ expect(fs.existsSync(legacyDir)).toBe(false);
874
+ });
875
+
876
+ it('should show legacy warning in dry-run mode without prompting', async () => {
877
+ // Create a legacy .cursorrules/ directory
878
+ const legacyDir = path.join(tempDir, LEGACY_CURSORRULES_DIR);
879
+ fs.mkdirSync(legacyDir, { recursive: true });
880
+ fs.writeFileSync(path.join(legacyDir, 'old-rule.md'), '# Old rule');
881
+
882
+ await install(tempDir, ['web-frontend'], true, false, ['cursor']);
883
+
884
+ // Legacy dir should still exist (dry-run doesn't modify)
885
+ expect(fs.existsSync(legacyDir)).toBe(true);
886
+ // Warning should have been printed
887
+ expect(consoleLogSpy).toHaveBeenCalledWith(
888
+ expect.stringContaining('Deprecated')
889
+ );
890
+ });
891
+ });
892
+
893
+ describe('remove', () => {
894
+ beforeEach(async () => {
895
+ // First install a template
896
+ await install(tempDir, ['web-frontend'], false, false, ['cursor']);
897
+ });
898
+
899
+ it('should remove template-specific files', async () => {
900
+ await remove(tempDir, ['web-frontend'], false, false, true, ['cursor']);
901
+
902
+ for (const rule of TEMPLATES['web-frontend'].rules) {
903
+ const prefixedName = `web-frontend-${rule}`;
904
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', prefixedName))).toBe(false);
905
+ }
906
+ });
907
+
908
+ it('should keep shared rules when removing template', async () => {
909
+ await remove(tempDir, ['web-frontend'], false, false, true, ['cursor']);
910
+
911
+ for (const rule of SHARED_RULES) {
912
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', rule))).toBe(true);
913
+ }
914
+ });
915
+
916
+ it('should not remove files in dry-run mode', async () => {
917
+ await remove(tempDir, ['web-frontend'], true, false, true, ['cursor']);
918
+
919
+ // Files should still exist
920
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc'))).toBe(true);
921
+ });
922
+
923
+ it('should skip modified files without force', async () => {
924
+ // Modify a file
925
+ const filePath = path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc');
926
+ fs.writeFileSync(filePath, '# Modified content');
927
+
928
+ await remove(tempDir, ['web-frontend'], false, false, true, ['cursor']);
929
+
930
+ // Modified file should still exist
931
+ expect(fs.existsSync(filePath)).toBe(true);
932
+ expect(fs.readFileSync(filePath, 'utf8')).toBe('# Modified content');
933
+ });
934
+
935
+ it('should remove modified files with force', async () => {
936
+ // Modify a file
937
+ const filePath = path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc');
938
+ fs.writeFileSync(filePath, '# Modified content');
939
+
940
+ await remove(tempDir, ['web-frontend'], false, true, true, ['cursor']);
941
+
942
+ // Modified file should be removed
943
+ expect(fs.existsSync(filePath)).toBe(false);
944
+ });
945
+
946
+ it('should also remove files from legacy .cursorrules/ directory', async () => {
947
+ // Manually create files in legacy location
948
+ const legacyDir = path.join(tempDir, LEGACY_CURSORRULES_DIR);
949
+ fs.mkdirSync(legacyDir, { recursive: true });
950
+ for (const rule of TEMPLATES['web-frontend'].rules) {
951
+ fs.writeFileSync(path.join(legacyDir, `web-frontend-${rule}`), '# legacy content');
952
+ }
953
+
954
+ await remove(tempDir, ['web-frontend'], false, true, true, ['cursor']);
955
+
956
+ // Both new and legacy files should be removed
957
+ for (const rule of TEMPLATES['web-frontend'].rules) {
958
+ const prefixedName = `web-frontend-${rule}`;
959
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', prefixedName))).toBe(false);
960
+ expect(fs.existsSync(path.join(legacyDir, prefixedName))).toBe(false);
961
+ }
962
+ });
963
+ });
964
+
965
+ describe('reset', () => {
966
+ beforeEach(async () => {
967
+ // Install templates
968
+ await install(tempDir, ['web-frontend', 'web-backend'], false, false, DEFAULT_IDES);
969
+ });
970
+
971
+ it('should remove all template files from .cursor/rules', async () => {
972
+ await reset(tempDir, false, false, true, ['cursor']);
973
+
974
+ // Template files should be removed
975
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc'))).toBe(false);
976
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-backend-overview.mdc'))).toBe(false);
977
+ });
978
+
979
+ it('should remove shared rules', async () => {
980
+ await reset(tempDir, false, false, true, ['cursor']);
981
+
982
+ for (const rule of SHARED_RULES) {
983
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', rule))).toBe(false);
984
+ }
985
+ });
986
+
987
+ it('should remove CLAUDE.md', async () => {
988
+ await reset(tempDir, false, false, true, ['claude']);
989
+
990
+ expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(false);
991
+ });
992
+
993
+ it('should remove copilot-instructions.md', async () => {
994
+ await reset(tempDir, false, false, true, ['codex']);
995
+
996
+ expect(fs.existsSync(path.join(tempDir, '.github', 'copilot-instructions.md'))).toBe(false);
997
+ });
998
+
999
+ it('should not remove files in dry-run mode', async () => {
1000
+ await reset(tempDir, true, false, true, DEFAULT_IDES);
1001
+
1002
+ // All files should still exist
1003
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules'))).toBe(true);
1004
+ expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
1005
+ });
1006
+
1007
+ it('should remove empty .cursor/rules directory', async () => {
1008
+ await reset(tempDir, false, false, true, ['cursor']);
1009
+
1010
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules'))).toBe(false);
1011
+ });
1012
+
1013
+ it('should keep .cursor/rules if non-template files remain', async () => {
1014
+ // Add a custom file
1015
+ fs.writeFileSync(path.join(tempDir, '.cursor', 'rules', 'my-custom-rules.md'), '# Custom');
1016
+
1017
+ await reset(tempDir, false, false, true, ['cursor']);
1018
+
1019
+ // Directory should still exist with custom file
1020
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules'))).toBe(true);
1021
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'my-custom-rules.md'))).toBe(true);
1022
+ });
1023
+
1024
+ it('should also clean up legacy .cursorrules/ directory', async () => {
1025
+ // Manually create legacy directory with template files
1026
+ const legacyDir = path.join(tempDir, LEGACY_CURSORRULES_DIR);
1027
+ fs.mkdirSync(legacyDir, { recursive: true });
1028
+ for (const rule of SHARED_RULES) {
1029
+ fs.writeFileSync(path.join(legacyDir, rule), '# legacy shared');
1030
+ }
1031
+ fs.writeFileSync(path.join(legacyDir, 'web-frontend-overview.mdc'), '# legacy template');
1032
+
1033
+ await reset(tempDir, false, true, true, ['cursor']);
1034
+
1035
+ // Both directories should be cleaned up
1036
+ expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', 'web-frontend-overview.mdc'))).toBe(false);
1037
+ expect(fs.existsSync(path.join(legacyDir, 'web-frontend-overview.mdc'))).toBe(false);
1038
+ });
1039
+ });
1040
+ });
1041
+
1042
+ // ============================================================================
1043
+ // CLI Argument Parsing Tests
1044
+ // ============================================================================
1045
+
1046
+ describe('CLI Argument Parsing', () => {
1047
+ let originalCwd;
1048
+ let tempDir;
1049
+ let exitSpy;
1050
+ let consoleLogSpy;
1051
+ let consoleErrorSpy;
1052
+
1053
+ beforeEach(() => {
1054
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-test-'));
1055
+ originalCwd = process.cwd();
1056
+ process.chdir(tempDir);
1057
+
1058
+ // Mock process.exit to prevent test from exiting
1059
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
1060
+ throw new Error('process.exit called');
1061
+ });
1062
+
1063
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
1064
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
1065
+ });
1066
+
1067
+ afterEach(() => {
1068
+ process.chdir(originalCwd);
1069
+ fs.rmSync(tempDir, { recursive: true, force: true });
1070
+ vi.restoreAllMocks();
1071
+ });
1072
+
1073
+ it('should show help with --help', async () => {
1074
+ await expect(run(['--help'])).rejects.toThrow('process.exit');
1075
+
1076
+ expect(exitSpy).toHaveBeenCalledWith(0);
1077
+ expect(consoleLogSpy).toHaveBeenCalled();
1078
+ });
1079
+
1080
+ it('should show help with -h', async () => {
1081
+ await expect(run(['-h'])).rejects.toThrow('process.exit');
1082
+
1083
+ expect(exitSpy).toHaveBeenCalledWith(0);
1084
+ });
1085
+
1086
+ it('should list templates with --list', async () => {
1087
+ await expect(run(['--list'])).rejects.toThrow('process.exit');
1088
+
1089
+ expect(exitSpy).toHaveBeenCalledWith(0);
1090
+ });
1091
+
1092
+ it('should list templates with -l', async () => {
1093
+ await expect(run(['-l'])).rejects.toThrow('process.exit');
1094
+
1095
+ expect(exitSpy).toHaveBeenCalledWith(0);
1096
+ });
1097
+
1098
+ it('should show version with --version', async () => {
1099
+ await expect(run(['--version'])).rejects.toThrow('process.exit');
1100
+
1101
+ expect(exitSpy).toHaveBeenCalledWith(0);
1102
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('@djm204/agent-skills'));
1103
+ });
1104
+
1105
+ it('should show version with -v', async () => {
1106
+ await expect(run(['-v'])).rejects.toThrow('process.exit');
1107
+
1108
+ expect(exitSpy).toHaveBeenCalledWith(0);
1109
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('@djm204/agent-skills'));
1110
+ });
1111
+
1112
+ it('should show changelog link with --version', async () => {
1113
+ await expect(run(['--version'])).rejects.toThrow('process.exit');
1114
+
1115
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('github.com'));
1116
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('releases/tag'));
1117
+ });
1118
+
1119
+ it('should error on unknown option', async () => {
1120
+ await expect(run(['--unknown-option'])).rejects.toThrow('process.exit');
1121
+
1122
+ expect(exitSpy).toHaveBeenCalledWith(1);
1123
+ expect(consoleErrorSpy).toHaveBeenCalled();
1124
+ });
1125
+
1126
+ it('should error when no templates specified', async () => {
1127
+ await expect(run([])).rejects.toThrow('process.exit');
1128
+
1129
+ expect(exitSpy).toHaveBeenCalledWith(1);
1130
+ });
1131
+
1132
+ it('should error on unknown template', async () => {
1133
+ await expect(run(['nonexistent-template'])).rejects.toThrow('process.exit');
1134
+
1135
+ expect(exitSpy).toHaveBeenCalledWith(1);
1136
+ });
1137
+
1138
+ it('should error on unknown IDE', async () => {
1139
+ await expect(run(['web-frontend', '--ide=unknown'])).rejects.toThrow('process.exit');
1140
+
1141
+ expect(exitSpy).toHaveBeenCalledWith(1);
1142
+ });
1143
+
1144
+ it('should accept valid template', async () => {
1145
+ await run(['web-frontend', '--dry-run']);
1146
+
1147
+ // Should not exit with error
1148
+ expect(exitSpy).not.toHaveBeenCalled();
1149
+ });
1150
+
1151
+ it('should accept multiple templates', async () => {
1152
+ await run(['web-frontend', 'web-backend', '--dry-run']);
1153
+
1154
+ expect(exitSpy).not.toHaveBeenCalled();
1155
+ });
1156
+
1157
+ it('should accept valid IDE option', async () => {
1158
+ await run(['web-frontend', '--ide=cursor', '--dry-run']);
1159
+
1160
+ expect(exitSpy).not.toHaveBeenCalled();
1161
+ });
1162
+
1163
+ it('should accept multiple IDE options', async () => {
1164
+ await run(['web-frontend', '--ide=cursor', '--ide=claude', '--dry-run']);
1165
+
1166
+ expect(exitSpy).not.toHaveBeenCalled();
1167
+ });
1168
+
1169
+ it('should error when using --remove and --reset together', async () => {
1170
+ await expect(run(['--remove', '--reset'])).rejects.toThrow('process.exit');
1171
+
1172
+ expect(exitSpy).toHaveBeenCalledWith(1);
1173
+ });
1174
+
1175
+ it('should error when --reset has template arguments', async () => {
1176
+ await expect(run(['--reset', 'web-frontend'])).rejects.toThrow('process.exit');
1177
+
1178
+ expect(exitSpy).toHaveBeenCalledWith(1);
1179
+ });
1180
+
1181
+ it('should error when --remove has no templates', async () => {
1182
+ await expect(run(['--remove'])).rejects.toThrow('process.exit');
1183
+
1184
+ expect(exitSpy).toHaveBeenCalledWith(1);
1185
+ });
1186
+
1187
+ it('should accept --remove with valid template', async () => {
1188
+ // First install, then remove
1189
+ await run(['web-frontend']);
1190
+ await run(['--remove', 'web-frontend', '--yes']);
1191
+
1192
+ expect(exitSpy).not.toHaveBeenCalled();
1193
+ });
1194
+
1195
+ it('should accept --reset with --yes', async () => {
1196
+ // First install, then reset
1197
+ await run(['web-frontend']);
1198
+ await run(['--reset', '--yes']);
1199
+
1200
+ expect(exitSpy).not.toHaveBeenCalled();
1201
+ });
1202
+
1203
+ it('should accept --force flag', async () => {
1204
+ await run(['web-frontend', '--force', '--dry-run']);
1205
+
1206
+ expect(exitSpy).not.toHaveBeenCalled();
1207
+ });
1208
+
1209
+ it('should accept -f shorthand for force', async () => {
1210
+ await run(['web-frontend', '-f', '--dry-run']);
1211
+
1212
+ expect(exitSpy).not.toHaveBeenCalled();
1213
+ });
1214
+
1215
+ it('should accept -y shorthand for yes', async () => {
1216
+ await run(['web-frontend']);
1217
+ await run(['--reset', '-y']);
1218
+
1219
+ expect(exitSpy).not.toHaveBeenCalled();
1220
+ });
1221
+
1222
+ it('should resolve shorthand alias "js" to javascript-expert', async () => {
1223
+ await run(['js', '--dry-run']);
1224
+
1225
+ expect(exitSpy).not.toHaveBeenCalled();
1226
+ });
1227
+
1228
+ it('should resolve shorthand alias "go" to golang-expert', async () => {
1229
+ await run(['go', '--dry-run']);
1230
+
1231
+ expect(exitSpy).not.toHaveBeenCalled();
1232
+ });
1233
+
1234
+ it('should resolve shorthand alias "py" to python-expert', async () => {
1235
+ await run(['py', '--dry-run']);
1236
+
1237
+ expect(exitSpy).not.toHaveBeenCalled();
1238
+ });
1239
+
1240
+ it('should resolve shorthand alias "rs" to rust-expert', async () => {
1241
+ await run(['rs', '--dry-run']);
1242
+
1243
+ expect(exitSpy).not.toHaveBeenCalled();
1244
+ });
1245
+
1246
+ it('should resolve shorthand alias "kt" to kotlin-expert', async () => {
1247
+ await run(['kt', '--dry-run']);
1248
+
1249
+ expect(exitSpy).not.toHaveBeenCalled();
1250
+ });
1251
+
1252
+ it('should resolve aliases in --remove mode', async () => {
1253
+ await run(['go']);
1254
+ await run(['--remove', 'go', '--yes']);
1255
+
1256
+ expect(exitSpy).not.toHaveBeenCalled();
1257
+ });
1258
+
1259
+ it('should still accept canonical template names', async () => {
1260
+ await run(['javascript-expert', '--dry-run']);
1261
+
1262
+ expect(exitSpy).not.toHaveBeenCalled();
1263
+ });
1264
+ });