javi-forge 0.1.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/.gitignore.template +105 -0
- package/.releaserc +44 -0
- package/README.md +45 -0
- package/ai-config/.skillignore +15 -0
- package/ai-config/AUTO_INVOKE.md +300 -0
- package/ai-config/agents/_TEMPLATE.md +93 -0
- package/ai-config/agents/business/api-designer.md +1657 -0
- package/ai-config/agents/business/business-analyst.md +1331 -0
- package/ai-config/agents/business/product-strategist.md +206 -0
- package/ai-config/agents/business/project-manager.md +178 -0
- package/ai-config/agents/business/requirements-analyst.md +1277 -0
- package/ai-config/agents/business/technical-writer.md +1679 -0
- package/ai-config/agents/creative/ux-designer.md +205 -0
- package/ai-config/agents/data-ai/ai-engineer.md +487 -0
- package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
- package/ai-config/agents/data-ai/data-engineer.md +173 -0
- package/ai-config/agents/data-ai/data-scientist.md +672 -0
- package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
- package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
- package/ai-config/agents/development/angular-expert.md +620 -0
- package/ai-config/agents/development/backend-architect.md +795 -0
- package/ai-config/agents/development/database-specialist.md +212 -0
- package/ai-config/agents/development/frontend-specialist.md +686 -0
- package/ai-config/agents/development/fullstack-engineer.md +668 -0
- package/ai-config/agents/development/golang-pro.md +338 -0
- package/ai-config/agents/development/java-enterprise.md +400 -0
- package/ai-config/agents/development/javascript-pro.md +422 -0
- package/ai-config/agents/development/nextjs-pro.md +474 -0
- package/ai-config/agents/development/python-pro.md +570 -0
- package/ai-config/agents/development/react-pro.md +487 -0
- package/ai-config/agents/development/rust-pro.md +246 -0
- package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
- package/ai-config/agents/development/typescript-pro.md +336 -0
- package/ai-config/agents/development/vue-specialist.md +605 -0
- package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
- package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
- package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
- package/ai-config/agents/infrastructure/incident-responder.md +519 -0
- package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
- package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
- package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
- package/ai-config/agents/orchestrator.md +241 -0
- package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
- package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
- package/ai-config/agents/quality/code-reviewer.md +363 -0
- package/ai-config/agents/quality/dependency-manager.md +743 -0
- package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
- package/ai-config/agents/quality/performance-tester.md +1086 -0
- package/ai-config/agents/quality/security-auditor.md +133 -0
- package/ai-config/agents/quality/test-engineer.md +453 -0
- package/ai-config/agents/specialists/api-designer.md +87 -0
- package/ai-config/agents/specialists/backend-architect.md +73 -0
- package/ai-config/agents/specialists/code-reviewer.md +77 -0
- package/ai-config/agents/specialists/db-optimizer.md +75 -0
- package/ai-config/agents/specialists/devops-engineer.md +83 -0
- package/ai-config/agents/specialists/documentation-writer.md +78 -0
- package/ai-config/agents/specialists/frontend-developer.md +75 -0
- package/ai-config/agents/specialists/performance-analyst.md +82 -0
- package/ai-config/agents/specialists/refactor-specialist.md +74 -0
- package/ai-config/agents/specialists/security-auditor.md +74 -0
- package/ai-config/agents/specialists/test-engineer.md +81 -0
- package/ai-config/agents/specialists/ux-consultant.md +76 -0
- package/ai-config/agents/specialized/agent-generator.md +1190 -0
- package/ai-config/agents/specialized/blockchain-developer.md +149 -0
- package/ai-config/agents/specialized/code-migrator.md +892 -0
- package/ai-config/agents/specialized/context-manager.md +978 -0
- package/ai-config/agents/specialized/documentation-writer.md +1078 -0
- package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
- package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
- package/ai-config/agents/specialized/error-detective.md +1034 -0
- package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
- package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
- package/ai-config/agents/specialized/game-developer.md +1963 -0
- package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
- package/ai-config/agents/specialized/mobile-developer.md +188 -0
- package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
- package/ai-config/agents/specialized/plan-executor.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
- package/ai-config/agents/specialized/template-writer.md +347 -0
- package/ai-config/agents/specialized/test-runner.md +99 -0
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
- package/ai-config/agents/specialized/wave-executor.md +138 -0
- package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
- package/ai-config/commands/git/changelog.md +32 -0
- package/ai-config/commands/git/ci-local.md +70 -0
- package/ai-config/commands/git/commit.md +35 -0
- package/ai-config/commands/git/fix-issue.md +23 -0
- package/ai-config/commands/git/pr-create.md +42 -0
- package/ai-config/commands/git/pr-review.md +50 -0
- package/ai-config/commands/git/worktree.md +39 -0
- package/ai-config/commands/refactoring/cleanup.md +24 -0
- package/ai-config/commands/refactoring/dead-code.md +40 -0
- package/ai-config/commands/refactoring/extract.md +31 -0
- package/ai-config/commands/testing/e2e.md +30 -0
- package/ai-config/commands/testing/tdd.md +36 -0
- package/ai-config/commands/testing/test-coverage.md +30 -0
- package/ai-config/commands/testing/test-fix.md +24 -0
- package/ai-config/commands/workflow/generate-agents-md.md +85 -0
- package/ai-config/commands/workflow/planning.md +47 -0
- package/ai-config/commands/workflows/compound.md +89 -0
- package/ai-config/commands/workflows/plan.md +77 -0
- package/ai-config/commands/workflows/review.md +78 -0
- package/ai-config/commands/workflows/work.md +75 -0
- package/ai-config/config.yaml +18 -0
- package/ai-config/hooks/_TEMPLATE.md +96 -0
- package/ai-config/hooks/block-dangerous-commands.md +75 -0
- package/ai-config/hooks/commit-guard.md +90 -0
- package/ai-config/hooks/context-loader.md +73 -0
- package/ai-config/hooks/improve-prompt.md +91 -0
- package/ai-config/hooks/learning-log.md +72 -0
- package/ai-config/hooks/model-router.md +86 -0
- package/ai-config/hooks/secret-scanner.md +64 -0
- package/ai-config/hooks/skill-validator.md +102 -0
- package/ai-config/hooks/task-artifact.md +114 -0
- package/ai-config/hooks/validate-workflow.md +100 -0
- package/ai-config/prompts/base.md +71 -0
- package/ai-config/prompts/modes/debug.md +34 -0
- package/ai-config/prompts/modes/deploy.md +40 -0
- package/ai-config/prompts/modes/research.md +32 -0
- package/ai-config/prompts/modes/review.md +33 -0
- package/ai-config/prompts/review-policy.md +79 -0
- package/ai-config/skills/_TEMPLATE.md +157 -0
- package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
- package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
- package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
- package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
- package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
- package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
- package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
- package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
- package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
- package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
- package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
- package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
- package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
- package/ai-config/skills/backend/websockets/SKILL.md +532 -0
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
- package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
- package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
- package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
- package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
- package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
- package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
- package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
- package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
- package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
- package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
- package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
- package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
- package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
- package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
- package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
- package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
- package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
- package/ai-config/skills/prompt-improver/SKILL.md +125 -0
- package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
- package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
- package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
- package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
- package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
- package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
- package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
- package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
- package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
- package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
- package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
- package/ci-local/README.md +170 -0
- package/ci-local/ci-local.sh +297 -0
- package/ci-local/hooks/commit-msg +74 -0
- package/ci-local/hooks/pre-commit +162 -0
- package/ci-local/hooks/pre-push +41 -0
- package/ci-local/install.sh +49 -0
- package/ci-local/semgrep.yml +214 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +55 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyze.test.d.ts +2 -0
- package/dist/commands/analyze.test.d.ts.map +1 -0
- package/dist/commands/analyze.test.js +145 -0
- package/dist/commands/analyze.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +2 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +200 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +283 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +271 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +201 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
- package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
- package/dist/e2e/aggressive.e2e.test.js +350 -0
- package/dist/e2e/aggressive.e2e.test.js.map +1 -0
- package/dist/e2e/commands.e2e.test.d.ts +2 -0
- package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
- package/dist/e2e/commands.e2e.test.js +213 -0
- package/dist/e2e/commands.e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts +17 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js +111 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/common.test.d.ts +2 -0
- package/dist/lib/common.test.d.ts.map +1 -0
- package/dist/lib/common.test.js +316 -0
- package/dist/lib/common.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +61 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +257 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +78 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/template.test.d.ts +2 -0
- package/dist/lib/template.test.d.ts.map +1 -0
- package/dist/lib/template.test.js +201 -0
- package/dist/lib/template.test.js.map +1 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts +7 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js +100 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts +13 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +100 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CIContext.d.ts +9 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js +9 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts +8 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js +45 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts +3 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +89 -0
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts +8 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js +30 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts +8 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js +46 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts +8 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js +69 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts +12 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +69 -0
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Progress.d.ts +11 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js +58 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts +9 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js +65 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts +12 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js +114 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/SyncUI.d.ts +10 -0
- package/dist/ui/SyncUI.d.ts.map +1 -0
- package/dist/ui/SyncUI.js +64 -0
- package/dist/ui/SyncUI.js.map +1 -0
- package/dist/ui/Welcome.d.ts +7 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js +45 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts +10 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +9 -0
- package/dist/ui/theme.js.map +1 -0
- package/modules/engram/.gitignore-snippet.txt +6 -0
- package/modules/engram/.mcp-config-snippet.json +11 -0
- package/modules/engram/README.md +146 -0
- package/modules/engram/install-engram.sh +216 -0
- package/modules/ghagga/.env.example +43 -0
- package/modules/ghagga/README.md +153 -0
- package/modules/ghagga/docker-compose.yml +80 -0
- package/modules/ghagga/setup-ghagga.sh +139 -0
- package/modules/memory-simple/.project/NOTES.md +22 -0
- package/modules/memory-simple/README.md +23 -0
- package/modules/obsidian-brain/.obsidian/app.json +23 -0
- package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
- package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
- package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
- package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
- package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
- package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
- package/modules/obsidian-brain/.obsidian/graph.json +37 -0
- package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
- package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
- package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
- package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
- package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
- package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
- package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
- package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
- package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
- package/modules/obsidian-brain/.project/Memory/README.md +61 -0
- package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
- package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
- package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
- package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
- package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
- package/modules/obsidian-brain/README.md +268 -0
- package/modules/obsidian-brain/new-wave.sh +182 -0
- package/package.json +51 -0
- package/schemas/agent.schema.json +34 -0
- package/schemas/ai-config.schema.json +28 -0
- package/schemas/skill.schema.json +44 -0
- package/src/commands/analyze.test.ts +145 -0
- package/src/commands/analyze.ts +69 -0
- package/src/commands/doctor.test.ts +208 -0
- package/src/commands/doctor.ts +163 -0
- package/src/commands/init.test.ts +298 -0
- package/src/commands/init.ts +285 -0
- package/src/constants.ts +69 -0
- package/src/e2e/aggressive.e2e.test.ts +557 -0
- package/src/e2e/commands.e2e.test.ts +298 -0
- package/src/index.tsx +106 -0
- package/src/lib/common.test.ts +318 -0
- package/src/lib/common.ts +127 -0
- package/src/lib/frontmatter.test.ts +291 -0
- package/src/lib/frontmatter.ts +77 -0
- package/src/lib/template.test.ts +226 -0
- package/src/lib/template.ts +99 -0
- package/src/types/index.ts +53 -0
- package/src/ui/AnalyzeUI.tsx +133 -0
- package/src/ui/App.tsx +175 -0
- package/src/ui/CIContext.tsx +25 -0
- package/src/ui/CISelector.tsx +72 -0
- package/src/ui/Doctor.tsx +122 -0
- package/src/ui/Header.tsx +48 -0
- package/src/ui/MemorySelector.tsx +73 -0
- package/src/ui/NameInput.tsx +82 -0
- package/src/ui/OptionSelector.tsx +100 -0
- package/src/ui/Progress.tsx +88 -0
- package/src/ui/StackSelector.tsx +101 -0
- package/src/ui/Summary.tsx +134 -0
- package/src/ui/Welcome.tsx +54 -0
- package/src/ui/theme.ts +10 -0
- package/stryker.config.json +19 -0
- package/tasks/_TEMPLATE/files-edited.md +3 -0
- package/tasks/_TEMPLATE/plan.md +3 -0
- package/tasks/_TEMPLATE/research.md +3 -0
- package/tasks/_TEMPLATE/verification.md +5 -0
- package/templates/common/dependabot/cargo.yml +11 -0
- package/templates/common/dependabot/github-actions.yml +16 -0
- package/templates/common/dependabot/gomod.yml +15 -0
- package/templates/common/dependabot/gradle.yml +15 -0
- package/templates/common/dependabot/header.yml +3 -0
- package/templates/common/dependabot/maven.yml +15 -0
- package/templates/common/dependabot/npm.yml +20 -0
- package/templates/common/dependabot/pip.yml +11 -0
- package/templates/dependabot.yml +162 -0
- package/templates/github/ci-go.yml +41 -0
- package/templates/github/ci-java.yml +45 -0
- package/templates/github/ci-monorepo.yml +150 -0
- package/templates/github/ci-node.yml +42 -0
- package/templates/github/ci-python.yml +42 -0
- package/templates/github/ci-rust.yml +42 -0
- package/templates/github/dependabot-automerge.yml +40 -0
- package/templates/gitlab/gitlab-ci-go.yml +88 -0
- package/templates/gitlab/gitlab-ci-java.yml +79 -0
- package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
- package/templates/gitlab/gitlab-ci-node.yml +63 -0
- package/templates/gitlab/gitlab-ci-python.yml +147 -0
- package/templates/gitlab/gitlab-ci-rust.yml +67 -0
- package/templates/global/claude-settings.json +98 -0
- package/templates/global/codex-config.toml +8 -0
- package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
- package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
- package/templates/global/gemini-commands/cleanup.toml +20 -0
- package/templates/global/gemini-commands/commit.toml +15 -0
- package/templates/global/gemini-commands/dead-code.toml +22 -0
- package/templates/global/gemini-commands/plan.toml +30 -0
- package/templates/global/gemini-commands/review.toml +17 -0
- package/templates/global/gemini-commands/sdd-apply.toml +22 -0
- package/templates/global/gemini-commands/sdd-ff.toml +14 -0
- package/templates/global/gemini-commands/sdd-new.toml +21 -0
- package/templates/global/gemini-commands/sdd-verify.toml +21 -0
- package/templates/global/gemini-commands/tdd.toml +26 -0
- package/templates/global/gemini-settings.json +8 -0
- package/templates/global/opencode-config.json +44 -0
- package/templates/global/sdd-instructions.md +47 -0
- package/templates/global/sdd-orchestrator-claude.md +46 -0
- package/templates/global/sdd-orchestrator-copilot.md +34 -0
- package/templates/renovate.json +69 -0
- package/templates/woodpecker/monorepo/backend.yml +34 -0
- package/templates/woodpecker/monorepo/frontend.yml +34 -0
- package/templates/woodpecker/monorepo/summary.yml +25 -0
- package/templates/woodpecker/woodpecker-go.yml +51 -0
- package/templates/woodpecker/woodpecker-java.yml +67 -0
- package/templates/woodpecker/woodpecker-node.yml +47 -0
- package/templates/woodpecker/woodpecker-python.yml +108 -0
- package/templates/woodpecker/woodpecker-rust.yml +57 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
- package/workflows/reusable-build-go.yml +111 -0
- package/workflows/reusable-build-java.yml +120 -0
- package/workflows/reusable-build-node.yml +145 -0
- package/workflows/reusable-build-python.yml +159 -0
- package/workflows/reusable-build-rust.yml +135 -0
- package/workflows/reusable-docker.yml +120 -0
- package/workflows/reusable-ghagga-review.yml +165 -0
- package/workflows/reusable-release.yml +91 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
// ── Mock fs-extra ────────────────────────────────────────────────────────────
|
|
4
|
+
vi.mock('fs-extra', () => {
|
|
5
|
+
const mockFs = {
|
|
6
|
+
pathExists: vi.fn(),
|
|
7
|
+
readFile: vi.fn(),
|
|
8
|
+
readJson: vi.fn(),
|
|
9
|
+
readdir: vi.fn(),
|
|
10
|
+
copy: vi.fn(),
|
|
11
|
+
ensureDir: vi.fn(),
|
|
12
|
+
}
|
|
13
|
+
return { default: mockFs, ...mockFs }
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// ── Mock child_process ───────────────────────────────────────────────────────
|
|
17
|
+
vi.mock('child_process', () => ({
|
|
18
|
+
execFile: vi.fn((_cmd: string, _args: string[], cb: unknown) => {
|
|
19
|
+
if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
|
|
20
|
+
return undefined as any
|
|
21
|
+
}),
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
// ── Mock common module ───────────────────────────────────────────────────────
|
|
25
|
+
vi.mock('../lib/common.js', () => ({
|
|
26
|
+
detectStack: vi.fn(),
|
|
27
|
+
backupIfExists: vi.fn().mockResolvedValue(false),
|
|
28
|
+
ensureDirExists: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
STACK_LABELS: {
|
|
30
|
+
'node': 'Node.js / TypeScript',
|
|
31
|
+
'python': 'Python',
|
|
32
|
+
'go': 'Go',
|
|
33
|
+
'rust': 'Rust',
|
|
34
|
+
'java-gradle': 'Java (Gradle)',
|
|
35
|
+
'java-maven': 'Java (Maven)',
|
|
36
|
+
'elixir': 'Elixir',
|
|
37
|
+
},
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
import fs from 'fs-extra'
|
|
41
|
+
import { execFile } from 'child_process'
|
|
42
|
+
import { runDoctor } from './doctor.js'
|
|
43
|
+
import { detectStack } from '../lib/common.js'
|
|
44
|
+
|
|
45
|
+
const mockedFs = vi.mocked(fs)
|
|
46
|
+
const mockedExecFile = vi.mocked(execFile)
|
|
47
|
+
const mockedDetectStack = vi.mocked(detectStack)
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.resetAllMocks()
|
|
51
|
+
|
|
52
|
+
// Default: all paths exist, readdir returns items
|
|
53
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
54
|
+
mockedFs.readdir.mockResolvedValue(['file1', 'file2', '.hidden'] as never)
|
|
55
|
+
|
|
56
|
+
// Default: which + version succeed
|
|
57
|
+
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, cb: unknown) => {
|
|
58
|
+
const cmd = String(_cmd)
|
|
59
|
+
if (cmd === 'which') {
|
|
60
|
+
if (typeof cb === 'function') cb(null, { stdout: '/usr/bin/tool', stderr: '' })
|
|
61
|
+
} else {
|
|
62
|
+
if (typeof cb === 'function') cb(null, { stdout: 'v1.0.0', stderr: '' })
|
|
63
|
+
}
|
|
64
|
+
return undefined as any
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Default: no stack detected
|
|
68
|
+
mockedDetectStack.mockResolvedValue(null)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('runDoctor', () => {
|
|
72
|
+
it('reports all tools as ok when present', async () => {
|
|
73
|
+
const result = await runDoctor('/test/project')
|
|
74
|
+
const toolSection = result.sections.find(s => s.title === 'System Tools')
|
|
75
|
+
expect(toolSection).toBeDefined()
|
|
76
|
+
const allOk = toolSection!.checks.every(c => c.status === 'ok')
|
|
77
|
+
expect(allOk).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('reports fail for missing required tool', async () => {
|
|
81
|
+
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, cb: unknown) => {
|
|
82
|
+
const cmd = String(_cmd)
|
|
83
|
+
const args = _args as string[]
|
|
84
|
+
if (cmd === 'which' && args?.[0] === 'git') {
|
|
85
|
+
if (typeof cb === 'function') cb(new Error('not found'), { stdout: '', stderr: '' })
|
|
86
|
+
} else if (cmd === 'which') {
|
|
87
|
+
if (typeof cb === 'function') cb(null, { stdout: '/usr/bin/tool', stderr: '' })
|
|
88
|
+
} else {
|
|
89
|
+
if (typeof cb === 'function') cb(null, { stdout: 'v1.0.0', stderr: '' })
|
|
90
|
+
}
|
|
91
|
+
return undefined as any
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const result = await runDoctor('/test/project')
|
|
95
|
+
const toolSection = result.sections.find(s => s.title === 'System Tools')!
|
|
96
|
+
const gitCheck = toolSection.checks.find(c => c.label === 'Git')
|
|
97
|
+
expect(gitCheck!.status).toBe('fail')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('reports skip for missing optional tool (docker)', async () => {
|
|
101
|
+
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, cb: unknown) => {
|
|
102
|
+
const cmd = String(_cmd)
|
|
103
|
+
const args = _args as string[]
|
|
104
|
+
if (cmd === 'which' && args?.[0] === 'docker') {
|
|
105
|
+
if (typeof cb === 'function') cb(new Error('not found'), { stdout: '', stderr: '' })
|
|
106
|
+
} else if (cmd === 'which') {
|
|
107
|
+
if (typeof cb === 'function') cb(null, { stdout: '/usr/bin/tool', stderr: '' })
|
|
108
|
+
} else {
|
|
109
|
+
if (typeof cb === 'function') cb(null, { stdout: 'v1.0.0', stderr: '' })
|
|
110
|
+
}
|
|
111
|
+
return undefined as any
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const result = await runDoctor('/test/project')
|
|
115
|
+
const toolSection = result.sections.find(s => s.title === 'System Tools')!
|
|
116
|
+
const dockerCheck = toolSection.checks.find(c => c.label === 'Docker')
|
|
117
|
+
expect(dockerCheck!.status).toBe('skip')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('shows skip when no manifest found', async () => {
|
|
121
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
122
|
+
const s = String(p)
|
|
123
|
+
if (s.includes('manifest.json')) return false as never
|
|
124
|
+
return true as never
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const result = await runDoctor('/test/project')
|
|
128
|
+
const manifestSection = result.sections.find(s => s.title === 'Project Manifest')!
|
|
129
|
+
const manifestCheck = manifestSection.checks.find(c => c.label === 'Forge manifest')
|
|
130
|
+
expect(manifestCheck!.status).toBe('skip')
|
|
131
|
+
expect(manifestCheck!.detail).toContain('not a forge-managed project')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('shows manifest details when found', async () => {
|
|
135
|
+
mockedFs.readJson.mockResolvedValue({
|
|
136
|
+
version: '0.1.0',
|
|
137
|
+
projectName: 'test-project',
|
|
138
|
+
stack: 'node',
|
|
139
|
+
ciProvider: 'github',
|
|
140
|
+
memory: 'engram',
|
|
141
|
+
createdAt: '2025-01-15T10:00:00Z',
|
|
142
|
+
updatedAt: '2025-01-15T10:00:00Z',
|
|
143
|
+
modules: ['engram', 'ghagga'],
|
|
144
|
+
} as never)
|
|
145
|
+
|
|
146
|
+
const result = await runDoctor('/test/project')
|
|
147
|
+
const manifestSection = result.sections.find(s => s.title === 'Project Manifest')!
|
|
148
|
+
const manifestCheck = manifestSection.checks.find(c => c.label === 'Forge manifest')
|
|
149
|
+
expect(manifestCheck!.status).toBe('ok')
|
|
150
|
+
expect(manifestCheck!.detail).toContain('test-project')
|
|
151
|
+
|
|
152
|
+
const modulesCheck = manifestSection.checks.find(c => c.label === 'Modules')
|
|
153
|
+
expect(modulesCheck!.status).toBe('ok')
|
|
154
|
+
expect(modulesCheck!.detail).toContain('engram')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('reports ok for existing framework dirs', async () => {
|
|
158
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
159
|
+
mockedFs.readdir.mockResolvedValue(['a', 'b', '.dotfile'] as never)
|
|
160
|
+
|
|
161
|
+
const result = await runDoctor('/test/project')
|
|
162
|
+
const structSection = result.sections.find(s => s.title === 'Framework Structure')!
|
|
163
|
+
expect(structSection.checks.every(c => c.status === 'ok')).toBe(true)
|
|
164
|
+
// countDir should filter dotfiles → "2 entries"
|
|
165
|
+
expect(structSection.checks[0].detail).toBe('2 entries')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('reports fail for missing framework dirs', async () => {
|
|
169
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
170
|
+
const s = String(p)
|
|
171
|
+
if (s.includes('templates')) return false as never
|
|
172
|
+
if (s.includes('manifest.json')) return false as never
|
|
173
|
+
// Modules directory for installed modules
|
|
174
|
+
if (s.includes('.javi-forge')) return false as never
|
|
175
|
+
return true as never
|
|
176
|
+
})
|
|
177
|
+
mockedFs.readdir.mockResolvedValue(['a'] as never)
|
|
178
|
+
|
|
179
|
+
const result = await runDoctor('/test/project')
|
|
180
|
+
const structSection = result.sections.find(s => s.title === 'Framework Structure')!
|
|
181
|
+
const templatesCheck = structSection.checks.find(c => c.label === 'templates/')
|
|
182
|
+
expect(templatesCheck!.status).toBe('fail')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('shows stack when detected', async () => {
|
|
186
|
+
mockedDetectStack.mockResolvedValue({
|
|
187
|
+
stackType: 'node',
|
|
188
|
+
buildTool: 'pnpm',
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const result = await runDoctor('/test/project')
|
|
192
|
+
const stackSection = result.sections.find(s => s.title === 'Stack Detection')!
|
|
193
|
+
const stackCheck = stackSection.checks[0]
|
|
194
|
+
expect(stackCheck.status).toBe('ok')
|
|
195
|
+
expect(stackCheck.detail).toContain('node')
|
|
196
|
+
expect(stackCheck.detail).toContain('pnpm')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('countDir filters dotfiles', async () => {
|
|
200
|
+
mockedFs.readdir.mockResolvedValue(['.hidden', 'file1', '.git', 'file2'] as never)
|
|
201
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
202
|
+
|
|
203
|
+
const result = await runDoctor('/test/project')
|
|
204
|
+
const structSection = result.sections.find(s => s.title === 'Framework Structure')!
|
|
205
|
+
// Filtered: file1, file2 → 2 entries
|
|
206
|
+
expect(structSection.checks[0].detail).toBe('2 entries')
|
|
207
|
+
})
|
|
208
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import fs from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { execFile } from 'child_process'
|
|
4
|
+
import { promisify } from 'util'
|
|
5
|
+
import { detectStack } from '../lib/common.js'
|
|
6
|
+
import { FORGE_ROOT, TEMPLATES_DIR, MODULES_DIR, AI_CONFIG_DIR } from '../constants.js'
|
|
7
|
+
import type { DoctorResult, DoctorSection, DoctorCheck, ForgeManifest } from '../types/index.js'
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile)
|
|
10
|
+
|
|
11
|
+
export type CheckStatus = 'ok' | 'fail' | 'skip'
|
|
12
|
+
|
|
13
|
+
/** Resolve a binary name to its full path, returns null if not found */
|
|
14
|
+
async function which(bin: string): Promise<string | null> {
|
|
15
|
+
try {
|
|
16
|
+
const { stdout } = await execFileAsync('which', [bin])
|
|
17
|
+
return stdout.trim() || null
|
|
18
|
+
} catch {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Read the forge manifest from a project directory */
|
|
24
|
+
async function readManifest(projectDir: string): Promise<ForgeManifest | null> {
|
|
25
|
+
const manifestPath = path.join(projectDir, '.javi-forge', 'manifest.json')
|
|
26
|
+
if (!await fs.pathExists(manifestPath)) return null
|
|
27
|
+
try {
|
|
28
|
+
return await fs.readJson(manifestPath) as ForgeManifest
|
|
29
|
+
} catch {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Count entries in a directory */
|
|
35
|
+
async function countDir(dir: string): Promise<number> {
|
|
36
|
+
if (!await fs.pathExists(dir)) return 0
|
|
37
|
+
const entries = await fs.readdir(dir)
|
|
38
|
+
return entries.filter(e => !e.startsWith('.')).length
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run comprehensive health checks for the project and framework.
|
|
43
|
+
*/
|
|
44
|
+
export async function runDoctor(projectDir?: string): Promise<DoctorResult> {
|
|
45
|
+
const cwd = projectDir ?? process.cwd()
|
|
46
|
+
const sections: DoctorSection[] = []
|
|
47
|
+
|
|
48
|
+
// ── 1. System Tools ────────────────────────────────────────────────────────
|
|
49
|
+
const toolChecks: DoctorCheck[] = []
|
|
50
|
+
const tools = [
|
|
51
|
+
{ name: 'git', label: 'Git' },
|
|
52
|
+
{ name: 'docker', label: 'Docker' },
|
|
53
|
+
{ name: 'semgrep', label: 'Semgrep' },
|
|
54
|
+
{ name: 'node', label: 'Node.js' },
|
|
55
|
+
{ name: 'pnpm', label: 'pnpm' },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
for (const tool of tools) {
|
|
59
|
+
const bin = await which(tool.name)
|
|
60
|
+
if (bin) {
|
|
61
|
+
// Try to get version
|
|
62
|
+
let version = ''
|
|
63
|
+
try {
|
|
64
|
+
const { stdout } = await execFileAsync(tool.name, ['--version'])
|
|
65
|
+
version = stdout.trim().split('\n')[0] ?? ''
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
toolChecks.push({
|
|
68
|
+
label: tool.label,
|
|
69
|
+
status: 'ok',
|
|
70
|
+
detail: version ? `${version}` : `found at ${bin}`,
|
|
71
|
+
})
|
|
72
|
+
} else {
|
|
73
|
+
toolChecks.push({
|
|
74
|
+
label: tool.label,
|
|
75
|
+
status: tool.name === 'docker' || tool.name === 'semgrep' ? 'skip' : 'fail',
|
|
76
|
+
detail: 'not found in PATH',
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
sections.push({ title: 'System Tools', checks: toolChecks })
|
|
81
|
+
|
|
82
|
+
// ── 2. Framework Structure ─────────────────────────────────────────────────
|
|
83
|
+
const structureChecks: DoctorCheck[] = []
|
|
84
|
+
const expectedDirs = [
|
|
85
|
+
{ path: TEMPLATES_DIR, label: 'templates/' },
|
|
86
|
+
{ path: MODULES_DIR, label: 'modules/' },
|
|
87
|
+
{ path: AI_CONFIG_DIR, label: 'ai-config/' },
|
|
88
|
+
{ path: path.join(FORGE_ROOT, 'workflows'), label: 'workflows/' },
|
|
89
|
+
{ path: path.join(FORGE_ROOT, 'schemas'), label: 'schemas/' },
|
|
90
|
+
{ path: path.join(FORGE_ROOT, 'ci-local'), label: 'ci-local/' },
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
for (const dir of expectedDirs) {
|
|
94
|
+
if (await fs.pathExists(dir.path)) {
|
|
95
|
+
const count = await countDir(dir.path)
|
|
96
|
+
structureChecks.push({ label: dir.label, status: 'ok', detail: `${count} entries` })
|
|
97
|
+
} else {
|
|
98
|
+
structureChecks.push({ label: dir.label, status: 'fail', detail: 'missing' })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
sections.push({ title: 'Framework Structure', checks: structureChecks })
|
|
102
|
+
|
|
103
|
+
// ── 3. Stack Detection ─────────────────────────────────────────────────────
|
|
104
|
+
const stackChecks: DoctorCheck[] = []
|
|
105
|
+
const detection = await detectStack(cwd)
|
|
106
|
+
if (detection) {
|
|
107
|
+
stackChecks.push({
|
|
108
|
+
label: 'Detected stack',
|
|
109
|
+
status: 'ok',
|
|
110
|
+
detail: `${detection.stackType} (${detection.buildTool})${detection.javaVersion ? ` Java ${detection.javaVersion}` : ''}`,
|
|
111
|
+
})
|
|
112
|
+
} else {
|
|
113
|
+
stackChecks.push({
|
|
114
|
+
label: 'Detected stack',
|
|
115
|
+
status: 'skip',
|
|
116
|
+
detail: 'no recognizable project files in current directory',
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
sections.push({ title: 'Stack Detection', checks: stackChecks })
|
|
120
|
+
|
|
121
|
+
// ── 4. Project Manifest ────────────────────────────────────────────────────
|
|
122
|
+
const manifestChecks: DoctorCheck[] = []
|
|
123
|
+
const manifest = await readManifest(cwd)
|
|
124
|
+
if (manifest) {
|
|
125
|
+
manifestChecks.push({
|
|
126
|
+
label: 'Forge manifest',
|
|
127
|
+
status: 'ok',
|
|
128
|
+
detail: `project: ${manifest.projectName}, stack: ${manifest.stack}`,
|
|
129
|
+
})
|
|
130
|
+
manifestChecks.push({
|
|
131
|
+
label: 'Created',
|
|
132
|
+
status: 'ok',
|
|
133
|
+
detail: manifest.createdAt.split('T')[0],
|
|
134
|
+
})
|
|
135
|
+
manifestChecks.push({
|
|
136
|
+
label: 'Modules',
|
|
137
|
+
status: manifest.modules.length > 0 ? 'ok' : 'skip',
|
|
138
|
+
detail: manifest.modules.length > 0 ? manifest.modules.join(', ') : 'none installed',
|
|
139
|
+
})
|
|
140
|
+
} else {
|
|
141
|
+
manifestChecks.push({
|
|
142
|
+
label: 'Forge manifest',
|
|
143
|
+
status: 'skip',
|
|
144
|
+
detail: 'not a forge-managed project (run javi-forge init)',
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
sections.push({ title: 'Project Manifest', checks: manifestChecks })
|
|
148
|
+
|
|
149
|
+
// ── 5. Installed Modules ───────────────────────────────────────────────────
|
|
150
|
+
const moduleChecks: DoctorCheck[] = []
|
|
151
|
+
const moduleNames = ['engram', 'obsidian-brain', 'memory-simple', 'ghagga']
|
|
152
|
+
for (const mod of moduleNames) {
|
|
153
|
+
const modPath = path.join(cwd, '.javi-forge', 'modules', mod)
|
|
154
|
+
if (await fs.pathExists(modPath)) {
|
|
155
|
+
moduleChecks.push({ label: mod, status: 'ok', detail: 'installed' })
|
|
156
|
+
} else {
|
|
157
|
+
moduleChecks.push({ label: mod, status: 'skip', detail: 'not installed' })
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
sections.push({ title: 'Installed Modules', checks: moduleChecks })
|
|
161
|
+
|
|
162
|
+
return { sections }
|
|
163
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import type { InitOptions, InitStep } from '../types/index.js'
|
|
3
|
+
|
|
4
|
+
// ── Mock fs-extra ────────────────────────────────────────────────────────────
|
|
5
|
+
vi.mock('fs-extra', () => {
|
|
6
|
+
const mockFs = {
|
|
7
|
+
pathExists: vi.fn(),
|
|
8
|
+
readFile: vi.fn(),
|
|
9
|
+
readJson: vi.fn(),
|
|
10
|
+
writeFile: vi.fn(),
|
|
11
|
+
writeJson: vi.fn(),
|
|
12
|
+
copy: vi.fn(),
|
|
13
|
+
ensureDir: vi.fn(),
|
|
14
|
+
}
|
|
15
|
+
return { default: mockFs, ...mockFs }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// ── Mock child_process ───────────────────────────────────────────────────────
|
|
19
|
+
vi.mock('child_process', () => ({
|
|
20
|
+
execFile: vi.fn((_cmd: string, _args: string[], _opts: unknown, cb: Function) => {
|
|
21
|
+
cb(null, { stdout: '', stderr: '' })
|
|
22
|
+
}),
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
// ── Mock template module ─────────────────────────────────────────────────────
|
|
26
|
+
vi.mock('../lib/template.js', () => ({
|
|
27
|
+
generateDependabotYml: vi.fn().mockResolvedValue('dependabot-content'),
|
|
28
|
+
generateCIWorkflow: vi.fn().mockResolvedValue('ci-workflow-content'),
|
|
29
|
+
getCIDestination: vi.fn().mockReturnValue('.github/workflows/ci.yml'),
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
// ── Mock common module ───────────────────────────────────────────────────────
|
|
33
|
+
vi.mock('../lib/common.js', () => ({
|
|
34
|
+
backupIfExists: vi.fn().mockResolvedValue(false),
|
|
35
|
+
ensureDirExists: vi.fn().mockResolvedValue(undefined),
|
|
36
|
+
}))
|
|
37
|
+
|
|
38
|
+
import fs from 'fs-extra'
|
|
39
|
+
import { execFile } from 'child_process'
|
|
40
|
+
import { initProject } from './init.js'
|
|
41
|
+
import { generateCIWorkflow, getCIDestination } from '../lib/template.js'
|
|
42
|
+
|
|
43
|
+
const mockedFs = vi.mocked(fs)
|
|
44
|
+
const mockedExecFile = vi.mocked(execFile)
|
|
45
|
+
const mockedGenerateCIWorkflow = vi.mocked(generateCIWorkflow)
|
|
46
|
+
const mockedGetCIDestination = vi.mocked(getCIDestination)
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.resetAllMocks()
|
|
50
|
+
|
|
51
|
+
// Default: most things exist
|
|
52
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
53
|
+
mockedFs.writeFile.mockResolvedValue(undefined as never)
|
|
54
|
+
mockedFs.writeJson.mockResolvedValue(undefined as never)
|
|
55
|
+
mockedFs.copy.mockResolvedValue(undefined as never)
|
|
56
|
+
mockedFs.ensureDir.mockResolvedValue(undefined as never)
|
|
57
|
+
|
|
58
|
+
// Default: execFile succeeds (promisified version)
|
|
59
|
+
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, _opts: unknown, cb: unknown) => {
|
|
60
|
+
if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
|
|
61
|
+
return undefined as any
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Default: CI workflow available
|
|
65
|
+
mockedGenerateCIWorkflow.mockResolvedValue('ci-workflow-content')
|
|
66
|
+
mockedGetCIDestination.mockReturnValue('.github/workflows/ci.yml')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
function makeOptions(overrides: Partial<InitOptions> = {}): InitOptions {
|
|
70
|
+
return {
|
|
71
|
+
projectName: 'test-project',
|
|
72
|
+
projectDir: '/test/project',
|
|
73
|
+
stack: 'node',
|
|
74
|
+
ciProvider: 'github',
|
|
75
|
+
memory: 'engram',
|
|
76
|
+
aiSync: true,
|
|
77
|
+
sdd: true,
|
|
78
|
+
ghagga: true,
|
|
79
|
+
dryRun: false,
|
|
80
|
+
...overrides,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function collectSteps(options: InitOptions): Promise<InitStep[]> {
|
|
85
|
+
const steps: InitStep[] = []
|
|
86
|
+
return initProject(options, (step) => steps.push(step)).then(() => steps)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
90
|
+
// initProject
|
|
91
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
92
|
+
describe('initProject', () => {
|
|
93
|
+
it('completes full happy path — all steps report done', async () => {
|
|
94
|
+
// .git doesn't exist yet so it initializes
|
|
95
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
96
|
+
const s = String(p)
|
|
97
|
+
if (s.endsWith('.git')) return false as never
|
|
98
|
+
return true as never
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const steps = await collectSteps(makeOptions())
|
|
102
|
+
const doneSteps = steps.filter(s => s.status === 'done')
|
|
103
|
+
// Should have multiple 'done' status steps
|
|
104
|
+
expect(doneSteps.length).toBeGreaterThanOrEqual(8)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('dry-run: no filesystem writes are made', async () => {
|
|
108
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
109
|
+
const s = String(p)
|
|
110
|
+
if (s.endsWith('.git')) return false as never
|
|
111
|
+
return true as never
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const steps = await collectSteps(makeOptions({ dryRun: true }))
|
|
115
|
+
// In dry-run, fs.writeFile and fs.writeJson should not be called
|
|
116
|
+
expect(mockedFs.writeFile).not.toHaveBeenCalled()
|
|
117
|
+
expect(mockedFs.writeJson).not.toHaveBeenCalled()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('continues other steps when one step errors', async () => {
|
|
121
|
+
// Make CI generation throw
|
|
122
|
+
mockedGenerateCIWorkflow.mockRejectedValue(new Error('CI template error'))
|
|
123
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
124
|
+
const s = String(p)
|
|
125
|
+
if (s.endsWith('.git')) return false as never
|
|
126
|
+
return true as never
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const steps = await collectSteps(makeOptions())
|
|
130
|
+
// Should have both error and done steps
|
|
131
|
+
const errorSteps = steps.filter(s => s.status === 'error')
|
|
132
|
+
const doneSteps = steps.filter(s => s.status === 'done')
|
|
133
|
+
expect(errorSteps.length).toBeGreaterThanOrEqual(1)
|
|
134
|
+
expect(doneSteps.length).toBeGreaterThanOrEqual(5)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('skips memory when memory is none', async () => {
|
|
138
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
139
|
+
const steps = await collectSteps(makeOptions({ memory: 'none' }))
|
|
140
|
+
const memStep = steps.find(s => s.id === 'memory' && s.status === 'skipped')
|
|
141
|
+
expect(memStep).toBeDefined()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('skips ghagga when ghagga is false', async () => {
|
|
145
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
146
|
+
const steps = await collectSteps(makeOptions({ ghagga: false }))
|
|
147
|
+
const ghStep = steps.find(s => s.id === 'ghagga' && s.status === 'skipped')
|
|
148
|
+
expect(ghStep).toBeDefined()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('skips SDD when sdd is false', async () => {
|
|
152
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
153
|
+
const steps = await collectSteps(makeOptions({ sdd: false }))
|
|
154
|
+
const sddStep = steps.find(s => s.id === 'sdd' && s.status === 'skipped')
|
|
155
|
+
expect(sddStep).toBeDefined()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('skips AI sync when aiSync is false', async () => {
|
|
159
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
160
|
+
const steps = await collectSteps(makeOptions({ aiSync: false }))
|
|
161
|
+
const aiStep = steps.find(s => s.id === 'ai-sync' && s.status === 'skipped')
|
|
162
|
+
expect(aiStep).toBeDefined()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('reports already exists when .git directory is present', async () => {
|
|
166
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
167
|
+
const steps = await collectSteps(makeOptions())
|
|
168
|
+
const gitStep = steps.find(s => s.id === 'git-init' && s.status === 'done' && s.detail === 'already exists')
|
|
169
|
+
expect(gitStep).toBeDefined()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('skips CI step when no template found', async () => {
|
|
173
|
+
mockedGenerateCIWorkflow.mockResolvedValue(null)
|
|
174
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
175
|
+
|
|
176
|
+
const steps = await collectSteps(makeOptions())
|
|
177
|
+
const ciStep = steps.find(s => s.id === 'ci-template' && s.status === 'skipped')
|
|
178
|
+
expect(ciStep).toBeDefined()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('writes manifest with correct structure', async () => {
|
|
182
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
183
|
+
const s = String(p)
|
|
184
|
+
if (s.endsWith('.git')) return false as never
|
|
185
|
+
return true as never
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
await collectSteps(makeOptions({
|
|
189
|
+
projectName: 'test-manifest',
|
|
190
|
+
stack: 'node',
|
|
191
|
+
ciProvider: 'github',
|
|
192
|
+
memory: 'engram',
|
|
193
|
+
ghagga: true,
|
|
194
|
+
sdd: true,
|
|
195
|
+
aiSync: true,
|
|
196
|
+
}))
|
|
197
|
+
|
|
198
|
+
expect(mockedFs.writeJson).toHaveBeenCalled()
|
|
199
|
+
const [manifestPath, manifestData] = mockedFs.writeJson.mock.calls[0]
|
|
200
|
+
expect(String(manifestPath)).toContain('manifest.json')
|
|
201
|
+
expect(manifestData).toMatchObject({
|
|
202
|
+
version: '0.1.0',
|
|
203
|
+
projectName: 'test-manifest',
|
|
204
|
+
stack: 'node',
|
|
205
|
+
ciProvider: 'github',
|
|
206
|
+
memory: 'engram',
|
|
207
|
+
})
|
|
208
|
+
expect((manifestData as any).modules).toContain('engram')
|
|
209
|
+
expect((manifestData as any).modules).toContain('ghagga')
|
|
210
|
+
expect((manifestData as any).modules).toContain('sdd')
|
|
211
|
+
expect((manifestData as any).modules).toContain('ai-config')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('reports error with helpful message when javi-ai not found', async () => {
|
|
215
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
216
|
+
const s = String(p)
|
|
217
|
+
if (s.endsWith('.git')) return true as never
|
|
218
|
+
return true as never
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Make javi-ai sync fail with ENOENT
|
|
222
|
+
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, _opts: unknown, cb: unknown) => {
|
|
223
|
+
const cmdStr = String(_cmd)
|
|
224
|
+
const argsArr = _args as string[]
|
|
225
|
+
if (cmdStr === 'npx' && argsArr?.includes('javi-ai')) {
|
|
226
|
+
if (typeof cb === 'function') cb(new Error('ENOENT: command not found'), { stdout: '', stderr: '' })
|
|
227
|
+
} else {
|
|
228
|
+
if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
|
|
229
|
+
}
|
|
230
|
+
return undefined as any
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const steps = await collectSteps(makeOptions({ aiSync: true }))
|
|
234
|
+
const aiStep = steps.find(s => s.id === 'ai-sync' && s.status === 'error')
|
|
235
|
+
expect(aiStep).toBeDefined()
|
|
236
|
+
expect(aiStep!.detail).toContain('javi-ai not found')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('reports steps in order via callback', async () => {
|
|
240
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
241
|
+
const s = String(p)
|
|
242
|
+
if (s.endsWith('.git')) return false as never
|
|
243
|
+
return true as never
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const steps = await collectSteps(makeOptions())
|
|
247
|
+
const stepIds = steps.map(s => s.id)
|
|
248
|
+
|
|
249
|
+
// First step should be git-init
|
|
250
|
+
expect(stepIds[0]).toBe('git-init')
|
|
251
|
+
|
|
252
|
+
// Manifest should be among the last
|
|
253
|
+
const manifestIdx = stepIds.lastIndexOf('manifest')
|
|
254
|
+
expect(manifestIdx).toBeGreaterThan(stepIds.indexOf('git-init'))
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('skips dependabot for non-github providers', async () => {
|
|
258
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
259
|
+
const steps = await collectSteps(makeOptions({ ciProvider: 'gitlab' }))
|
|
260
|
+
const depStep = steps.find(s => s.id === 'dependabot' && s.status === 'skipped')
|
|
261
|
+
expect(depStep).toBeDefined()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('skips gitignore when .gitignore already exists', async () => {
|
|
265
|
+
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
266
|
+
const steps = await collectSteps(makeOptions())
|
|
267
|
+
const giStep = steps.find(s => s.id === 'gitignore' && s.detail === 'already exists')
|
|
268
|
+
expect(giStep).toBeDefined()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('skips hooks when ci-local dir is missing', async () => {
|
|
272
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
273
|
+
const s = String(p)
|
|
274
|
+
if (s.includes('ci-local')) return false as never
|
|
275
|
+
if (s.endsWith('.git')) return false as never
|
|
276
|
+
return true as never
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const steps = await collectSteps(makeOptions())
|
|
280
|
+
const hookStep = steps.find(s => s.id === 'git-hooks' && s.status === 'skipped')
|
|
281
|
+
expect(hookStep).toBeDefined()
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('reports error when memory module not found', async () => {
|
|
285
|
+
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
286
|
+
const s = String(p)
|
|
287
|
+
// Module source directory doesn't exist
|
|
288
|
+
if (s.includes('modules/engram') && !s.includes('.javi-forge')) return false as never
|
|
289
|
+
if (s.endsWith('.git')) return false as never
|
|
290
|
+
return true as never
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const steps = await collectSteps(makeOptions({ memory: 'engram' }))
|
|
294
|
+
const memStep = steps.find(s => s.id === 'memory' && s.status === 'error')
|
|
295
|
+
expect(memStep).toBeDefined()
|
|
296
|
+
expect(memStep!.detail).toContain('module not found')
|
|
297
|
+
})
|
|
298
|
+
})
|