bmad-method 5.0.0 → 5.0.1

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 (329) hide show
  1. package/.github/FUNDING.yaml +15 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  4. package/.github/workflows/discord.yaml +25 -0
  5. package/.github/workflows/format-check.yaml +42 -0
  6. package/.github/workflows/manual-release.yaml +173 -0
  7. package/.husky/pre-commit +3 -2
  8. package/.vscode/settings.json +67 -74
  9. package/CHANGELOG.md +564 -19
  10. package/CONTRIBUTING.md +168 -5
  11. package/LICENSE +1 -1
  12. package/README.md +146 -218
  13. package/bmad-core/agent-teams/team-all.yaml +14 -0
  14. package/bmad-core/agent-teams/team-fullstack.yaml +18 -0
  15. package/bmad-core/agent-teams/team-ide-minimal.yaml +10 -0
  16. package/bmad-core/agent-teams/team-no-ui.yaml +13 -0
  17. package/bmad-core/agents/analyst.md +81 -0
  18. package/bmad-core/agents/architect.md +83 -0
  19. package/bmad-core/agents/bmad-master.md +107 -0
  20. package/bmad-core/agents/bmad-orchestrator.md +149 -0
  21. package/bmad-core/agents/dev.md +75 -0
  22. package/bmad-core/agents/pm.md +81 -0
  23. package/bmad-core/agents/po.md +76 -0
  24. package/bmad-core/agents/qa.md +88 -0
  25. package/bmad-core/agents/sm.md +62 -0
  26. package/bmad-core/agents/ux-expert.md +66 -0
  27. package/{.bmad-core → bmad-core}/checklists/architect-checklist.md +0 -5
  28. package/{.bmad-core → bmad-core}/checklists/change-checklist.md +2 -2
  29. package/{.bmad-core → bmad-core}/checklists/pm-checklist.md +0 -5
  30. package/{.bmad-core → bmad-core}/checklists/po-master-checklist.md +0 -9
  31. package/{.bmad-core → bmad-core}/checklists/story-dod-checklist.md +0 -7
  32. package/{.bmad-core → bmad-core}/checklists/story-draft-checklist.md +1 -4
  33. package/bmad-core/core-config.yaml +20 -0
  34. package/bmad-core/data/bmad-kb.md +806 -0
  35. package/bmad-core/data/brainstorming-techniques.md +36 -0
  36. package/bmad-core/data/elicitation-methods.md +154 -0
  37. package/bmad-core/data/test-levels-framework.md +146 -0
  38. package/bmad-core/data/test-priorities-matrix.md +172 -0
  39. package/bmad-core/tasks/advanced-elicitation.md +117 -0
  40. package/{.bmad-core → bmad-core}/tasks/correct-course.md +9 -12
  41. package/bmad-core/tasks/create-brownfield-story.md +312 -0
  42. package/{.bmad-core → bmad-core}/tasks/create-deep-research-prompt.md +4 -27
  43. package/bmad-core/tasks/create-next-story.md +112 -0
  44. package/bmad-core/tasks/document-project.md +343 -0
  45. package/bmad-core/tasks/facilitate-brainstorming-session.md +136 -0
  46. package/bmad-core/tasks/generate-ai-frontend-prompt.md +51 -0
  47. package/{.bmad-core → bmad-core}/tasks/index-docs.md +3 -13
  48. package/bmad-core/tasks/kb-mode-interaction.md +75 -0
  49. package/bmad-core/tasks/nfr-assess.md +343 -0
  50. package/bmad-core/tasks/qa-gate.md +159 -0
  51. package/bmad-core/tasks/review-story.md +314 -0
  52. package/bmad-core/tasks/risk-profile.md +353 -0
  53. package/{.bmad-core → bmad-core}/tasks/shard-doc.md +27 -15
  54. package/bmad-core/tasks/test-design.md +174 -0
  55. package/bmad-core/tasks/trace-requirements.md +264 -0
  56. package/bmad-core/tasks/validate-next-story.md +134 -0
  57. package/bmad-core/templates/architecture-tmpl.yaml +650 -0
  58. package/bmad-core/templates/brainstorming-output-tmpl.yaml +156 -0
  59. package/bmad-core/templates/brownfield-architecture-tmpl.yaml +476 -0
  60. package/bmad-core/templates/brownfield-prd-tmpl.yaml +280 -0
  61. package/bmad-core/templates/competitor-analysis-tmpl.yaml +306 -0
  62. package/bmad-core/templates/front-end-architecture-tmpl.yaml +218 -0
  63. package/bmad-core/templates/front-end-spec-tmpl.yaml +349 -0
  64. package/bmad-core/templates/fullstack-architecture-tmpl.yaml +823 -0
  65. package/bmad-core/templates/market-research-tmpl.yaml +252 -0
  66. package/bmad-core/templates/prd-tmpl.yaml +202 -0
  67. package/bmad-core/templates/project-brief-tmpl.yaml +221 -0
  68. package/bmad-core/templates/qa-gate-tmpl.yaml +102 -0
  69. package/bmad-core/templates/story-tmpl.yaml +137 -0
  70. package/bmad-core/workflows/brownfield-fullstack.yaml +297 -0
  71. package/bmad-core/workflows/brownfield-service.yaml +187 -0
  72. package/bmad-core/workflows/brownfield-ui.yaml +197 -0
  73. package/{.bmad-core/workflows/greenfield-fullstack.yml → bmad-core/workflows/greenfield-fullstack.yaml} +140 -77
  74. package/bmad-core/workflows/greenfield-service.yaml +206 -0
  75. package/bmad-core/workflows/greenfield-ui.yaml +235 -0
  76. package/common/tasks/create-doc.md +101 -0
  77. package/{.bmad-core → common}/tasks/execute-checklist.md +2 -13
  78. package/common/utils/bmad-doc-template.md +325 -0
  79. package/common/utils/workflow-management.md +69 -0
  80. package/dist/agents/analyst.txt +2889 -0
  81. package/dist/agents/architect.txt +3552 -0
  82. package/dist/agents/bmad-master.txt +8769 -0
  83. package/dist/agents/bmad-orchestrator.txt +1513 -0
  84. package/dist/agents/dev.txt +414 -0
  85. package/{.bmad-core/web-bundles → dist}/agents/pm.txt +668 -1119
  86. package/{.bmad-core/web-bundles → dist}/agents/po.txt +341 -484
  87. package/dist/agents/qa.txt +1987 -0
  88. package/dist/agents/sm.txt +658 -0
  89. package/dist/agents/ux-expert.txt +694 -0
  90. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +2371 -0
  91. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +1620 -0
  92. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +815 -0
  93. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +10952 -0
  94. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +4012 -0
  95. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +3698 -0
  96. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +450 -0
  97. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +973 -0
  98. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +15376 -0
  99. package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +2075 -0
  100. package/dist/teams/team-all.txt +12682 -0
  101. package/dist/teams/team-fullstack.txt +10421 -0
  102. package/dist/teams/team-ide-minimal.txt +5103 -0
  103. package/dist/teams/team-no-ui.txt +8980 -0
  104. package/docs/GUIDING-PRINCIPLES.md +91 -0
  105. package/docs/core-architecture.md +219 -0
  106. package/docs/enhanced-ide-development-workflow.md +248 -0
  107. package/docs/expansion-packs.md +280 -0
  108. package/docs/how-to-contribute-with-pull-requests.md +158 -0
  109. package/docs/user-guide.md +504 -0
  110. package/docs/versioning-and-releases.md +115 -53
  111. package/docs/versions.md +4 -5
  112. package/docs/working-in-the-brownfield.md +597 -0
  113. package/eslint.config.mjs +119 -0
  114. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/Complete AI Agent System - Flowchart.svg +102 -0
  115. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.1 Google Cloud Project Setup/1.1.1 - Initial Project Configuration - bash copy.txt +13 -0
  116. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.1 Google Cloud Project Setup/1.1.1 - Initial Project Configuration - bash.txt +13 -0
  117. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.2 Agent Development Kit Installation/1.2.2 - Basic Project Structure - txt.txt +25 -0
  118. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.3 Core Configuration Files/1.3.1 - settings.py +34 -0
  119. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.3 Core Configuration Files/1.3.2 - main.py - Base Application.py +70 -0
  120. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.4 Deployment Configuration/1.4.2 - cloudbuild.yaml +26 -0
  121. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/README.md +109 -0
  122. package/expansion-packs/README.md +2 -112
  123. package/expansion-packs/bmad-2d-phaser-game-dev/agent-teams/phaser-2d-nodejs-game-team.yaml +13 -0
  124. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.md +71 -0
  125. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +78 -0
  126. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +64 -0
  127. package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-design-checklist.md +201 -0
  128. package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-story-dod-checklist.md +160 -0
  129. package/expansion-packs/bmad-2d-phaser-game-dev/config.yaml +8 -0
  130. package/expansion-packs/bmad-2d-phaser-game-dev/data/bmad-kb.md +250 -0
  131. package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +647 -0
  132. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/advanced-elicitation.md +110 -0
  133. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/create-game-story.md +216 -0
  134. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/game-design-brainstorming.md +290 -0
  135. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +613 -0
  136. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +356 -0
  137. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +343 -0
  138. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +253 -0
  139. package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +484 -0
  140. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +183 -0
  141. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +175 -0
  142. package/expansion-packs/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +14 -0
  143. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.md +80 -0
  144. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.md +77 -0
  145. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +78 -0
  146. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.md +65 -0
  147. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +391 -0
  148. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +203 -0
  149. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +201 -0
  150. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +124 -0
  151. package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +6 -0
  152. package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +769 -0
  153. package/expansion-packs/bmad-2d-unity-game-dev/data/development-guidelines.md +586 -0
  154. package/expansion-packs/bmad-2d-unity-game-dev/tasks/advanced-elicitation.md +110 -0
  155. package/expansion-packs/bmad-2d-unity-game-dev/tasks/correct-course-game.md +141 -0
  156. package/expansion-packs/bmad-2d-unity-game-dev/tasks/create-game-story.md +184 -0
  157. package/expansion-packs/bmad-2d-unity-game-dev/tasks/game-design-brainstorming.md +290 -0
  158. package/expansion-packs/bmad-2d-unity-game-dev/tasks/validate-game-story.md +200 -0
  159. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +1030 -0
  160. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +356 -0
  161. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +705 -0
  162. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +256 -0
  163. package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +484 -0
  164. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +183 -0
  165. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +175 -0
  166. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/README.md +9 -9
  167. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/agents/infra-devops-platform.md +30 -18
  168. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/checklists/infrastructure-checklist.md +1 -1
  169. package/expansion-packs/bmad-infrastructure-devops/config.yaml +9 -0
  170. package/expansion-packs/bmad-infrastructure-devops/data/bmad-kb.md +305 -0
  171. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/tasks/review-infrastructure.md +4 -5
  172. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/tasks/validate-infrastructure.md +4 -5
  173. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +424 -0
  174. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +629 -0
  175. package/package.json +74 -42
  176. package/prettier.config.mjs +32 -0
  177. package/release_notes.md +30 -0
  178. package/tools/bmad-npx-wrapper.js +13 -15
  179. package/tools/builders/web-builder.js +544 -15
  180. package/tools/bump-all-versions.js +115 -0
  181. package/tools/bump-expansion-version.js +90 -0
  182. package/tools/cli.js +65 -32
  183. package/tools/flattener/aggregate.js +76 -0
  184. package/tools/flattener/binary.js +80 -0
  185. package/tools/flattener/discovery.js +71 -0
  186. package/tools/flattener/files.js +35 -0
  187. package/tools/flattener/ignoreRules.js +176 -0
  188. package/tools/flattener/main.js +573 -0
  189. package/tools/flattener/projectRoot.js +206 -0
  190. package/tools/flattener/prompts.js +44 -0
  191. package/tools/flattener/stats.helpers.js +395 -0
  192. package/tools/flattener/stats.js +80 -0
  193. package/tools/flattener/test-matrix.js +413 -0
  194. package/tools/flattener/xml.js +88 -0
  195. package/tools/installer/README.md +3 -53
  196. package/tools/installer/bin/bmad.js +475 -90
  197. package/tools/installer/config/ide-agent-config.yaml +58 -0
  198. package/tools/installer/config/install.config.yaml +123 -0
  199. package/tools/installer/lib/config-loader.js +208 -40
  200. package/tools/installer/lib/file-manager.js +258 -55
  201. package/tools/installer/lib/ide-base-setup.js +228 -0
  202. package/tools/installer/lib/ide-setup.js +1265 -253
  203. package/tools/installer/lib/installer.js +1651 -310
  204. package/tools/installer/lib/memory-profiler.js +225 -0
  205. package/tools/installer/lib/module-manager.js +114 -0
  206. package/tools/installer/lib/resource-locator.js +308 -0
  207. package/tools/installer/package.json +25 -24
  208. package/tools/lib/dependency-resolver.js +44 -48
  209. package/tools/lib/yaml-utils.js +29 -0
  210. package/tools/md-assets/web-agent-startup-instructions.md +39 -0
  211. package/tools/preview-release-notes.js +66 -0
  212. package/tools/shared/bannerArt.js +105 -0
  213. package/tools/sync-installer-version.js +7 -9
  214. package/tools/update-expansion-version.js +53 -0
  215. package/tools/upgraders/v3-to-v4-upgrader.js +221 -320
  216. package/tools/version-bump.js +42 -27
  217. package/tools/yaml-format.js +57 -44
  218. package/.bmad-core/agent-teams/team-all.yml +0 -16
  219. package/.bmad-core/agent-teams/team-fullstack.yml +0 -26
  220. package/.bmad-core/agent-teams/team-no-ui.yml +0 -15
  221. package/.bmad-core/agents/analyst.md +0 -59
  222. package/.bmad-core/agents/architect.md +0 -66
  223. package/.bmad-core/agents/bmad-master.md +0 -104
  224. package/.bmad-core/agents/bmad-orchestrator.md +0 -81
  225. package/.bmad-core/agents/dev.md +0 -70
  226. package/.bmad-core/agents/pm.md +0 -59
  227. package/.bmad-core/agents/po.md +0 -60
  228. package/.bmad-core/agents/qa.md +0 -52
  229. package/.bmad-core/agents/sm.md +0 -55
  230. package/.bmad-core/agents/ux-expert.md +0 -66
  231. package/.bmad-core/data/bmad-kb.md +0 -47
  232. package/.bmad-core/schemas/agent-team-schema.yml +0 -153
  233. package/.bmad-core/tasks/advanced-elicitation.md +0 -92
  234. package/.bmad-core/tasks/brainstorming-techniques.md +0 -238
  235. package/.bmad-core/tasks/core-dump.md +0 -74
  236. package/.bmad-core/tasks/create-agent.md +0 -202
  237. package/.bmad-core/tasks/create-doc.md +0 -74
  238. package/.bmad-core/tasks/create-expansion-pack.md +0 -425
  239. package/.bmad-core/tasks/create-next-story.md +0 -206
  240. package/.bmad-core/tasks/create-team.md +0 -229
  241. package/.bmad-core/tasks/doc-migration-task.md +0 -143
  242. package/.bmad-core/tasks/generate-ai-frontend-prompt.md +0 -58
  243. package/.bmad-core/templates/agent-tmpl.md +0 -58
  244. package/.bmad-core/templates/architecture-tmpl.md +0 -771
  245. package/.bmad-core/templates/brownfield-architecture-tmpl.md +0 -542
  246. package/.bmad-core/templates/brownfield-prd-tmpl.md +0 -240
  247. package/.bmad-core/templates/competitor-analysis-tmpl.md +0 -289
  248. package/.bmad-core/templates/expansion-pack-plan-tmpl.md +0 -91
  249. package/.bmad-core/templates/front-end-architecture-tmpl.md +0 -173
  250. package/.bmad-core/templates/front-end-spec-tmpl.md +0 -411
  251. package/.bmad-core/templates/fullstack-architecture-tmpl.md +0 -1016
  252. package/.bmad-core/templates/market-research-tmpl.md +0 -261
  253. package/.bmad-core/templates/prd-tmpl.md +0 -200
  254. package/.bmad-core/templates/project-brief-tmpl.md +0 -228
  255. package/.bmad-core/templates/simple-project-prd-tmpl.md +0 -461
  256. package/.bmad-core/templates/story-tmpl.md +0 -61
  257. package/.bmad-core/templates/web-agent-startup-instructions-template.md +0 -39
  258. package/.bmad-core/utils/agent-switcher.ide.md +0 -112
  259. package/.bmad-core/utils/template-format.md +0 -26
  260. package/.bmad-core/utils/workflow-management.md +0 -224
  261. package/.bmad-core/web-bundles/agents/analyst.txt +0 -1684
  262. package/.bmad-core/web-bundles/agents/architect.txt +0 -3584
  263. package/.bmad-core/web-bundles/agents/bmad-master.txt +0 -9491
  264. package/.bmad-core/web-bundles/agents/bmad-orchestrator.txt +0 -1466
  265. package/.bmad-core/web-bundles/agents/dev.txt +0 -316
  266. package/.bmad-core/web-bundles/agents/qa.txt +0 -129
  267. package/.bmad-core/web-bundles/agents/sm.txt +0 -658
  268. package/.bmad-core/web-bundles/agents/ux-expert.txt +0 -1099
  269. package/.bmad-core/web-bundles/teams/team-all.txt +0 -10757
  270. package/.bmad-core/web-bundles/teams/team-fullstack.txt +0 -10109
  271. package/.bmad-core/web-bundles/teams/team-no-ui.txt +0 -8950
  272. package/.bmad-core/workflows/brownfield-fullstack.yml +0 -116
  273. package/.bmad-core/workflows/brownfield-service.yml +0 -117
  274. package/.bmad-core/workflows/brownfield-ui.yml +0 -127
  275. package/.bmad-core/workflows/greenfield-service.yml +0 -143
  276. package/.bmad-core/workflows/greenfield-ui.yml +0 -172
  277. package/.claude/commands/analyst.md +0 -63
  278. package/.claude/commands/architect.md +0 -70
  279. package/.claude/commands/bmad-master.md +0 -108
  280. package/.claude/commands/bmad-orchestrator.md +0 -85
  281. package/.claude/commands/dev.md +0 -74
  282. package/.claude/commands/pm.md +0 -63
  283. package/.claude/commands/po.md +0 -64
  284. package/.claude/commands/qa.md +0 -56
  285. package/.claude/commands/sm.md +0 -59
  286. package/.claude/commands/ux-expert.md +0 -70
  287. package/.cursor/rules/analyst.mdc +0 -77
  288. package/.cursor/rules/architect.mdc +0 -84
  289. package/.cursor/rules/bmad-master.mdc +0 -122
  290. package/.cursor/rules/bmad-orchestrator.mdc +0 -99
  291. package/.cursor/rules/dev.mdc +0 -88
  292. package/.cursor/rules/pm.mdc +0 -77
  293. package/.cursor/rules/po.mdc +0 -78
  294. package/.cursor/rules/qa.mdc +0 -70
  295. package/.cursor/rules/sm.mdc +0 -73
  296. package/.cursor/rules/ux-expert.mdc +0 -84
  297. package/.github/workflows/release.yml +0 -59
  298. package/.releaserc.json +0 -18
  299. package/.roo/.roomodes +0 -95
  300. package/.roo/README.md +0 -38
  301. package/.vscode/extensions.json +0 -6
  302. package/.windsurf/rules/analyst.md +0 -71
  303. package/.windsurf/rules/architect.md +0 -78
  304. package/.windsurf/rules/bmad-master.md +0 -116
  305. package/.windsurf/rules/bmad-orchestrator.md +0 -93
  306. package/.windsurf/rules/dev.md +0 -82
  307. package/.windsurf/rules/pm.md +0 -71
  308. package/.windsurf/rules/po.md +0 -72
  309. package/.windsurf/rules/qa.md +0 -64
  310. package/.windsurf/rules/sm.md +0 -67
  311. package/.windsurf/rules/ux-expert.md +0 -78
  312. package/docs/bmad-workflow-guide.md +0 -161
  313. package/docs/claude-code-guide.md +0 -119
  314. package/docs/cursor-guide.md +0 -127
  315. package/docs/roo-code-guide.md +0 -140
  316. package/docs/sample-output/simple-fullstack-greenfield/prd.md +0 -42
  317. package/docs/windsurf-guide.md +0 -127
  318. package/expansion-packs/infrastructure-devops/manifest.yml +0 -38
  319. package/expansion-packs/infrastructure-devops/templates/infrastructure-architecture-tmpl.md +0 -415
  320. package/expansion-packs/infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.md +0 -0
  321. package/tools/installer/config/install.config.yml +0 -139
  322. package/tools/installer/package-lock.json +0 -906
  323. package/tools/installer/templates/claude-commands.md +0 -7
  324. package/tools/installer/templates/cursor-rules.md +0 -22
  325. package/tools/installer/templates/windsurf-rules.md +0 -22
  326. package/tools/semantic-release-sync-installer.js +0 -31
  327. /package/{.bmad-core → bmad-core}/data/technical-preferences.md +0 -0
  328. /package/{.bmad-core → bmad-core}/tasks/brownfield-create-epic.md +0 -0
  329. /package/{.bmad-core → bmad-core}/tasks/brownfield-create-story.md +0 -0
