@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.
- package/README.md +597 -0
- package/bin/cli.js +8 -0
- package/package.json +55 -0
- package/src/index.js +1817 -0
- package/src/index.test.js +1264 -0
- package/templates/_shared/code-quality.mdc +52 -0
- package/templates/_shared/communication.mdc +43 -0
- package/templates/_shared/core-principles.mdc +67 -0
- package/templates/_shared/git-workflow.mdc +48 -0
- package/templates/_shared/security-fundamentals.mdc +41 -0
- package/templates/agents/utility-agent/.cursor/rules/action-control.mdc +71 -0
- package/templates/agents/utility-agent/.cursor/rules/context-management.mdc +61 -0
- package/templates/agents/utility-agent/.cursor/rules/hallucination-prevention.mdc +58 -0
- package/templates/agents/utility-agent/.cursor/rules/overview.mdc +34 -0
- package/templates/agents/utility-agent/.cursor/rules/token-optimization.mdc +71 -0
- package/templates/agents/utility-agent/CLAUDE.md +513 -0
- package/templates/business/market-intelligence/.cursor/rules/data-sources.mdc +62 -0
- package/templates/business/market-intelligence/.cursor/rules/overview.mdc +55 -0
- package/templates/business/market-intelligence/.cursor/rules/reporting.mdc +59 -0
- package/templates/business/market-intelligence/.cursor/rules/risk-signals.mdc +63 -0
- package/templates/business/market-intelligence/.cursor/rules/sentiment-analysis.mdc +70 -0
- package/templates/business/market-intelligence/.cursor/rules/trend-detection.mdc +72 -0
- package/templates/business/market-intelligence/CLAUDE.md +371 -0
- package/templates/business/marketing-expert/.cursor/rules/brand-strategy.mdc +74 -0
- package/templates/business/marketing-expert/.cursor/rules/campaign-planning.mdc +60 -0
- package/templates/business/marketing-expert/.cursor/rules/growth-frameworks.mdc +69 -0
- package/templates/business/marketing-expert/.cursor/rules/market-analysis.mdc +70 -0
- package/templates/business/marketing-expert/.cursor/rules/marketing-analytics.mdc +71 -0
- package/templates/business/marketing-expert/.cursor/rules/overview.mdc +56 -0
- package/templates/business/marketing-expert/CLAUDE.md +567 -0
- package/templates/business/predictive-maintenance/.cursor/rules/alerting.mdc +56 -0
- package/templates/business/predictive-maintenance/.cursor/rules/asset-lifecycle.mdc +71 -0
- package/templates/business/predictive-maintenance/.cursor/rules/failure-prediction.mdc +65 -0
- package/templates/business/predictive-maintenance/.cursor/rules/maintenance-scheduling.mdc +61 -0
- package/templates/business/predictive-maintenance/.cursor/rules/overview.mdc +55 -0
- package/templates/business/predictive-maintenance/.cursor/rules/sensor-analytics.mdc +66 -0
- package/templates/business/predictive-maintenance/CLAUDE.md +529 -0
- package/templates/business/product-manager/.cursor/rules/communication.mdc +77 -0
- package/templates/business/product-manager/.cursor/rules/discovery.mdc +79 -0
- package/templates/business/product-manager/.cursor/rules/metrics.mdc +75 -0
- package/templates/business/product-manager/.cursor/rules/overview.mdc +47 -0
- package/templates/business/product-manager/.cursor/rules/prioritization.mdc +66 -0
- package/templates/business/product-manager/.cursor/rules/requirements.mdc +79 -0
- package/templates/business/product-manager/CLAUDE.md +593 -0
- package/templates/business/project-manager/.cursor/rules/overview.mdc +53 -0
- package/templates/business/project-manager/.cursor/rules/reporting.mdc +68 -0
- package/templates/business/project-manager/.cursor/rules/risk-management.mdc +71 -0
- package/templates/business/project-manager/.cursor/rules/scheduling.mdc +67 -0
- package/templates/business/project-manager/.cursor/rules/scope-management.mdc +66 -0
- package/templates/business/project-manager/.cursor/rules/stakeholder-management.mdc +70 -0
- package/templates/business/project-manager/CLAUDE.md +540 -0
- package/templates/business/regulatory-sentinel/.cursor/rules/compliance-tracking.mdc +74 -0
- package/templates/business/regulatory-sentinel/.cursor/rules/impact-assessment.mdc +62 -0
- package/templates/business/regulatory-sentinel/.cursor/rules/monitoring.mdc +67 -0
- package/templates/business/regulatory-sentinel/.cursor/rules/overview.mdc +55 -0
- package/templates/business/regulatory-sentinel/.cursor/rules/reporting.mdc +61 -0
- package/templates/business/regulatory-sentinel/.cursor/rules/risk-classification.mdc +73 -0
- package/templates/business/regulatory-sentinel/CLAUDE.md +572 -0
- package/templates/business/resource-allocator/.cursor/rules/capacity-modeling.mdc +65 -0
- package/templates/business/resource-allocator/.cursor/rules/coordination.mdc +67 -0
- package/templates/business/resource-allocator/.cursor/rules/crisis-management.mdc +64 -0
- package/templates/business/resource-allocator/.cursor/rules/demand-prediction.mdc +52 -0
- package/templates/business/resource-allocator/.cursor/rules/overview.mdc +76 -0
- package/templates/business/resource-allocator/.cursor/rules/scheduling.mdc +63 -0
- package/templates/business/resource-allocator/CLAUDE.md +525 -0
- package/templates/business/strategic-negotiator/.cursor/rules/contract-analysis.mdc +60 -0
- package/templates/business/strategic-negotiator/.cursor/rules/deal-structuring.mdc +66 -0
- package/templates/business/strategic-negotiator/.cursor/rules/game-theory.mdc +64 -0
- package/templates/business/strategic-negotiator/.cursor/rules/overview.mdc +55 -0
- package/templates/business/strategic-negotiator/.cursor/rules/preparation.mdc +79 -0
- package/templates/business/strategic-negotiator/.cursor/rules/scenario-modeling.mdc +66 -0
- package/templates/business/strategic-negotiator/CLAUDE.md +640 -0
- package/templates/business/supply-chain/.cursor/rules/cost-modeling.mdc +67 -0
- package/templates/business/supply-chain/.cursor/rules/demand-forecasting.mdc +67 -0
- package/templates/business/supply-chain/.cursor/rules/inventory-management.mdc +69 -0
- package/templates/business/supply-chain/.cursor/rules/logistics.mdc +61 -0
- package/templates/business/supply-chain/.cursor/rules/overview.mdc +64 -0
- package/templates/business/supply-chain/.cursor/rules/supplier-evaluation.mdc +66 -0
- package/templates/business/supply-chain/CLAUDE.md +590 -0
- package/templates/business/supply-chain-harmonizer/.cursor/rules/disruption-response.mdc +67 -0
- package/templates/business/supply-chain-harmonizer/.cursor/rules/inventory-rebalancing.mdc +63 -0
- package/templates/business/supply-chain-harmonizer/.cursor/rules/overview.mdc +65 -0
- package/templates/business/supply-chain-harmonizer/.cursor/rules/rerouting.mdc +64 -0
- package/templates/business/supply-chain-harmonizer/.cursor/rules/scenario-simulation.mdc +68 -0
- package/templates/business/supply-chain-harmonizer/.cursor/rules/stakeholder-notifications.mdc +61 -0
- package/templates/business/supply-chain-harmonizer/CLAUDE.md +600 -0
- package/templates/creative/brand-guardian/.cursor/rules/brand-voice.mdc +64 -0
- package/templates/creative/brand-guardian/.cursor/rules/content-review.mdc +47 -0
- package/templates/creative/brand-guardian/.cursor/rules/ethical-guidelines.mdc +47 -0
- package/templates/creative/brand-guardian/.cursor/rules/multi-channel.mdc +49 -0
- package/templates/creative/brand-guardian/.cursor/rules/overview.mdc +58 -0
- package/templates/creative/brand-guardian/.cursor/rules/visual-identity.mdc +64 -0
- package/templates/creative/brand-guardian/CLAUDE.md +634 -0
- package/templates/creative/content-creation-expert/.cursor/rules/content-strategy.mdc +65 -0
- package/templates/creative/content-creation-expert/.cursor/rules/copywriting.mdc +59 -0
- package/templates/creative/content-creation-expert/.cursor/rules/editorial-operations.mdc +65 -0
- package/templates/creative/content-creation-expert/.cursor/rules/multimedia-production.mdc +64 -0
- package/templates/creative/content-creation-expert/.cursor/rules/overview.mdc +58 -0
- package/templates/creative/content-creation-expert/.cursor/rules/seo-content.mdc +75 -0
- package/templates/creative/content-creation-expert/CLAUDE.md +568 -0
- package/templates/creative/narrative-architect/.cursor/rules/collaboration.mdc +62 -0
- package/templates/creative/narrative-architect/.cursor/rules/continuity-tracking.mdc +56 -0
- package/templates/creative/narrative-architect/.cursor/rules/overview.mdc +68 -0
- package/templates/creative/narrative-architect/.cursor/rules/story-bible.mdc +77 -0
- package/templates/creative/narrative-architect/.cursor/rules/timeline-management.mdc +60 -0
- package/templates/creative/narrative-architect/.cursor/rules/world-building.mdc +78 -0
- package/templates/creative/narrative-architect/CLAUDE.md +737 -0
- package/templates/creative/social-media-expert/.cursor/rules/audience-growth.mdc +62 -0
- package/templates/creative/social-media-expert/.cursor/rules/community-management.mdc +67 -0
- package/templates/creative/social-media-expert/.cursor/rules/content-strategy.mdc +60 -0
- package/templates/creative/social-media-expert/.cursor/rules/overview.mdc +48 -0
- package/templates/creative/social-media-expert/.cursor/rules/platform-strategy.mdc +64 -0
- package/templates/creative/social-media-expert/.cursor/rules/social-analytics.mdc +64 -0
- package/templates/creative/social-media-expert/CLAUDE.md +624 -0
- package/templates/creative/trend-forecaster/.cursor/rules/cultural-analysis.mdc +59 -0
- package/templates/creative/trend-forecaster/.cursor/rules/forecasting-methods.mdc +63 -0
- package/templates/creative/trend-forecaster/.cursor/rules/overview.mdc +58 -0
- package/templates/creative/trend-forecaster/.cursor/rules/reporting.mdc +61 -0
- package/templates/creative/trend-forecaster/.cursor/rules/signal-analysis.mdc +74 -0
- package/templates/creative/trend-forecaster/.cursor/rules/trend-lifecycle.mdc +75 -0
- package/templates/creative/trend-forecaster/CLAUDE.md +717 -0
- package/templates/creative/ux-designer/.cursor/rules/accessibility.mdc +69 -0
- package/templates/creative/ux-designer/.cursor/rules/emotional-design.mdc +59 -0
- package/templates/creative/ux-designer/.cursor/rules/handoff.mdc +73 -0
- package/templates/creative/ux-designer/.cursor/rules/information-architecture.mdc +62 -0
- package/templates/creative/ux-designer/.cursor/rules/interaction-design.mdc +66 -0
- package/templates/creative/ux-designer/.cursor/rules/overview.mdc +61 -0
- package/templates/creative/ux-designer/.cursor/rules/research.mdc +61 -0
- package/templates/creative/ux-designer/.cursor/rules/visual-design.mdc +68 -0
- package/templates/creative/ux-designer/CLAUDE.md +124 -0
- package/templates/dogfood/project-overview.mdc +12 -0
- package/templates/dogfood/project-structure.mdc +82 -0
- package/templates/dogfood/rules-creation-best-practices.mdc +45 -0
- package/templates/education/educator/.cursor/rules/accessibility.mdc +67 -0
- package/templates/education/educator/.cursor/rules/assessment.mdc +68 -0
- package/templates/education/educator/.cursor/rules/curriculum.mdc +57 -0
- package/templates/education/educator/.cursor/rules/engagement.mdc +65 -0
- package/templates/education/educator/.cursor/rules/instructional-design.mdc +69 -0
- package/templates/education/educator/.cursor/rules/overview.mdc +57 -0
- package/templates/education/educator/.cursor/rules/retention.mdc +64 -0
- package/templates/education/educator/CLAUDE.md +338 -0
- package/templates/engineering/blockchain/.cursor/rules/defi-patterns.mdc +48 -0
- package/templates/engineering/blockchain/.cursor/rules/gas-optimization.mdc +77 -0
- package/templates/engineering/blockchain/.cursor/rules/overview.mdc +41 -0
- package/templates/engineering/blockchain/.cursor/rules/security.mdc +61 -0
- package/templates/engineering/blockchain/.cursor/rules/smart-contracts.mdc +64 -0
- package/templates/engineering/blockchain/.cursor/rules/testing.mdc +77 -0
- package/templates/engineering/blockchain/.cursor/rules/web3-integration.mdc +47 -0
- package/templates/engineering/blockchain/CLAUDE.md +389 -0
- package/templates/engineering/cli-tools/.cursor/rules/architecture.mdc +76 -0
- package/templates/engineering/cli-tools/.cursor/rules/arguments.mdc +65 -0
- package/templates/engineering/cli-tools/.cursor/rules/distribution.mdc +40 -0
- package/templates/engineering/cli-tools/.cursor/rules/error-handling.mdc +67 -0
- package/templates/engineering/cli-tools/.cursor/rules/overview.mdc +58 -0
- package/templates/engineering/cli-tools/.cursor/rules/testing.mdc +42 -0
- package/templates/engineering/cli-tools/.cursor/rules/user-experience.mdc +43 -0
- package/templates/engineering/cli-tools/CLAUDE.md +356 -0
- package/templates/engineering/data-engineering/.cursor/rules/data-modeling.mdc +71 -0
- package/templates/engineering/data-engineering/.cursor/rules/data-quality.mdc +78 -0
- package/templates/engineering/data-engineering/.cursor/rules/overview.mdc +49 -0
- package/templates/engineering/data-engineering/.cursor/rules/performance.mdc +71 -0
- package/templates/engineering/data-engineering/.cursor/rules/pipeline-design.mdc +79 -0
- package/templates/engineering/data-engineering/.cursor/rules/security.mdc +79 -0
- package/templates/engineering/data-engineering/.cursor/rules/testing.mdc +75 -0
- package/templates/engineering/data-engineering/CLAUDE.md +974 -0
- package/templates/engineering/devops-sre/.cursor/rules/capacity-planning.mdc +49 -0
- package/templates/engineering/devops-sre/.cursor/rules/change-management.mdc +51 -0
- package/templates/engineering/devops-sre/.cursor/rules/chaos-engineering.mdc +50 -0
- package/templates/engineering/devops-sre/.cursor/rules/disaster-recovery.mdc +54 -0
- package/templates/engineering/devops-sre/.cursor/rules/incident-management.mdc +56 -0
- package/templates/engineering/devops-sre/.cursor/rules/observability.mdc +50 -0
- package/templates/engineering/devops-sre/.cursor/rules/overview.mdc +76 -0
- package/templates/engineering/devops-sre/.cursor/rules/postmortems.mdc +49 -0
- package/templates/engineering/devops-sre/.cursor/rules/runbooks.mdc +49 -0
- package/templates/engineering/devops-sre/.cursor/rules/slo-sli.mdc +46 -0
- package/templates/engineering/devops-sre/.cursor/rules/toil-reduction.mdc +52 -0
- package/templates/engineering/devops-sre/CLAUDE.md +1007 -0
- package/templates/engineering/fullstack/.cursor/rules/api-contracts.mdc +79 -0
- package/templates/engineering/fullstack/.cursor/rules/architecture.mdc +79 -0
- package/templates/engineering/fullstack/.cursor/rules/overview.mdc +61 -0
- package/templates/engineering/fullstack/.cursor/rules/shared-types.mdc +77 -0
- package/templates/engineering/fullstack/.cursor/rules/testing.mdc +72 -0
- package/templates/engineering/fullstack/CLAUDE.md +349 -0
- package/templates/engineering/ml-ai/.cursor/rules/data-engineering.mdc +71 -0
- package/templates/engineering/ml-ai/.cursor/rules/deployment.mdc +43 -0
- package/templates/engineering/ml-ai/.cursor/rules/model-development.mdc +44 -0
- package/templates/engineering/ml-ai/.cursor/rules/monitoring.mdc +45 -0
- package/templates/engineering/ml-ai/.cursor/rules/overview.mdc +42 -0
- package/templates/engineering/ml-ai/.cursor/rules/security.mdc +51 -0
- package/templates/engineering/ml-ai/.cursor/rules/testing.mdc +44 -0
- package/templates/engineering/ml-ai/CLAUDE.md +1136 -0
- package/templates/engineering/mobile/.cursor/rules/navigation.mdc +75 -0
- package/templates/engineering/mobile/.cursor/rules/offline-first.mdc +68 -0
- package/templates/engineering/mobile/.cursor/rules/overview.mdc +76 -0
- package/templates/engineering/mobile/.cursor/rules/performance.mdc +78 -0
- package/templates/engineering/mobile/.cursor/rules/testing.mdc +77 -0
- package/templates/engineering/mobile/CLAUDE.md +233 -0
- package/templates/engineering/platform-engineering/.cursor/rules/ci-cd.mdc +51 -0
- package/templates/engineering/platform-engineering/.cursor/rules/developer-experience.mdc +48 -0
- package/templates/engineering/platform-engineering/.cursor/rules/infrastructure-as-code.mdc +62 -0
- package/templates/engineering/platform-engineering/.cursor/rules/kubernetes.mdc +51 -0
- package/templates/engineering/platform-engineering/.cursor/rules/observability.mdc +52 -0
- package/templates/engineering/platform-engineering/.cursor/rules/overview.mdc +44 -0
- package/templates/engineering/platform-engineering/.cursor/rules/security.mdc +74 -0
- package/templates/engineering/platform-engineering/.cursor/rules/testing.mdc +59 -0
- package/templates/engineering/platform-engineering/CLAUDE.md +850 -0
- package/templates/engineering/qa-engineering/.cursor/rules/automation.mdc +71 -0
- package/templates/engineering/qa-engineering/.cursor/rules/metrics.mdc +68 -0
- package/templates/engineering/qa-engineering/.cursor/rules/overview.mdc +45 -0
- package/templates/engineering/qa-engineering/.cursor/rules/quality-gates.mdc +54 -0
- package/templates/engineering/qa-engineering/.cursor/rules/test-design.mdc +59 -0
- package/templates/engineering/qa-engineering/.cursor/rules/test-strategy.mdc +62 -0
- package/templates/engineering/qa-engineering/CLAUDE.md +726 -0
- package/templates/engineering/testing/.cursor/rules/advanced-techniques.mdc +44 -0
- package/templates/engineering/testing/.cursor/rules/ci-cd-integration.mdc +43 -0
- package/templates/engineering/testing/.cursor/rules/overview.mdc +61 -0
- package/templates/engineering/testing/.cursor/rules/performance-testing.mdc +39 -0
- package/templates/engineering/testing/.cursor/rules/quality-metrics.mdc +74 -0
- package/templates/engineering/testing/.cursor/rules/reliability.mdc +39 -0
- package/templates/engineering/testing/.cursor/rules/tdd-methodology.mdc +52 -0
- package/templates/engineering/testing/.cursor/rules/test-data.mdc +46 -0
- package/templates/engineering/testing/.cursor/rules/test-design.mdc +45 -0
- package/templates/engineering/testing/.cursor/rules/test-types.mdc +71 -0
- package/templates/engineering/testing/CLAUDE.md +1134 -0
- package/templates/engineering/unity-dev-expert/.cursor/rules/csharp-architecture.mdc +61 -0
- package/templates/engineering/unity-dev-expert/.cursor/rules/multiplayer-networking.mdc +67 -0
- package/templates/engineering/unity-dev-expert/.cursor/rules/overview.mdc +56 -0
- package/templates/engineering/unity-dev-expert/.cursor/rules/performance-optimization.mdc +76 -0
- package/templates/engineering/unity-dev-expert/.cursor/rules/physics-rendering.mdc +59 -0
- package/templates/engineering/unity-dev-expert/.cursor/rules/ui-systems.mdc +59 -0
- package/templates/engineering/unity-dev-expert/CLAUDE.md +534 -0
- package/templates/engineering/web-backend/.cursor/rules/api-design.mdc +64 -0
- package/templates/engineering/web-backend/.cursor/rules/authentication.mdc +69 -0
- package/templates/engineering/web-backend/.cursor/rules/database-patterns.mdc +73 -0
- package/templates/engineering/web-backend/.cursor/rules/error-handling.mdc +66 -0
- package/templates/engineering/web-backend/.cursor/rules/overview.mdc +74 -0
- package/templates/engineering/web-backend/.cursor/rules/security.mdc +60 -0
- package/templates/engineering/web-backend/.cursor/rules/testing.mdc +74 -0
- package/templates/engineering/web-backend/CLAUDE.md +366 -0
- package/templates/engineering/web-frontend/.cursor/rules/accessibility.mdc +75 -0
- package/templates/engineering/web-frontend/.cursor/rules/component-patterns.mdc +76 -0
- package/templates/engineering/web-frontend/.cursor/rules/overview.mdc +77 -0
- package/templates/engineering/web-frontend/.cursor/rules/performance.mdc +73 -0
- package/templates/engineering/web-frontend/.cursor/rules/state-management.mdc +71 -0
- package/templates/engineering/web-frontend/.cursor/rules/styling.mdc +69 -0
- package/templates/engineering/web-frontend/.cursor/rules/testing.mdc +75 -0
- package/templates/engineering/web-frontend/CLAUDE.md +399 -0
- package/templates/languages/cpp-expert/.cursor/rules/concurrency.mdc +68 -0
- package/templates/languages/cpp-expert/.cursor/rules/error-handling.mdc +65 -0
- package/templates/languages/cpp-expert/.cursor/rules/memory-and-ownership.mdc +68 -0
- package/templates/languages/cpp-expert/.cursor/rules/modern-cpp.mdc +75 -0
- package/templates/languages/cpp-expert/.cursor/rules/overview.mdc +37 -0
- package/templates/languages/cpp-expert/.cursor/rules/performance.mdc +74 -0
- package/templates/languages/cpp-expert/.cursor/rules/testing.mdc +70 -0
- package/templates/languages/cpp-expert/.cursor/rules/tooling.mdc +77 -0
- package/templates/languages/cpp-expert/CLAUDE.md +242 -0
- package/templates/languages/csharp-expert/.cursor/rules/aspnet-core.mdc +78 -0
- package/templates/languages/csharp-expert/.cursor/rules/async-patterns.mdc +71 -0
- package/templates/languages/csharp-expert/.cursor/rules/dependency-injection.mdc +76 -0
- package/templates/languages/csharp-expert/.cursor/rules/error-handling.mdc +65 -0
- package/templates/languages/csharp-expert/.cursor/rules/language-features.mdc +74 -0
- package/templates/languages/csharp-expert/.cursor/rules/overview.mdc +47 -0
- package/templates/languages/csharp-expert/.cursor/rules/performance.mdc +66 -0
- package/templates/languages/csharp-expert/.cursor/rules/testing.mdc +78 -0
- package/templates/languages/csharp-expert/.cursor/rules/tooling.mdc +78 -0
- package/templates/languages/csharp-expert/CLAUDE.md +360 -0
- package/templates/languages/golang-expert/.cursor/rules/concurrency.mdc +79 -0
- package/templates/languages/golang-expert/.cursor/rules/error-handling.mdc +77 -0
- package/templates/languages/golang-expert/.cursor/rules/interfaces-and-types.mdc +77 -0
- package/templates/languages/golang-expert/.cursor/rules/overview.mdc +74 -0
- package/templates/languages/golang-expert/.cursor/rules/performance.mdc +76 -0
- package/templates/languages/golang-expert/.cursor/rules/production-patterns.mdc +76 -0
- package/templates/languages/golang-expert/.cursor/rules/stdlib-and-tooling.mdc +68 -0
- package/templates/languages/golang-expert/.cursor/rules/testing.mdc +77 -0
- package/templates/languages/golang-expert/CLAUDE.md +361 -0
- package/templates/languages/java-expert/.cursor/rules/concurrency.mdc +69 -0
- package/templates/languages/java-expert/.cursor/rules/error-handling.mdc +70 -0
- package/templates/languages/java-expert/.cursor/rules/modern-java.mdc +74 -0
- package/templates/languages/java-expert/.cursor/rules/overview.mdc +42 -0
- package/templates/languages/java-expert/.cursor/rules/performance.mdc +69 -0
- package/templates/languages/java-expert/.cursor/rules/persistence.mdc +74 -0
- package/templates/languages/java-expert/.cursor/rules/spring-boot.mdc +73 -0
- package/templates/languages/java-expert/.cursor/rules/testing.mdc +79 -0
- package/templates/languages/java-expert/.cursor/rules/tooling.mdc +76 -0
- package/templates/languages/java-expert/CLAUDE.md +325 -0
- package/templates/languages/javascript-expert/.cursor/rules/language-deep-dive.mdc +74 -0
- package/templates/languages/javascript-expert/.cursor/rules/node-patterns.mdc +77 -0
- package/templates/languages/javascript-expert/.cursor/rules/overview.mdc +66 -0
- package/templates/languages/javascript-expert/.cursor/rules/performance.mdc +64 -0
- package/templates/languages/javascript-expert/.cursor/rules/react-patterns.mdc +70 -0
- package/templates/languages/javascript-expert/.cursor/rules/testing.mdc +76 -0
- package/templates/languages/javascript-expert/.cursor/rules/tooling.mdc +72 -0
- package/templates/languages/javascript-expert/.cursor/rules/typescript-deep-dive.mdc +77 -0
- package/templates/languages/javascript-expert/CLAUDE.md +479 -0
- package/templates/languages/kotlin-expert/.cursor/rules/coroutines.mdc +75 -0
- package/templates/languages/kotlin-expert/.cursor/rules/error-handling.mdc +69 -0
- package/templates/languages/kotlin-expert/.cursor/rules/frameworks.mdc +76 -0
- package/templates/languages/kotlin-expert/.cursor/rules/language-features.mdc +78 -0
- package/templates/languages/kotlin-expert/.cursor/rules/overview.mdc +38 -0
- package/templates/languages/kotlin-expert/.cursor/rules/performance.mdc +73 -0
- package/templates/languages/kotlin-expert/.cursor/rules/testing.mdc +70 -0
- package/templates/languages/kotlin-expert/.cursor/rules/tooling.mdc +67 -0
- package/templates/languages/kotlin-expert/CLAUDE.md +276 -0
- package/templates/languages/python-expert/.cursor/rules/async-python.mdc +71 -0
- package/templates/languages/python-expert/.cursor/rules/overview.mdc +76 -0
- package/templates/languages/python-expert/.cursor/rules/patterns-and-idioms.mdc +77 -0
- package/templates/languages/python-expert/.cursor/rules/performance.mdc +74 -0
- package/templates/languages/python-expert/.cursor/rules/testing.mdc +77 -0
- package/templates/languages/python-expert/.cursor/rules/tooling.mdc +77 -0
- package/templates/languages/python-expert/.cursor/rules/type-system.mdc +77 -0
- package/templates/languages/python-expert/.cursor/rules/web-and-apis.mdc +76 -0
- package/templates/languages/python-expert/CLAUDE.md +264 -0
- package/templates/languages/ruby-expert/.cursor/rules/concurrency-and-threading.mdc +65 -0
- package/templates/languages/ruby-expert/.cursor/rules/error-handling.mdc +69 -0
- package/templates/languages/ruby-expert/.cursor/rules/idioms-and-style.mdc +76 -0
- package/templates/languages/ruby-expert/.cursor/rules/overview.mdc +60 -0
- package/templates/languages/ruby-expert/.cursor/rules/performance.mdc +68 -0
- package/templates/languages/ruby-expert/.cursor/rules/rails-and-frameworks.mdc +60 -0
- package/templates/languages/ruby-expert/.cursor/rules/testing.mdc +56 -0
- package/templates/languages/ruby-expert/.cursor/rules/tooling.mdc +52 -0
- package/templates/languages/ruby-expert/CLAUDE.md +102 -0
- package/templates/languages/rust-expert/.cursor/rules/concurrency.mdc +69 -0
- package/templates/languages/rust-expert/.cursor/rules/ecosystem-and-tooling.mdc +76 -0
- package/templates/languages/rust-expert/.cursor/rules/error-handling.mdc +76 -0
- package/templates/languages/rust-expert/.cursor/rules/overview.mdc +62 -0
- package/templates/languages/rust-expert/.cursor/rules/ownership-and-borrowing.mdc +70 -0
- package/templates/languages/rust-expert/.cursor/rules/performance-and-unsafe.mdc +70 -0
- package/templates/languages/rust-expert/.cursor/rules/testing.mdc +73 -0
- package/templates/languages/rust-expert/.cursor/rules/traits-and-generics.mdc +76 -0
- package/templates/languages/rust-expert/CLAUDE.md +283 -0
- package/templates/languages/swift-expert/.cursor/rules/concurrency.mdc +77 -0
- package/templates/languages/swift-expert/.cursor/rules/error-handling.mdc +76 -0
- package/templates/languages/swift-expert/.cursor/rules/language-features.mdc +78 -0
- package/templates/languages/swift-expert/.cursor/rules/overview.mdc +46 -0
- package/templates/languages/swift-expert/.cursor/rules/performance.mdc +69 -0
- package/templates/languages/swift-expert/.cursor/rules/swiftui.mdc +77 -0
- package/templates/languages/swift-expert/.cursor/rules/testing.mdc +75 -0
- package/templates/languages/swift-expert/.cursor/rules/tooling.mdc +77 -0
- package/templates/languages/swift-expert/CLAUDE.md +275 -0
- package/templates/professional/documentation/.cursor/rules/adr.mdc +65 -0
- package/templates/professional/documentation/.cursor/rules/api-documentation.mdc +64 -0
- package/templates/professional/documentation/.cursor/rules/code-comments.mdc +75 -0
- package/templates/professional/documentation/.cursor/rules/maintenance.mdc +58 -0
- package/templates/professional/documentation/.cursor/rules/overview.mdc +48 -0
- package/templates/professional/documentation/.cursor/rules/readme-standards.mdc +70 -0
- package/templates/professional/documentation/CLAUDE.md +120 -0
- package/templates/professional/executive-assistant/.cursor/rules/calendar.mdc +51 -0
- package/templates/professional/executive-assistant/.cursor/rules/confidentiality.mdc +53 -0
- package/templates/professional/executive-assistant/.cursor/rules/email.mdc +49 -0
- package/templates/professional/executive-assistant/.cursor/rules/meetings.mdc +39 -0
- package/templates/professional/executive-assistant/.cursor/rules/overview.mdc +42 -0
- package/templates/professional/executive-assistant/.cursor/rules/prioritization.mdc +48 -0
- package/templates/professional/executive-assistant/.cursor/rules/stakeholder-management.mdc +50 -0
- package/templates/professional/executive-assistant/.cursor/rules/travel.mdc +43 -0
- package/templates/professional/executive-assistant/CLAUDE.md +620 -0
- package/templates/professional/grant-writer/.cursor/rules/budgets.mdc +55 -0
- package/templates/professional/grant-writer/.cursor/rules/compliance.mdc +47 -0
- package/templates/professional/grant-writer/.cursor/rules/funding-research.mdc +47 -0
- package/templates/professional/grant-writer/.cursor/rules/narrative.mdc +58 -0
- package/templates/professional/grant-writer/.cursor/rules/overview.mdc +68 -0
- package/templates/professional/grant-writer/.cursor/rules/post-award.mdc +59 -0
- package/templates/professional/grant-writer/.cursor/rules/review-criteria.mdc +51 -0
- package/templates/professional/grant-writer/.cursor/rules/sustainability.mdc +48 -0
- package/templates/professional/grant-writer/CLAUDE.md +577 -0
- package/templates/professional/knowledge-synthesis/.cursor/rules/document-management.mdc +51 -0
- package/templates/professional/knowledge-synthesis/.cursor/rules/knowledge-graphs.mdc +63 -0
- package/templates/professional/knowledge-synthesis/.cursor/rules/overview.mdc +74 -0
- package/templates/professional/knowledge-synthesis/.cursor/rules/research-workflow.mdc +50 -0
- package/templates/professional/knowledge-synthesis/.cursor/rules/search-retrieval.mdc +62 -0
- package/templates/professional/knowledge-synthesis/.cursor/rules/summarization.mdc +61 -0
- package/templates/professional/knowledge-synthesis/CLAUDE.md +593 -0
- package/templates/professional/life-logistics/.cursor/rules/financial-optimization.mdc +78 -0
- package/templates/professional/life-logistics/.cursor/rules/negotiation.mdc +68 -0
- package/templates/professional/life-logistics/.cursor/rules/overview.mdc +75 -0
- package/templates/professional/life-logistics/.cursor/rules/research-methodology.mdc +76 -0
- package/templates/professional/life-logistics/.cursor/rules/scheduling.mdc +68 -0
- package/templates/professional/life-logistics/.cursor/rules/task-management.mdc +47 -0
- package/templates/professional/life-logistics/CLAUDE.md +601 -0
- package/templates/professional/research-assistant/.cursor/rules/citation-attribution.mdc +61 -0
- package/templates/professional/research-assistant/.cursor/rules/information-synthesis.mdc +65 -0
- package/templates/professional/research-assistant/.cursor/rules/overview.mdc +56 -0
- package/templates/professional/research-assistant/.cursor/rules/research-methodologies.mdc +54 -0
- package/templates/professional/research-assistant/.cursor/rules/search-strategies.mdc +57 -0
- package/templates/professional/research-assistant/.cursor/rules/source-evaluation.mdc +59 -0
- package/templates/professional/research-assistant/CLAUDE.md +318 -0
- package/templates/professional/wellness-orchestrator/.cursor/rules/adaptive-planning.mdc +69 -0
- package/templates/professional/wellness-orchestrator/.cursor/rules/data-integration.mdc +60 -0
- package/templates/professional/wellness-orchestrator/.cursor/rules/fitness-programming.mdc +66 -0
- package/templates/professional/wellness-orchestrator/.cursor/rules/nutrition-planning.mdc +57 -0
- package/templates/professional/wellness-orchestrator/.cursor/rules/overview.mdc +76 -0
- package/templates/professional/wellness-orchestrator/.cursor/rules/sleep-optimization.mdc +68 -0
- 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
|
+
});
|