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.
Files changed (500) hide show
  1. package/.gitignore.template +105 -0
  2. package/.releaserc +44 -0
  3. package/README.md +45 -0
  4. package/ai-config/.skillignore +15 -0
  5. package/ai-config/AUTO_INVOKE.md +300 -0
  6. package/ai-config/agents/_TEMPLATE.md +93 -0
  7. package/ai-config/agents/business/api-designer.md +1657 -0
  8. package/ai-config/agents/business/business-analyst.md +1331 -0
  9. package/ai-config/agents/business/product-strategist.md +206 -0
  10. package/ai-config/agents/business/project-manager.md +178 -0
  11. package/ai-config/agents/business/requirements-analyst.md +1277 -0
  12. package/ai-config/agents/business/technical-writer.md +1679 -0
  13. package/ai-config/agents/creative/ux-designer.md +205 -0
  14. package/ai-config/agents/data-ai/ai-engineer.md +487 -0
  15. package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
  16. package/ai-config/agents/data-ai/data-engineer.md +173 -0
  17. package/ai-config/agents/data-ai/data-scientist.md +672 -0
  18. package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
  19. package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
  20. package/ai-config/agents/development/angular-expert.md +620 -0
  21. package/ai-config/agents/development/backend-architect.md +795 -0
  22. package/ai-config/agents/development/database-specialist.md +212 -0
  23. package/ai-config/agents/development/frontend-specialist.md +686 -0
  24. package/ai-config/agents/development/fullstack-engineer.md +668 -0
  25. package/ai-config/agents/development/golang-pro.md +338 -0
  26. package/ai-config/agents/development/java-enterprise.md +400 -0
  27. package/ai-config/agents/development/javascript-pro.md +422 -0
  28. package/ai-config/agents/development/nextjs-pro.md +474 -0
  29. package/ai-config/agents/development/python-pro.md +570 -0
  30. package/ai-config/agents/development/react-pro.md +487 -0
  31. package/ai-config/agents/development/rust-pro.md +246 -0
  32. package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
  33. package/ai-config/agents/development/typescript-pro.md +336 -0
  34. package/ai-config/agents/development/vue-specialist.md +605 -0
  35. package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
  36. package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
  37. package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
  38. package/ai-config/agents/infrastructure/incident-responder.md +519 -0
  39. package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
  40. package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
  41. package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
  42. package/ai-config/agents/orchestrator.md +241 -0
  43. package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
  44. package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
  45. package/ai-config/agents/quality/code-reviewer.md +363 -0
  46. package/ai-config/agents/quality/dependency-manager.md +743 -0
  47. package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
  48. package/ai-config/agents/quality/performance-tester.md +1086 -0
  49. package/ai-config/agents/quality/security-auditor.md +133 -0
  50. package/ai-config/agents/quality/test-engineer.md +453 -0
  51. package/ai-config/agents/specialists/api-designer.md +87 -0
  52. package/ai-config/agents/specialists/backend-architect.md +73 -0
  53. package/ai-config/agents/specialists/code-reviewer.md +77 -0
  54. package/ai-config/agents/specialists/db-optimizer.md +75 -0
  55. package/ai-config/agents/specialists/devops-engineer.md +83 -0
  56. package/ai-config/agents/specialists/documentation-writer.md +78 -0
  57. package/ai-config/agents/specialists/frontend-developer.md +75 -0
  58. package/ai-config/agents/specialists/performance-analyst.md +82 -0
  59. package/ai-config/agents/specialists/refactor-specialist.md +74 -0
  60. package/ai-config/agents/specialists/security-auditor.md +74 -0
  61. package/ai-config/agents/specialists/test-engineer.md +81 -0
  62. package/ai-config/agents/specialists/ux-consultant.md +76 -0
  63. package/ai-config/agents/specialized/agent-generator.md +1190 -0
  64. package/ai-config/agents/specialized/blockchain-developer.md +149 -0
  65. package/ai-config/agents/specialized/code-migrator.md +892 -0
  66. package/ai-config/agents/specialized/context-manager.md +978 -0
  67. package/ai-config/agents/specialized/documentation-writer.md +1078 -0
  68. package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
  69. package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
  70. package/ai-config/agents/specialized/error-detective.md +1034 -0
  71. package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
  72. package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
  73. package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
  74. package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
  75. package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
  76. package/ai-config/agents/specialized/game-developer.md +1963 -0
  77. package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
  78. package/ai-config/agents/specialized/mobile-developer.md +188 -0
  79. package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
  80. package/ai-config/agents/specialized/plan-executor.md +485 -0
  81. package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
  82. package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
  83. package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
  84. package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
  85. package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
  86. package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
  87. package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
  88. package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
  89. package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
  90. package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
  91. package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
  92. package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
  93. package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
  94. package/ai-config/agents/specialized/template-writer.md +347 -0
  95. package/ai-config/agents/specialized/test-runner.md +99 -0
  96. package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
  97. package/ai-config/agents/specialized/wave-executor.md +138 -0
  98. package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
  99. package/ai-config/commands/git/changelog.md +32 -0
  100. package/ai-config/commands/git/ci-local.md +70 -0
  101. package/ai-config/commands/git/commit.md +35 -0
  102. package/ai-config/commands/git/fix-issue.md +23 -0
  103. package/ai-config/commands/git/pr-create.md +42 -0
  104. package/ai-config/commands/git/pr-review.md +50 -0
  105. package/ai-config/commands/git/worktree.md +39 -0
  106. package/ai-config/commands/refactoring/cleanup.md +24 -0
  107. package/ai-config/commands/refactoring/dead-code.md +40 -0
  108. package/ai-config/commands/refactoring/extract.md +31 -0
  109. package/ai-config/commands/testing/e2e.md +30 -0
  110. package/ai-config/commands/testing/tdd.md +36 -0
  111. package/ai-config/commands/testing/test-coverage.md +30 -0
  112. package/ai-config/commands/testing/test-fix.md +24 -0
  113. package/ai-config/commands/workflow/generate-agents-md.md +85 -0
  114. package/ai-config/commands/workflow/planning.md +47 -0
  115. package/ai-config/commands/workflows/compound.md +89 -0
  116. package/ai-config/commands/workflows/plan.md +77 -0
  117. package/ai-config/commands/workflows/review.md +78 -0
  118. package/ai-config/commands/workflows/work.md +75 -0
  119. package/ai-config/config.yaml +18 -0
  120. package/ai-config/hooks/_TEMPLATE.md +96 -0
  121. package/ai-config/hooks/block-dangerous-commands.md +75 -0
  122. package/ai-config/hooks/commit-guard.md +90 -0
  123. package/ai-config/hooks/context-loader.md +73 -0
  124. package/ai-config/hooks/improve-prompt.md +91 -0
  125. package/ai-config/hooks/learning-log.md +72 -0
  126. package/ai-config/hooks/model-router.md +86 -0
  127. package/ai-config/hooks/secret-scanner.md +64 -0
  128. package/ai-config/hooks/skill-validator.md +102 -0
  129. package/ai-config/hooks/task-artifact.md +114 -0
  130. package/ai-config/hooks/validate-workflow.md +100 -0
  131. package/ai-config/prompts/base.md +71 -0
  132. package/ai-config/prompts/modes/debug.md +34 -0
  133. package/ai-config/prompts/modes/deploy.md +40 -0
  134. package/ai-config/prompts/modes/research.md +32 -0
  135. package/ai-config/prompts/modes/review.md +33 -0
  136. package/ai-config/prompts/review-policy.md +79 -0
  137. package/ai-config/skills/_TEMPLATE.md +157 -0
  138. package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
  139. package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
  140. package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
  141. package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
  142. package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
  143. package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
  144. package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
  145. package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
  146. package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
  147. package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
  148. package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
  149. package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
  150. package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
  151. package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
  152. package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
  153. package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
  154. package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
  155. package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
  156. package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
  157. package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
  158. package/ai-config/skills/backend/websockets/SKILL.md +532 -0
  159. package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
  160. package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
  161. package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
  162. package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
  163. package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
  164. package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
  165. package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
  166. package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
  167. package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
  168. package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
  169. package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
  170. package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
  171. package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
  172. package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
  173. package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
  174. package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
  175. package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
  176. package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
  177. package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
  178. package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
  179. package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
  180. package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
  181. package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
  182. package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
  183. package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
  184. package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
  185. package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
  186. package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
  187. package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
  188. package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
  189. package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
  190. package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
  191. package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
  192. package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
  193. package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
  194. package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
  195. package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
  196. package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
  197. package/ai-config/skills/prompt-improver/SKILL.md +125 -0
  198. package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
  199. package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
  200. package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
  201. package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
  202. package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
  203. package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
  204. package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
  205. package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
  206. package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
  207. package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
  208. package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
  209. package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
  210. package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
  211. package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
  212. package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
  213. package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
  214. package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
  215. package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
  216. package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
  217. package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
  218. package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
  219. package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
  220. package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
  221. package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
  222. package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
  223. package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
  224. package/ci-local/README.md +170 -0
  225. package/ci-local/ci-local.sh +297 -0
  226. package/ci-local/hooks/commit-msg +74 -0
  227. package/ci-local/hooks/pre-commit +162 -0
  228. package/ci-local/hooks/pre-push +41 -0
  229. package/ci-local/install.sh +49 -0
  230. package/ci-local/semgrep.yml +214 -0
  231. package/dist/commands/analyze.d.ts +9 -0
  232. package/dist/commands/analyze.d.ts.map +1 -0
  233. package/dist/commands/analyze.js +55 -0
  234. package/dist/commands/analyze.js.map +1 -0
  235. package/dist/commands/analyze.test.d.ts +2 -0
  236. package/dist/commands/analyze.test.d.ts.map +1 -0
  237. package/dist/commands/analyze.test.js +145 -0
  238. package/dist/commands/analyze.test.js.map +1 -0
  239. package/dist/commands/doctor.d.ts +7 -0
  240. package/dist/commands/doctor.d.ts.map +1 -0
  241. package/dist/commands/doctor.js +158 -0
  242. package/dist/commands/doctor.js.map +1 -0
  243. package/dist/commands/doctor.test.d.ts +2 -0
  244. package/dist/commands/doctor.test.d.ts.map +1 -0
  245. package/dist/commands/doctor.test.js +200 -0
  246. package/dist/commands/doctor.test.js.map +1 -0
  247. package/dist/commands/init.d.ts +9 -0
  248. package/dist/commands/init.d.ts.map +1 -0
  249. package/dist/commands/init.js +283 -0
  250. package/dist/commands/init.js.map +1 -0
  251. package/dist/commands/init.test.d.ts +2 -0
  252. package/dist/commands/init.test.d.ts.map +1 -0
  253. package/dist/commands/init.test.js +271 -0
  254. package/dist/commands/init.test.js.map +1 -0
  255. package/dist/commands/sync.d.ts +8 -0
  256. package/dist/commands/sync.d.ts.map +1 -0
  257. package/dist/commands/sync.js +201 -0
  258. package/dist/commands/sync.js.map +1 -0
  259. package/dist/constants.d.ts +21 -0
  260. package/dist/constants.d.ts.map +1 -0
  261. package/dist/constants.js +57 -0
  262. package/dist/constants.js.map +1 -0
  263. package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
  264. package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
  265. package/dist/e2e/aggressive.e2e.test.js +350 -0
  266. package/dist/e2e/aggressive.e2e.test.js.map +1 -0
  267. package/dist/e2e/commands.e2e.test.d.ts +2 -0
  268. package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
  269. package/dist/e2e/commands.e2e.test.js +213 -0
  270. package/dist/e2e/commands.e2e.test.js.map +1 -0
  271. package/dist/index.d.ts +3 -0
  272. package/dist/index.d.ts.map +1 -0
  273. package/dist/index.js +82 -0
  274. package/dist/index.js.map +1 -0
  275. package/dist/lib/common.d.ts +17 -0
  276. package/dist/lib/common.d.ts.map +1 -0
  277. package/dist/lib/common.js +111 -0
  278. package/dist/lib/common.js.map +1 -0
  279. package/dist/lib/common.test.d.ts +2 -0
  280. package/dist/lib/common.test.d.ts.map +1 -0
  281. package/dist/lib/common.test.js +316 -0
  282. package/dist/lib/common.test.js.map +1 -0
  283. package/dist/lib/frontmatter.d.ts +18 -0
  284. package/dist/lib/frontmatter.d.ts.map +1 -0
  285. package/dist/lib/frontmatter.js +61 -0
  286. package/dist/lib/frontmatter.js.map +1 -0
  287. package/dist/lib/frontmatter.test.d.ts +2 -0
  288. package/dist/lib/frontmatter.test.d.ts.map +1 -0
  289. package/dist/lib/frontmatter.test.js +257 -0
  290. package/dist/lib/frontmatter.test.js.map +1 -0
  291. package/dist/lib/template.d.ts +24 -0
  292. package/dist/lib/template.d.ts.map +1 -0
  293. package/dist/lib/template.js +78 -0
  294. package/dist/lib/template.js.map +1 -0
  295. package/dist/lib/template.test.d.ts +2 -0
  296. package/dist/lib/template.test.d.ts.map +1 -0
  297. package/dist/lib/template.test.js +201 -0
  298. package/dist/lib/template.test.js.map +1 -0
  299. package/dist/types/index.d.ts +48 -0
  300. package/dist/types/index.d.ts.map +1 -0
  301. package/dist/types/index.js +2 -0
  302. package/dist/types/index.js.map +1 -0
  303. package/dist/ui/AnalyzeUI.d.ts +7 -0
  304. package/dist/ui/AnalyzeUI.d.ts.map +1 -0
  305. package/dist/ui/AnalyzeUI.js +100 -0
  306. package/dist/ui/AnalyzeUI.js.map +1 -0
  307. package/dist/ui/App.d.ts +13 -0
  308. package/dist/ui/App.d.ts.map +1 -0
  309. package/dist/ui/App.js +100 -0
  310. package/dist/ui/App.js.map +1 -0
  311. package/dist/ui/CIContext.d.ts +9 -0
  312. package/dist/ui/CIContext.d.ts.map +1 -0
  313. package/dist/ui/CIContext.js +9 -0
  314. package/dist/ui/CIContext.js.map +1 -0
  315. package/dist/ui/CISelector.d.ts +8 -0
  316. package/dist/ui/CISelector.d.ts.map +1 -0
  317. package/dist/ui/CISelector.js +45 -0
  318. package/dist/ui/CISelector.js.map +1 -0
  319. package/dist/ui/Doctor.d.ts +3 -0
  320. package/dist/ui/Doctor.d.ts.map +1 -0
  321. package/dist/ui/Doctor.js +89 -0
  322. package/dist/ui/Doctor.js.map +1 -0
  323. package/dist/ui/Header.d.ts +8 -0
  324. package/dist/ui/Header.d.ts.map +1 -0
  325. package/dist/ui/Header.js +30 -0
  326. package/dist/ui/Header.js.map +1 -0
  327. package/dist/ui/MemorySelector.d.ts +8 -0
  328. package/dist/ui/MemorySelector.d.ts.map +1 -0
  329. package/dist/ui/MemorySelector.js +46 -0
  330. package/dist/ui/MemorySelector.js.map +1 -0
  331. package/dist/ui/NameInput.d.ts +8 -0
  332. package/dist/ui/NameInput.d.ts.map +1 -0
  333. package/dist/ui/NameInput.js +69 -0
  334. package/dist/ui/NameInput.js.map +1 -0
  335. package/dist/ui/OptionSelector.d.ts +12 -0
  336. package/dist/ui/OptionSelector.d.ts.map +1 -0
  337. package/dist/ui/OptionSelector.js +69 -0
  338. package/dist/ui/OptionSelector.js.map +1 -0
  339. package/dist/ui/Progress.d.ts +11 -0
  340. package/dist/ui/Progress.d.ts.map +1 -0
  341. package/dist/ui/Progress.js +58 -0
  342. package/dist/ui/Progress.js.map +1 -0
  343. package/dist/ui/StackSelector.d.ts +9 -0
  344. package/dist/ui/StackSelector.d.ts.map +1 -0
  345. package/dist/ui/StackSelector.js +65 -0
  346. package/dist/ui/StackSelector.js.map +1 -0
  347. package/dist/ui/Summary.d.ts +12 -0
  348. package/dist/ui/Summary.d.ts.map +1 -0
  349. package/dist/ui/Summary.js +114 -0
  350. package/dist/ui/Summary.js.map +1 -0
  351. package/dist/ui/SyncUI.d.ts +10 -0
  352. package/dist/ui/SyncUI.d.ts.map +1 -0
  353. package/dist/ui/SyncUI.js +64 -0
  354. package/dist/ui/SyncUI.js.map +1 -0
  355. package/dist/ui/Welcome.d.ts +7 -0
  356. package/dist/ui/Welcome.d.ts.map +1 -0
  357. package/dist/ui/Welcome.js +45 -0
  358. package/dist/ui/Welcome.js.map +1 -0
  359. package/dist/ui/theme.d.ts +10 -0
  360. package/dist/ui/theme.d.ts.map +1 -0
  361. package/dist/ui/theme.js +9 -0
  362. package/dist/ui/theme.js.map +1 -0
  363. package/modules/engram/.gitignore-snippet.txt +6 -0
  364. package/modules/engram/.mcp-config-snippet.json +11 -0
  365. package/modules/engram/README.md +146 -0
  366. package/modules/engram/install-engram.sh +216 -0
  367. package/modules/ghagga/.env.example +43 -0
  368. package/modules/ghagga/README.md +153 -0
  369. package/modules/ghagga/docker-compose.yml +80 -0
  370. package/modules/ghagga/setup-ghagga.sh +139 -0
  371. package/modules/memory-simple/.project/NOTES.md +22 -0
  372. package/modules/memory-simple/README.md +23 -0
  373. package/modules/obsidian-brain/.obsidian/app.json +23 -0
  374. package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
  375. package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
  376. package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
  377. package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
  378. package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
  379. package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
  380. package/modules/obsidian-brain/.obsidian/graph.json +37 -0
  381. package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
  382. package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
  383. package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
  384. package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
  385. package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
  386. package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
  387. package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
  388. package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
  389. package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
  390. package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
  391. package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
  392. package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
  393. package/modules/obsidian-brain/.project/Memory/README.md +61 -0
  394. package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
  395. package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
  396. package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
  397. package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
  398. package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
  399. package/modules/obsidian-brain/README.md +268 -0
  400. package/modules/obsidian-brain/new-wave.sh +182 -0
  401. package/package.json +51 -0
  402. package/schemas/agent.schema.json +34 -0
  403. package/schemas/ai-config.schema.json +28 -0
  404. package/schemas/skill.schema.json +44 -0
  405. package/src/commands/analyze.test.ts +145 -0
  406. package/src/commands/analyze.ts +69 -0
  407. package/src/commands/doctor.test.ts +208 -0
  408. package/src/commands/doctor.ts +163 -0
  409. package/src/commands/init.test.ts +298 -0
  410. package/src/commands/init.ts +285 -0
  411. package/src/constants.ts +69 -0
  412. package/src/e2e/aggressive.e2e.test.ts +557 -0
  413. package/src/e2e/commands.e2e.test.ts +298 -0
  414. package/src/index.tsx +106 -0
  415. package/src/lib/common.test.ts +318 -0
  416. package/src/lib/common.ts +127 -0
  417. package/src/lib/frontmatter.test.ts +291 -0
  418. package/src/lib/frontmatter.ts +77 -0
  419. package/src/lib/template.test.ts +226 -0
  420. package/src/lib/template.ts +99 -0
  421. package/src/types/index.ts +53 -0
  422. package/src/ui/AnalyzeUI.tsx +133 -0
  423. package/src/ui/App.tsx +175 -0
  424. package/src/ui/CIContext.tsx +25 -0
  425. package/src/ui/CISelector.tsx +72 -0
  426. package/src/ui/Doctor.tsx +122 -0
  427. package/src/ui/Header.tsx +48 -0
  428. package/src/ui/MemorySelector.tsx +73 -0
  429. package/src/ui/NameInput.tsx +82 -0
  430. package/src/ui/OptionSelector.tsx +100 -0
  431. package/src/ui/Progress.tsx +88 -0
  432. package/src/ui/StackSelector.tsx +101 -0
  433. package/src/ui/Summary.tsx +134 -0
  434. package/src/ui/Welcome.tsx +54 -0
  435. package/src/ui/theme.ts +10 -0
  436. package/stryker.config.json +19 -0
  437. package/tasks/_TEMPLATE/files-edited.md +3 -0
  438. package/tasks/_TEMPLATE/plan.md +3 -0
  439. package/tasks/_TEMPLATE/research.md +3 -0
  440. package/tasks/_TEMPLATE/verification.md +5 -0
  441. package/templates/common/dependabot/cargo.yml +11 -0
  442. package/templates/common/dependabot/github-actions.yml +16 -0
  443. package/templates/common/dependabot/gomod.yml +15 -0
  444. package/templates/common/dependabot/gradle.yml +15 -0
  445. package/templates/common/dependabot/header.yml +3 -0
  446. package/templates/common/dependabot/maven.yml +15 -0
  447. package/templates/common/dependabot/npm.yml +20 -0
  448. package/templates/common/dependabot/pip.yml +11 -0
  449. package/templates/dependabot.yml +162 -0
  450. package/templates/github/ci-go.yml +41 -0
  451. package/templates/github/ci-java.yml +45 -0
  452. package/templates/github/ci-monorepo.yml +150 -0
  453. package/templates/github/ci-node.yml +42 -0
  454. package/templates/github/ci-python.yml +42 -0
  455. package/templates/github/ci-rust.yml +42 -0
  456. package/templates/github/dependabot-automerge.yml +40 -0
  457. package/templates/gitlab/gitlab-ci-go.yml +88 -0
  458. package/templates/gitlab/gitlab-ci-java.yml +79 -0
  459. package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
  460. package/templates/gitlab/gitlab-ci-node.yml +63 -0
  461. package/templates/gitlab/gitlab-ci-python.yml +147 -0
  462. package/templates/gitlab/gitlab-ci-rust.yml +67 -0
  463. package/templates/global/claude-settings.json +98 -0
  464. package/templates/global/codex-config.toml +8 -0
  465. package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
  466. package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
  467. package/templates/global/gemini-commands/cleanup.toml +20 -0
  468. package/templates/global/gemini-commands/commit.toml +15 -0
  469. package/templates/global/gemini-commands/dead-code.toml +22 -0
  470. package/templates/global/gemini-commands/plan.toml +30 -0
  471. package/templates/global/gemini-commands/review.toml +17 -0
  472. package/templates/global/gemini-commands/sdd-apply.toml +22 -0
  473. package/templates/global/gemini-commands/sdd-ff.toml +14 -0
  474. package/templates/global/gemini-commands/sdd-new.toml +21 -0
  475. package/templates/global/gemini-commands/sdd-verify.toml +21 -0
  476. package/templates/global/gemini-commands/tdd.toml +26 -0
  477. package/templates/global/gemini-settings.json +8 -0
  478. package/templates/global/opencode-config.json +44 -0
  479. package/templates/global/sdd-instructions.md +47 -0
  480. package/templates/global/sdd-orchestrator-claude.md +46 -0
  481. package/templates/global/sdd-orchestrator-copilot.md +34 -0
  482. package/templates/renovate.json +69 -0
  483. package/templates/woodpecker/monorepo/backend.yml +34 -0
  484. package/templates/woodpecker/monorepo/frontend.yml +34 -0
  485. package/templates/woodpecker/monorepo/summary.yml +25 -0
  486. package/templates/woodpecker/woodpecker-go.yml +51 -0
  487. package/templates/woodpecker/woodpecker-java.yml +67 -0
  488. package/templates/woodpecker/woodpecker-node.yml +47 -0
  489. package/templates/woodpecker/woodpecker-python.yml +108 -0
  490. package/templates/woodpecker/woodpecker-rust.yml +57 -0
  491. package/tsconfig.json +19 -0
  492. package/vitest.config.ts +16 -0
  493. package/workflows/reusable-build-go.yml +111 -0
  494. package/workflows/reusable-build-java.yml +120 -0
  495. package/workflows/reusable-build-node.yml +145 -0
  496. package/workflows/reusable-build-python.yml +159 -0
  497. package/workflows/reusable-build-rust.yml +135 -0
  498. package/workflows/reusable-docker.yml +120 -0
  499. package/workflows/reusable-ghagga-review.yml +165 -0
  500. 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
+ })