@@ -0,0 +1,206 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('node:path');
3
+
4
+ // Deno/Node compatibility: explicitly import process
5
+ const process = require('node:process');
6
+ const { execFile } = require('node:child_process');
7
+ const { promisify } = require('node:util');
8
+ const execFileAsync = promisify(execFile);
9
+
10
+ // Simple memoization across calls (keyed by realpath of startDir)
11
+ const _cache = new Map();
12
+
13
+ async function _tryRun(cmd, args, cwd, timeoutMs = 500) {
14
+ try {
15
+ const { stdout } = await execFileAsync(cmd, args, {
16
+ cwd,
17
+ timeout: timeoutMs,
18
+ windowsHide: true,
19
+ maxBuffer: 1024 * 1024,
20
+ });
21
+ const out = String(stdout || '').trim();
22
+ return out || null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ async function _detectVcsTopLevel(startDir) {
29
+ // Run common VCS root queries in parallel; ignore failures
30
+ const gitP = _tryRun('git', ['rev-parse', '--show-toplevel'], startDir);
31
+ const hgP = _tryRun('hg', ['root'], startDir);
32
+ const svnP = (async () => {
33
+ const show = await _tryRun('svn', ['info', '--show-item', 'wc-root'], startDir);
34
+ if (show) return show;
35
+ const info = await _tryRun('svn', ['info'], startDir);
36
+ if (info) {
37
+ const line = info
38
+ .split(/\r?\n/)
39
+ .find((l) => l.toLowerCase().startsWith('working copy root path:'));
40
+ if (line) return line.split(':').slice(1).join(':').trim();
41
+ }
42
+ return null;
43
+ })();
44
+ const [git, hg, svn] = await Promise.all([gitP, hgP, svnP]);
45
+ return git || hg || svn || null;
46
+ }
47
+
48
+ /**
49
+ * Attempt to find the project root by walking up from startDir.
50
+ * Uses a robust, prioritized set of ecosystem markers (VCS > workspaces/monorepo > lock/build > language config).
51
+ * Also recognizes package.json with "workspaces" as a workspace root.
52
+ * You can augment markers via env PROJECT_ROOT_MARKERS as a comma-separated list of file/dir names.
53
+ * @param {string} startDir
54
+ * @returns {Promise<string|null>} project root directory or null if not found
55
+ */
56
+ async function findProjectRoot(startDir) {
57
+ try {
58
+ // Resolve symlinks for robustness (e.g., when invoked from a symlinked path)
59
+ let dir = path.resolve(startDir);
60
+ try {
61
+ dir = await fs.realpath(dir);
62
+ } catch {
63
+ // ignore if realpath fails; continue with resolved path
64
+ }
65
+ const startKey = dir; // preserve starting point for caching
66
+ if (_cache.has(startKey)) return _cache.get(startKey);
67
+ const fsRoot = path.parse(dir).root;
68
+
69
+ // Helper to safely check for existence
70
+ const exists = (p) => fs.pathExists(p);
71
+
72
+ // Build checks: an array of { makePath: (dir) => string, weight }
73
+ const checks = [];
74
+
75
+ const add = (rel, weight) => {
76
+ const makePath = (d) => (Array.isArray(rel) ? path.join(d, ...rel) : path.join(d, rel));
77
+ checks.push({ makePath, weight });
78
+ };
79
+
80
+ // Highest priority: explicit sentinel markers
81
+ add('.project-root', 110);
82
+ add('.workspace-root', 110);
83
+ add('.repo-root', 110);
84
+
85
+ // Highest priority: VCS roots
86
+ add('.git', 100);
87
+ add('.hg', 95);
88
+ add('.svn', 95);
89
+
90
+ // Monorepo/workspace indicators
91
+ add('pnpm-workspace.yaml', 90);
92
+ add('lerna.json', 90);
93
+ add('turbo.json', 90);
94
+ add('nx.json', 90);
95
+ add('rush.json', 90);
96
+ add('go.work', 90);
97
+ add('WORKSPACE', 90);
98
+ add('WORKSPACE.bazel', 90);
99
+ add('MODULE.bazel', 90);
100
+ add('pants.toml', 90);
101
+
102
+ // Lockfiles and package-manager/top-level locks
103
+ add('yarn.lock', 85);
104
+ add('pnpm-lock.yaml', 85);
105
+ add('package-lock.json', 85);
106
+ add('bun.lockb', 85);
107
+ add('Cargo.lock', 85);
108
+ add('composer.lock', 85);
109
+ add('poetry.lock', 85);
110
+ add('Pipfile.lock', 85);
111
+ add('Gemfile.lock', 85);
112
+
113
+ // Build-system root indicators
114
+ add('settings.gradle', 80);
115
+ add('settings.gradle.kts', 80);
116
+ add('gradlew', 80);
117
+ add('pom.xml', 80);
118
+ add('build.sbt', 80);
119
+ add(['project', 'build.properties'], 80);
120
+
121
+ // Language/project config markers
122
+ add('deno.json', 75);
123
+ add('deno.jsonc', 75);
124
+ add('pyproject.toml', 75);
125
+ add('Pipfile', 75);
126
+ add('requirements.txt', 75);
127
+ add('go.mod', 75);
128
+ add('Cargo.toml', 75);
129
+ add('composer.json', 75);
130
+ add('mix.exs', 75);
131
+ add('Gemfile', 75);
132
+ add('CMakeLists.txt', 75);
133
+ add('stack.yaml', 75);
134
+ add('cabal.project', 75);
135
+ add('rebar.config', 75);
136
+ add('pubspec.yaml', 75);
137
+ add('flake.nix', 75);
138
+ add('shell.nix', 75);
139
+ add('default.nix', 75);
140
+ add('.tool-versions', 75);
141
+ add('package.json', 74); // generic Node project (lower than lockfiles/workspaces)
142
+
143
+ // Changesets
144
+ add(['.changeset', 'config.json'], 70);
145
+ add('.changeset', 70);
146
+
147
+ // Custom markers via env (comma-separated names)
148
+ if (process.env.PROJECT_ROOT_MARKERS) {
149
+ for (const name of process.env.PROJECT_ROOT_MARKERS.split(',')
150
+ .map((s) => s.trim())
151
+ .filter(Boolean)) {
152
+ add(name, 72);
153
+ }
154
+ }
155
+
156
+ /** Check for package.json with "workspaces" */
157
+ const hasWorkspacePackageJson = async (d) => {
158
+ const pkgPath = path.join(d, 'package.json');
159
+ if (!(await exists(pkgPath))) return false;
160
+ try {
161
+ const raw = await fs.readFile(pkgPath, 'utf8');
162
+ const pkg = JSON.parse(raw);
163
+ return Boolean(pkg && pkg.workspaces);
164
+ } catch {
165
+ return false;
166
+ }
167
+ };
168
+
169
+ let best = null; // { dir, weight }
170
+
171
+ // Try to detect VCS toplevel once up-front; treat as authoritative slightly above .git marker
172
+ const vcsTop = await _detectVcsTopLevel(dir);
173
+ if (vcsTop) {
174
+ best = { dir: vcsTop, weight: 101 };
175
+ }
176
+
177
+ while (true) {
178
+ // Special check: package.json with "workspaces"
179
+ if ((await hasWorkspacePackageJson(dir)) && (!best || 90 >= best.weight))
180
+ best = { dir, weight: 90 };
181
+
182
+ // Evaluate all other checks in parallel
183
+ const results = await Promise.all(
184
+ checks.map(async (c) => ({ c, ok: await exists(c.makePath(dir)) })),
185
+ );
186
+
187
+ for (const { c, ok } of results) {
188
+ if (!ok) continue;
189
+ if (!best || c.weight >= best.weight) {
190
+ best = { dir, weight: c.weight };
191
+ }
192
+ }
193
+
194
+ if (dir === fsRoot) break;
195
+ dir = path.dirname(dir);
196
+ }
197
+
198
+ const out = best ? best.dir : null;
199
+ _cache.set(startKey, out);
200
+ return out;
201
+ } catch {
202
+ return null;
203
+ }
204
+ }
205
+
206
+ module.exports = { findProjectRoot };
@@ -0,0 +1,44 @@
1
+ const os = require('node:os');
2
+ const path = require('node:path');
3
+ const readline = require('node:readline');
4
+ const process = require('node:process');
5
+
6
+ function expandHome(p) {
7
+ if (!p) return p;
8
+ if (p.startsWith('~')) return path.join(os.homedir(), p.slice(1));
9
+ return p;
10
+ }
11
+
12
+ function createRl() {
13
+ return readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ });
17
+ }
18
+
19
+ function promptQuestion(question) {
20
+ return new Promise((resolve) => {
21
+ const rl = createRl();
22
+ rl.question(question, (answer) => {
23
+ rl.close();
24
+ resolve(answer);
25
+ });
26
+ });
27
+ }
28
+
29
+ async function promptYesNo(question, defaultYes = true) {
30
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
31
+ const ans = (await promptQuestion(`${question}${suffix}`)).trim().toLowerCase();
32
+ if (!ans) return defaultYes;
33
+ if (['y', 'yes'].includes(ans)) return true;
34
+ if (['n', 'no'].includes(ans)) return false;
35
+ return promptYesNo(question, defaultYes);
36
+ }
37
+
38
+ async function promptPath(question, defaultValue) {
39
+ const prompt = `${question}${defaultValue ? ` (default: ${defaultValue})` : ''}: `;
40
+ const ans = (await promptQuestion(prompt)).trim();
41
+ return expandHome(ans || defaultValue);
42
+ }
43
+
44
+ module.exports = { promptYesNo, promptPath, promptQuestion, expandHome };
@@ -0,0 +1,395 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const zlib = require('node:zlib');
6
+ const { Buffer } = require('node:buffer');
7
+ const crypto = require('node:crypto');
8
+ const cp = require('node:child_process');
9
+
10
+ const KB = 1024;
11
+ const MB = 1024 * KB;
12
+
13
+ const formatSize = (bytes) => {
14
+ if (bytes < 1024) return `${bytes} B`;
15
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
16
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
17
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
18
+ };
19
+
20
+ const percentile = (sorted, p) => {
21
+ if (sorted.length === 0) return 0;
22
+ const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
23
+ return sorted[idx];
24
+ };
25
+
26
+ async function processWithLimit(items, fn, concurrency = 64) {
27
+ for (let i = 0; i < items.length; i += concurrency) {
28
+ await Promise.all(items.slice(i, i + concurrency).map(fn));
29
+ }
30
+ }
31
+
32
+ async function enrichAllFiles(textFiles, binaryFiles) {
33
+ /** @type {Array<{ path: string; absolutePath: string; size: number; lines?: number; isBinary: boolean; ext: string; dir: string; depth: number; hidden: boolean; mtimeMs: number; isSymlink: boolean; }>} */
34
+ const allFiles = [];
35
+
36
+ async function enrich(file, isBinary) {
37
+ const ext = (path.extname(file.path) || '').toLowerCase();
38
+ const dir = path.dirname(file.path) || '.';
39
+ const depth = file.path.split(path.sep).filter(Boolean).length;
40
+ const hidden = file.path.split(path.sep).some((seg) => seg.startsWith('.'));
41
+ let mtimeMs = 0;
42
+ let isSymlink = false;
43
+ try {
44
+ const lst = await fs.lstat(file.absolutePath);
45
+ mtimeMs = lst.mtimeMs;
46
+ isSymlink = lst.isSymbolicLink();
47
+ } catch {
48
+ /* ignore lstat errors during enrichment */
49
+ }
50
+ allFiles.push({
51
+ path: file.path,
52
+ absolutePath: file.absolutePath,
53
+ size: file.size || 0,
54
+ lines: file.lines,
55
+ isBinary,
56
+ ext,
57
+ dir,
58
+ depth,
59
+ hidden,
60
+ mtimeMs,
61
+ isSymlink,
62
+ });
63
+ }
64
+
65
+ await processWithLimit(textFiles, (f) => enrich(f, false));
66
+ await processWithLimit(binaryFiles, (f) => enrich(f, true));
67
+ return allFiles;
68
+ }
69
+
70
+ function buildHistogram(allFiles) {
71
+ const buckets = [
72
+ [1 * KB, '0–1KB'],
73
+ [10 * KB, '1–10KB'],
74
+ [100 * KB, '10–100KB'],
75
+ [1 * MB, '100KB–1MB'],
76
+ [10 * MB, '1–10MB'],
77
+ [100 * MB, '10–100MB'],
78
+ [Infinity, '>=100MB'],
79
+ ];
80
+ const histogram = buckets.map(([_, label]) => ({ label, count: 0, bytes: 0 }));
81
+ for (const f of allFiles) {
82
+ for (const [i, bucket] of buckets.entries()) {
83
+ if (f.size < bucket[0]) {
84
+ histogram[i].count++;
85
+ histogram[i].bytes += f.size;
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ return histogram;
91
+ }
92
+
93
+ function aggregateByExtension(allFiles) {
94
+ const byExtension = new Map();
95
+ for (const f of allFiles) {
96
+ const key = f.ext || '<none>';
97
+ const v = byExtension.get(key) || { ext: key, count: 0, bytes: 0 };
98
+ v.count++;
99
+ v.bytes += f.size;
100
+ byExtension.set(key, v);
101
+ }
102
+ return [...byExtension.values()].sort((a, b) => b.bytes - a.bytes);
103
+ }
104
+
105
+ function aggregateByDirectory(allFiles) {
106
+ const byDirectory = new Map();
107
+ function addDirBytes(dir, bytes) {
108
+ const v = byDirectory.get(dir) || { dir, count: 0, bytes: 0 };
109
+ v.count++;
110
+ v.bytes += bytes;
111
+ byDirectory.set(dir, v);
112
+ }
113
+ for (const f of allFiles) {
114
+ const parts = f.dir === '.' ? [] : f.dir.split(path.sep);
115
+ let acc = '';
116
+ for (let i = 0; i < parts.length; i++) {
117
+ acc = i === 0 ? parts[0] : acc + path.sep + parts[i];
118
+ addDirBytes(acc, f.size);
119
+ }
120
+ if (parts.length === 0) addDirBytes('.', f.size);
121
+ }
122
+ return [...byDirectory.values()].sort((a, b) => b.bytes - a.bytes);
123
+ }
124
+
125
+ function computeDepthAndLongest(allFiles) {
126
+ const depthDistribution = new Map();
127
+ for (const f of allFiles) {
128
+ depthDistribution.set(f.depth, (depthDistribution.get(f.depth) || 0) + 1);
129
+ }
130
+ const longestPaths = [...allFiles]
131
+ .sort((a, b) => b.path.length - a.path.length)
132
+ .slice(0, 25)
133
+ .map((f) => ({ path: f.path, length: f.path.length, size: f.size }));
134
+ const depthDist = [...depthDistribution.entries()]
135
+ .sort((a, b) => a[0] - b[0])
136
+ .map(([depth, count]) => ({ depth, count }));
137
+ return { depthDist, longestPaths };
138
+ }
139
+
140
+ function computeTemporal(allFiles, nowMs) {
141
+ let oldest = null,
142
+ newest = null;
143
+ const ageBuckets = [
144
+ { label: '> 1 year', minDays: 365, maxDays: Infinity, count: 0, bytes: 0 },
145
+ { label: '6–12 months', minDays: 180, maxDays: 365, count: 0, bytes: 0 },
146
+ { label: '1–6 months', minDays: 30, maxDays: 180, count: 0, bytes: 0 },
147
+ { label: '7–30 days', minDays: 7, maxDays: 30, count: 0, bytes: 0 },
148
+ { label: '1–7 days', minDays: 1, maxDays: 7, count: 0, bytes: 0 },
149
+ { label: '< 1 day', minDays: 0, maxDays: 1, count: 0, bytes: 0 },
150
+ ];
151
+ for (const f of allFiles) {
152
+ const ageDays = Math.max(0, (nowMs - (f.mtimeMs || nowMs)) / (24 * 60 * 60 * 1000));
153
+ for (const b of ageBuckets) {
154
+ if (ageDays >= b.minDays && ageDays < b.maxDays) {
155
+ b.count++;
156
+ b.bytes += f.size;
157
+ break;
158
+ }
159
+ }
160
+ if (!oldest || f.mtimeMs < oldest.mtimeMs) oldest = f;
161
+ if (!newest || f.mtimeMs > newest.mtimeMs) newest = f;
162
+ }
163
+ return {
164
+ oldest: oldest
165
+ ? { path: oldest.path, mtime: oldest.mtimeMs ? new Date(oldest.mtimeMs).toISOString() : null }
166
+ : null,
167
+ newest: newest
168
+ ? { path: newest.path, mtime: newest.mtimeMs ? new Date(newest.mtimeMs).toISOString() : null }
169
+ : null,
170
+ ageBuckets,
171
+ };
172
+ }
173
+
174
+ function computeQuality(allFiles, textFiles) {
175
+ const zeroByteFiles = allFiles.filter((f) => f.size === 0).length;
176
+ const emptyTextFiles = textFiles.filter(
177
+ (f) => (f.size || 0) === 0 || (f.lines || 0) === 0,
178
+ ).length;
179
+ const hiddenFiles = allFiles.filter((f) => f.hidden).length;
180
+ const symlinks = allFiles.filter((f) => f.isSymlink).length;
181
+ const largeThreshold = 50 * MB;
182
+ const suspiciousThreshold = 100 * MB;
183
+ const largeFilesCount = allFiles.filter((f) => f.size >= largeThreshold).length;
184
+ const suspiciousLargeFilesCount = allFiles.filter((f) => f.size >= suspiciousThreshold).length;
185
+ return {
186
+ zeroByteFiles,
187
+ emptyTextFiles,
188
+ hiddenFiles,
189
+ symlinks,
190
+ largeFilesCount,
191
+ suspiciousLargeFilesCount,
192
+ largeThreshold,
193
+ };
194
+ }
195
+
196
+ function computeDuplicates(allFiles, textFiles) {
197
+ const duplicatesBySize = new Map();
198
+ for (const f of allFiles) {
199
+ const key = String(f.size);
200
+ const arr = duplicatesBySize.get(key) || [];
201
+ arr.push(f);
202
+ duplicatesBySize.set(key, arr);
203
+ }
204
+ const duplicateCandidates = [];
205
+ for (const [sizeKey, arr] of duplicatesBySize.entries()) {
206
+ if (arr.length < 2) continue;
207
+ const textGroup = arr.filter((f) => !f.isBinary);
208
+ const otherGroup = arr.filter((f) => f.isBinary);
209
+ const contentHashGroups = new Map();
210
+ for (const tf of textGroup) {
211
+ try {
212
+ const src = textFiles.find((x) => x.absolutePath === tf.absolutePath);
213
+ const content = src ? src.content : '';
214
+ const h = crypto.createHash('sha1').update(content).digest('hex');
215
+ const g = contentHashGroups.get(h) || [];
216
+ g.push(tf);
217
+ contentHashGroups.set(h, g);
218
+ } catch {
219
+ /* ignore hashing errors for duplicate detection */
220
+ }
221
+ }
222
+ for (const [_h, g] of contentHashGroups.entries()) {
223
+ if (g.length > 1)
224
+ duplicateCandidates.push({
225
+ reason: 'same-size+text-hash',
226
+ size: Number(sizeKey),
227
+ count: g.length,
228
+ files: g.map((f) => f.path),
229
+ });
230
+ }
231
+ if (otherGroup.length > 1) {
232
+ duplicateCandidates.push({
233
+ reason: 'same-size',
234
+ size: Number(sizeKey),
235
+ count: otherGroup.length,
236
+ files: otherGroup.map((f) => f.path),
237
+ });
238
+ }
239
+ }
240
+ return duplicateCandidates;
241
+ }
242
+
243
+ function estimateCompressibility(textFiles) {
244
+ let compSampleBytes = 0;
245
+ let compCompressedBytes = 0;
246
+ for (const tf of textFiles) {
247
+ try {
248
+ const sampleLen = Math.min(256 * 1024, tf.size || 0);
249
+ if (sampleLen <= 0) continue;
250
+ const sample = tf.content.slice(0, sampleLen);
251
+ const gz = zlib.gzipSync(Buffer.from(sample, 'utf8'));
252
+ compSampleBytes += sampleLen;
253
+ compCompressedBytes += gz.length;
254
+ } catch {
255
+ /* ignore compression errors during sampling */
256
+ }
257
+ }
258
+ return compSampleBytes > 0 ? compCompressedBytes / compSampleBytes : null;
259
+ }
260
+
261
+ function computeGitInfo(allFiles, rootDir, largeThreshold) {
262
+ const info = {
263
+ isRepo: false,
264
+ trackedCount: 0,
265
+ trackedBytes: 0,
266
+ untrackedCount: 0,
267
+ untrackedBytes: 0,
268
+ lfsCandidates: [],
269
+ };
270
+ try {
271
+ if (!rootDir) return info;
272
+ const top = cp
273
+ .execFileSync('git', ['rev-parse', '--show-toplevel'], {
274
+ cwd: rootDir,
275
+ stdio: ['ignore', 'pipe', 'ignore'],
276
+ })
277
+ .toString()
278
+ .trim();
279
+ if (!top) return info;
280
+ info.isRepo = true;
281
+ const out = cp.execFileSync('git', ['ls-files', '-z'], {
282
+ cwd: rootDir,
283
+ stdio: ['ignore', 'pipe', 'ignore'],
284
+ });
285
+ const tracked = new Set(out.toString().split('\0').filter(Boolean));
286
+ let trackedBytes = 0,
287
+ trackedCount = 0,
288
+ untrackedBytes = 0,
289
+ untrackedCount = 0;
290
+ const lfsCandidates = [];
291
+ for (const f of allFiles) {
292
+ const isTracked = tracked.has(f.path);
293
+ if (isTracked) {
294
+ trackedCount++;
295
+ trackedBytes += f.size;
296
+ if (f.size >= largeThreshold) lfsCandidates.push({ path: f.path, size: f.size });
297
+ } else {
298
+ untrackedCount++;
299
+ untrackedBytes += f.size;
300
+ }
301
+ }
302
+ info.trackedCount = trackedCount;
303
+ info.trackedBytes = trackedBytes;
304
+ info.untrackedCount = untrackedCount;
305
+ info.untrackedBytes = untrackedBytes;
306
+ info.lfsCandidates = lfsCandidates.sort((a, b) => b.size - a.size).slice(0, 50);
307
+ } catch {
308
+ /* git not available or not a repo, ignore */
309
+ }
310
+ return info;
311
+ }
312
+
313
+ function computeLargestFiles(allFiles, totalBytes) {
314
+ const toPct = (num, den) => (den === 0 ? 0 : (num / den) * 100);
315
+ return [...allFiles]
316
+ .sort((a, b) => b.size - a.size)
317
+ .slice(0, 50)
318
+ .map((f) => ({
319
+ path: f.path,
320
+ size: f.size,
321
+ sizeFormatted: formatSize(f.size),
322
+ percentOfTotal: toPct(f.size, totalBytes),
323
+ ext: f.ext || '',
324
+ isBinary: f.isBinary,
325
+ mtime: f.mtimeMs ? new Date(f.mtimeMs).toISOString() : null,
326
+ }));
327
+ }
328
+
329
+ function mdTable(rows, headers) {
330
+ const header = `| ${headers.join(' | ')} |`;
331
+ const sep = `| ${headers.map(() => '---').join(' | ')} |`;
332
+ const body = rows.map((r) => `| ${r.join(' | ')} |`).join('\n');
333
+ return `${header}\n${sep}\n${body}`;
334
+ }
335
+
336
+ function buildMarkdownReport(largestFiles, byExtensionArr, byDirectoryArr, totalBytes) {
337
+ const toPct = (num, den) => (den === 0 ? 0 : (num / den) * 100);
338
+ const md = [];
339
+ md.push(
340
+ '\n### Top Largest Files (Top 50)\n',
341
+ mdTable(
342
+ largestFiles.map((f) => [
343
+ f.path,
344
+ f.sizeFormatted,
345
+ `${f.percentOfTotal.toFixed(2)}%`,
346
+ f.ext || '',
347
+ f.isBinary ? 'binary' : 'text',
348
+ ]),
349
+ ['Path', 'Size', '% of total', 'Ext', 'Type'],
350
+ ),
351
+ '\n\n### Top Extensions by Bytes (Top 20)\n',
352
+ );
353
+ const topExtRows = byExtensionArr
354
+ .slice(0, 20)
355
+ .map((e) => [
356
+ e.ext,
357
+ String(e.count),
358
+ formatSize(e.bytes),
359
+ `${toPct(e.bytes, totalBytes).toFixed(2)}%`,
360
+ ]);
361
+ md.push(
362
+ mdTable(topExtRows, ['Ext', 'Count', 'Bytes', '% of total']),
363
+ '\n\n### Top Directories by Bytes (Top 20)\n',
364
+ );
365
+ const topDirRows = byDirectoryArr
366
+ .slice(0, 20)
367
+ .map((d) => [
368
+ d.dir,
369
+ String(d.count),
370
+ formatSize(d.bytes),
371
+ `${toPct(d.bytes, totalBytes).toFixed(2)}%`,
372
+ ]);
373
+ md.push(mdTable(topDirRows, ['Directory', 'Files', 'Bytes', '% of total']));
374
+ return md.join('\n');
375
+ }
376
+
377
+ module.exports = {
378
+ KB,
379
+ MB,
380
+ formatSize,
381
+ percentile,
382
+ processWithLimit,
383
+ enrichAllFiles,
384
+ buildHistogram,
385
+ aggregateByExtension,
386
+ aggregateByDirectory,
387
+ computeDepthAndLongest,
388
+ computeTemporal,
389
+ computeQuality,
390
+ computeDuplicates,
391
+ estimateCompressibility,
392
+ computeGitInfo,
393
+ computeLargestFiles,
394
+ buildMarkdownReport,
395
+ };