@zeyue0329/xiaoma-cli 1.0.7 โ†’ 1.0.10

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 (321) hide show
  1. package/.github/FORK_GUIDE.md +106 -0
  2. package/.github/FUNDING.yaml +15 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  5. package/.github/workflows/discord.yaml +26 -0
  6. package/.github/workflows/format-check.yaml +44 -0
  7. package/.github/workflows/manual-release.yaml +174 -0
  8. package/.github/workflows/pr-validation.yaml +55 -0
  9. package/.husky/pre-commit +3 -0
  10. package/.vscode/settings.json +26 -1
  11. package/CHANGELOG.md +686 -0
  12. package/CONTRIBUTING.md +250 -0
  13. package/LICENSE +6 -1
  14. package/common/tasks/create-doc.md +2 -0
  15. package/common/tasks/execute-checklist.md +2 -7
  16. package/common/utils/bmad-doc-template.md +7 -5
  17. package/common/utils/workflow-management.md +2 -0
  18. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +2103 -0
  19. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +1627 -0
  20. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +822 -0
  21. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +8486 -0
  22. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +3210 -0
  23. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +3244 -0
  24. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +317 -0
  25. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +982 -0
  26. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +12854 -0
  27. package/dist/expansion-packs/bmad-creative-writing/agents/beta-reader.txt +921 -0
  28. package/dist/expansion-packs/bmad-creative-writing/agents/book-critic.txt +81 -0
  29. package/dist/expansion-packs/bmad-creative-writing/agents/character-psychologist.txt +886 -0
  30. package/dist/expansion-packs/bmad-creative-writing/agents/cover-designer.txt +85 -0
  31. package/dist/expansion-packs/bmad-creative-writing/agents/dialog-specialist.txt +903 -0
  32. package/dist/expansion-packs/bmad-creative-writing/agents/editor.txt +837 -0
  33. package/dist/expansion-packs/bmad-creative-writing/agents/genre-specialist.txt +989 -0
  34. package/dist/expansion-packs/bmad-creative-writing/agents/narrative-designer.txt +888 -0
  35. package/dist/expansion-packs/bmad-creative-writing/agents/plot-architect.txt +1173 -0
  36. package/dist/expansion-packs/bmad-creative-writing/agents/world-builder.txt +914 -0
  37. package/dist/expansion-packs/bmad-creative-writing/teams/agent-team.txt +6071 -0
  38. package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +2079 -0
  39. package/docs/GUIDING-PRINCIPLES.md +91 -0
  40. package/docs/core-architecture.md +219 -0
  41. package/docs/enhanced-ide-development-workflow.md +248 -0
  42. package/docs/expansion-packs.md +200 -0
  43. package/docs/how-to-contribute-with-pull-requests.md +158 -0
  44. package/docs/user-guide.md +530 -0
  45. package/docs/versioning-and-releases.md +155 -0
  46. package/docs/versions.md +48 -0
  47. package/docs/working-in-the-brownfield.md +597 -0
  48. package/eslint.config.mjs +119 -0
  49. package/expansion-packs/bmad-2d-phaser-game-dev/agent-teams/phaser-2d-nodejs-game-team.yaml +14 -0
  50. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.md +73 -0
  51. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +80 -0
  52. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +66 -0
  53. package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-design-checklist.md +203 -0
  54. package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-story-dod-checklist.md +162 -0
  55. package/expansion-packs/bmad-2d-phaser-game-dev/config.yaml +9 -0
  56. package/expansion-packs/bmad-2d-phaser-game-dev/data/bmad-kb.md +252 -0
  57. package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +649 -0
  58. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/advanced-elicitation.md +112 -0
  59. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/create-game-story.md +218 -0
  60. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/game-design-brainstorming.md +292 -0
  61. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +614 -0
  62. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +357 -0
  63. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +344 -0
  64. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +254 -0
  65. package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +485 -0
  66. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +184 -0
  67. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +176 -0
  68. package/expansion-packs/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +15 -0
  69. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.md +82 -0
  70. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.md +79 -0
  71. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +80 -0
  72. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.md +67 -0
  73. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +393 -0
  74. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +205 -0
  75. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +203 -0
  76. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +126 -0
  77. package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +7 -0
  78. package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +771 -0
  79. package/expansion-packs/bmad-2d-unity-game-dev/data/development-guidelines.md +588 -0
  80. package/expansion-packs/bmad-2d-unity-game-dev/tasks/advanced-elicitation.md +112 -0
  81. package/expansion-packs/bmad-2d-unity-game-dev/tasks/correct-course-game.md +143 -0
  82. package/expansion-packs/bmad-2d-unity-game-dev/tasks/create-game-story.md +186 -0
  83. package/expansion-packs/bmad-2d-unity-game-dev/tasks/game-design-brainstorming.md +292 -0
  84. package/expansion-packs/bmad-2d-unity-game-dev/tasks/validate-game-story.md +202 -0
  85. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +1031 -0
  86. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +357 -0
  87. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +706 -0
  88. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +257 -0
  89. package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +485 -0
  90. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +184 -0
  91. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +176 -0
  92. package/expansion-packs/bmad-creative-writing/README.md +146 -0
  93. package/expansion-packs/bmad-creative-writing/agent-teams/agent-team.yaml +20 -0
  94. package/expansion-packs/bmad-creative-writing/agents/beta-reader.md +94 -0
  95. package/expansion-packs/bmad-creative-writing/agents/book-critic.md +40 -0
  96. package/expansion-packs/bmad-creative-writing/agents/character-psychologist.md +93 -0
  97. package/expansion-packs/bmad-creative-writing/agents/cover-designer.md +46 -0
  98. package/expansion-packs/bmad-creative-writing/agents/dialog-specialist.md +92 -0
  99. package/expansion-packs/bmad-creative-writing/agents/editor.md +93 -0
  100. package/expansion-packs/bmad-creative-writing/agents/genre-specialist.md +95 -0
  101. package/expansion-packs/bmad-creative-writing/agents/narrative-designer.md +93 -0
  102. package/expansion-packs/bmad-creative-writing/agents/plot-architect.md +95 -0
  103. package/expansion-packs/bmad-creative-writing/agents/world-builder.md +94 -0
  104. package/expansion-packs/bmad-creative-writing/checklists/beta-feedback-closure-checklist.md +23 -0
  105. package/expansion-packs/bmad-creative-writing/checklists/character-consistency-checklist.md +23 -0
  106. package/expansion-packs/bmad-creative-writing/checklists/comedic-timing-checklist.md +23 -0
  107. package/expansion-packs/bmad-creative-writing/checklists/cyberpunk-aesthetic-checklist.md +23 -0
  108. package/expansion-packs/bmad-creative-writing/checklists/ebook-formatting-checklist.md +21 -0
  109. package/expansion-packs/bmad-creative-writing/checklists/epic-poetry-meter-checklist.md +23 -0
  110. package/expansion-packs/bmad-creative-writing/checklists/fantasy-magic-system-checklist.md +23 -0
  111. package/expansion-packs/bmad-creative-writing/checklists/foreshadowing-payoff-checklist.md +22 -0
  112. package/expansion-packs/bmad-creative-writing/checklists/genre-tropes-checklist.md +22 -0
  113. package/expansion-packs/bmad-creative-writing/checklists/historical-accuracy-checklist.md +23 -0
  114. package/expansion-packs/bmad-creative-writing/checklists/horror-suspense-checklist.md +23 -0
  115. package/expansion-packs/bmad-creative-writing/checklists/kdp-cover-ready-checklist.md +25 -0
  116. package/expansion-packs/bmad-creative-writing/checklists/line-edit-quality-checklist.md +23 -0
  117. package/expansion-packs/bmad-creative-writing/checklists/marketing-copy-checklist.md +23 -0
  118. package/expansion-packs/bmad-creative-writing/checklists/mystery-clue-trail-checklist.md +23 -0
  119. package/expansion-packs/bmad-creative-writing/checklists/orbital-mechanics-checklist.md +23 -0
  120. package/expansion-packs/bmad-creative-writing/checklists/plot-structure-checklist.md +59 -0
  121. package/expansion-packs/bmad-creative-writing/checklists/publication-readiness-checklist.md +23 -0
  122. package/expansion-packs/bmad-creative-writing/checklists/romance-emotional-beats-checklist.md +23 -0
  123. package/expansion-packs/bmad-creative-writing/checklists/scene-quality-checklist.md +23 -0
  124. package/expansion-packs/bmad-creative-writing/checklists/scifi-technology-plausibility-checklist.md +22 -0
  125. package/expansion-packs/bmad-creative-writing/checklists/sensitivity-representation-checklist.md +23 -0
  126. package/expansion-packs/bmad-creative-writing/checklists/steampunk-gadget-checklist.md +23 -0
  127. package/expansion-packs/bmad-creative-writing/checklists/thriller-pacing-stakes-checklist.md +23 -0
  128. package/expansion-packs/bmad-creative-writing/checklists/timeline-continuity-checklist.md +23 -0
  129. package/expansion-packs/bmad-creative-writing/checklists/world-building-continuity-checklist.md +23 -0
  130. package/expansion-packs/bmad-creative-writing/checklists/ya-appropriateness-checklist.md +23 -0
  131. package/expansion-packs/bmad-creative-writing/config.yaml +12 -0
  132. package/expansion-packs/bmad-creative-writing/data/bmad-kb.md +209 -0
  133. package/expansion-packs/bmad-creative-writing/data/story-structures.md +67 -0
  134. package/expansion-packs/bmad-creative-writing/docs/brief.md +212 -0
  135. package/expansion-packs/bmad-creative-writing/tasks/advanced-elicitation.md +119 -0
  136. package/expansion-packs/bmad-creative-writing/tasks/analyze-reader-feedback.md +23 -0
  137. package/expansion-packs/bmad-creative-writing/tasks/analyze-story-structure.md +67 -0
  138. package/expansion-packs/bmad-creative-writing/tasks/assemble-kdp-package.md +29 -0
  139. package/expansion-packs/bmad-creative-writing/tasks/brainstorm-premise.md +23 -0
  140. package/expansion-packs/bmad-creative-writing/tasks/build-world.md +24 -0
  141. package/expansion-packs/bmad-creative-writing/tasks/character-depth-pass.md +22 -0
  142. package/expansion-packs/bmad-creative-writing/tasks/create-doc.md +103 -0
  143. package/expansion-packs/bmad-creative-writing/tasks/create-draft-section.md +26 -0
  144. package/expansion-packs/bmad-creative-writing/tasks/critical-review.md +26 -0
  145. package/expansion-packs/bmad-creative-writing/tasks/develop-character.md +24 -0
  146. package/expansion-packs/bmad-creative-writing/tasks/execute-checklist.md +88 -0
  147. package/expansion-packs/bmad-creative-writing/tasks/expand-premise.md +23 -0
  148. package/expansion-packs/bmad-creative-writing/tasks/expand-synopsis.md +23 -0
  149. package/expansion-packs/bmad-creative-writing/tasks/final-polish.md +23 -0
  150. package/expansion-packs/bmad-creative-writing/tasks/generate-cover-brief.md +25 -0
  151. package/expansion-packs/bmad-creative-writing/tasks/generate-cover-prompts.md +26 -0
  152. package/expansion-packs/bmad-creative-writing/tasks/generate-scene-list.md +23 -0
  153. package/expansion-packs/bmad-creative-writing/tasks/incorporate-feedback.md +25 -0
  154. package/expansion-packs/bmad-creative-writing/tasks/outline-scenes.md +23 -0
  155. package/expansion-packs/bmad-creative-writing/tasks/provide-feedback.md +24 -0
  156. package/expansion-packs/bmad-creative-writing/tasks/publish-chapter.md +23 -0
  157. package/expansion-packs/bmad-creative-writing/tasks/quick-feedback.md +22 -0
  158. package/expansion-packs/bmad-creative-writing/tasks/select-next-arc.md +23 -0
  159. package/expansion-packs/bmad-creative-writing/tasks/workshop-dialog.md +64 -0
  160. package/expansion-packs/bmad-creative-writing/templates/beta-feedback-form.yaml +97 -0
  161. package/expansion-packs/bmad-creative-writing/templates/chapter-draft-tmpl.yaml +82 -0
  162. package/expansion-packs/bmad-creative-writing/templates/character-profile-tmpl.yaml +92 -0
  163. package/expansion-packs/bmad-creative-writing/templates/cover-design-brief-tmpl.yaml +98 -0
  164. package/expansion-packs/bmad-creative-writing/templates/premise-brief-tmpl.yaml +78 -0
  165. package/expansion-packs/bmad-creative-writing/templates/scene-list-tmpl.yaml +55 -0
  166. package/expansion-packs/bmad-creative-writing/templates/story-outline-tmpl.yaml +96 -0
  167. package/expansion-packs/bmad-creative-writing/templates/world-guide-tmpl.yaml +89 -0
  168. package/expansion-packs/bmad-creative-writing/workflows/book-cover-design-workflow.md +218 -0
  169. package/expansion-packs/bmad-creative-writing/workflows/novel-greenfield-workflow.yaml +56 -0
  170. package/expansion-packs/bmad-creative-writing/workflows/novel-serial-workflow.yaml +50 -0
  171. package/expansion-packs/bmad-creative-writing/workflows/novel-snowflake-workflow.yaml +69 -0
  172. package/expansion-packs/bmad-creative-writing/workflows/novel-writing.yaml +91 -0
  173. package/expansion-packs/bmad-creative-writing/workflows/screenplay-development.yaml +85 -0
  174. package/expansion-packs/bmad-creative-writing/workflows/series-planning.yaml +78 -0
  175. package/expansion-packs/bmad-creative-writing/workflows/short-story-creation.yaml +64 -0
  176. package/expansion-packs/bmad-infrastructure-devops/README.md +147 -0
  177. package/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.md +73 -0
  178. package/expansion-packs/bmad-infrastructure-devops/checklists/infrastructure-checklist.md +486 -0
  179. package/expansion-packs/bmad-infrastructure-devops/config.yaml +10 -0
  180. package/expansion-packs/bmad-infrastructure-devops/data/bmad-kb.md +307 -0
  181. package/expansion-packs/bmad-infrastructure-devops/tasks/review-infrastructure.md +161 -0
  182. package/expansion-packs/bmad-infrastructure-devops/tasks/validate-infrastructure.md +155 -0
  183. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +425 -0
  184. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +630 -0
  185. package/implement-fork-friendly-ci.sh +229 -0
  186. package/package.json +75 -45
  187. package/prettier.config.mjs +32 -0
  188. package/test.md +1 -0
  189. package/tools/builders/web-builder.js +143 -149
  190. package/tools/bump-all-versions.js +42 -33
  191. package/tools/bump-expansion-version.js +23 -16
  192. package/tools/cli.js +15 -15
  193. package/tools/flattener/aggregate.js +76 -0
  194. package/tools/flattener/binary.js +80 -0
  195. package/tools/flattener/discovery.js +71 -0
  196. package/tools/flattener/files.js +35 -0
  197. package/tools/flattener/ignoreRules.js +176 -0
  198. package/tools/flattener/main.js +458 -460
  199. package/tools/flattener/projectRoot.js +206 -0
  200. package/tools/flattener/prompts.js +44 -0
  201. package/tools/flattener/stats.helpers.js +395 -0
  202. package/tools/flattener/stats.js +80 -0
  203. package/tools/flattener/test-matrix.js +413 -0
  204. package/tools/flattener/xml.js +88 -0
  205. package/tools/installer/README.md +1 -1
  206. package/tools/installer/bin/xiaoma.js +392 -99
  207. package/tools/installer/config/ide-agent-config.yaml +1 -1
  208. package/tools/installer/config/install.config.yaml +60 -9
  209. package/tools/installer/lib/config-loader.js +55 -51
  210. package/tools/installer/lib/file-manager.js +92 -114
  211. package/tools/installer/lib/ide-base-setup.js +57 -56
  212. package/tools/installer/lib/ide-setup.js +821 -414
  213. package/tools/installer/lib/installer.js +924 -696
  214. package/tools/installer/lib/memory-profiler.js +54 -53
  215. package/tools/installer/lib/module-manager.js +19 -15
  216. package/tools/installer/lib/resource-locator.js +31 -33
  217. package/tools/installer/package.json +24 -23
  218. package/tools/lib/dependency-resolver.js +39 -43
  219. package/tools/lib/yaml-utils.js +7 -7
  220. package/tools/md-assets/web-agent-startup-instructions.md +6 -6
  221. package/tools/preview-release-notes.js +66 -0
  222. package/tools/setup-hooks.sh +37 -0
  223. package/tools/shared/bannerArt.js +105 -0
  224. package/tools/sync-installer-version.js +7 -9
  225. package/tools/sync-version.sh +23 -0
  226. package/tools/update-expansion-version.js +14 -15
  227. package/tools/upgraders/v3-to-v4-upgrader.js +203 -294
  228. package/tools/version-bump.js +41 -26
  229. package/tools/xiaoma-npx-wrapper.js +14 -14
  230. package/tools/yaml-format.js +56 -43
  231. package/xiaoma-core/agent-teams/team-all.yaml +3 -2
  232. package/xiaoma-core/agent-teams/team-fullstack.yaml +2 -1
  233. package/xiaoma-core/agent-teams/team-ide-minimal.yaml +1 -0
  234. package/xiaoma-core/agent-teams/team-no-ui.yaml +2 -1
  235. package/xiaoma-core/agents/analyst.md +20 -17
  236. package/xiaoma-core/agents/architect.md +15 -14
  237. package/xiaoma-core/agents/{xiaoma-master.md โ†’ bmad-master.md} +29 -27
  238. package/xiaoma-core/agents/{xiaoma-orchestrator.md โ†’ bmad-orchestrator.md} +36 -39
  239. package/xiaoma-core/agents/dev.md +23 -18
  240. package/xiaoma-core/agents/pm.md +18 -15
  241. package/xiaoma-core/agents/po.md +13 -10
  242. package/xiaoma-core/agents/qa.md +46 -24
  243. package/xiaoma-core/agents/sm.md +11 -8
  244. package/xiaoma-core/agents/ux-expert.md +10 -7
  245. package/xiaoma-core/checklists/architect-checklist.md +2 -5
  246. package/xiaoma-core/checklists/change-checklist.md +4 -2
  247. package/xiaoma-core/checklists/pm-checklist.md +2 -5
  248. package/xiaoma-core/checklists/po-master-checklist.md +2 -9
  249. package/xiaoma-core/checklists/story-dod-checklist.md +2 -7
  250. package/xiaoma-core/checklists/story-draft-checklist.md +2 -3
  251. package/xiaoma-core/core-config.yaml +4 -1
  252. package/xiaoma-core/data/{xiaoma-kb.md โ†’ bmad-kb.md} +48 -42
  253. package/xiaoma-core/data/brainstorming-techniques.md +2 -0
  254. package/xiaoma-core/data/elicitation-methods.md +22 -0
  255. package/xiaoma-core/data/technical-preferences.md +2 -0
  256. package/xiaoma-core/data/test-levels-framework.md +148 -0
  257. package/xiaoma-core/data/test-priorities-matrix.md +174 -0
  258. package/xiaoma-core/tasks/advanced-elicitation.md +2 -0
  259. package/xiaoma-core/tasks/apply-qa-fixes.md +150 -0
  260. package/xiaoma-core/tasks/brownfield-create-epic.md +2 -0
  261. package/xiaoma-core/tasks/brownfield-create-story.md +2 -0
  262. package/xiaoma-core/tasks/correct-course.md +2 -0
  263. package/xiaoma-core/tasks/create-brownfield-story.md +14 -4
  264. package/xiaoma-core/tasks/create-deep-research-prompt.md +2 -11
  265. package/xiaoma-core/tasks/create-next-story.md +3 -1
  266. package/xiaoma-core/tasks/document-project.md +17 -13
  267. package/xiaoma-core/tasks/facilitate-brainstorming-session.md +5 -3
  268. package/xiaoma-core/tasks/generate-ai-frontend-prompt.md +2 -0
  269. package/xiaoma-core/tasks/index-docs.md +2 -6
  270. package/xiaoma-core/tasks/kb-mode-interaction.md +17 -15
  271. package/xiaoma-core/tasks/nfr-assess.md +345 -0
  272. package/xiaoma-core/tasks/qa-gate.md +163 -0
  273. package/xiaoma-core/tasks/review-story.md +245 -74
  274. package/xiaoma-core/tasks/risk-profile.md +355 -0
  275. package/xiaoma-core/tasks/shard-doc.md +2 -2
  276. package/xiaoma-core/tasks/test-design.md +176 -0
  277. package/xiaoma-core/tasks/trace-requirements.md +266 -0
  278. package/xiaoma-core/tasks/validate-next-story.md +5 -3
  279. package/xiaoma-core/templates/architecture-tmpl.yaml +50 -49
  280. package/xiaoma-core/templates/brainstorming-output-tmpl.yaml +5 -5
  281. package/xiaoma-core/templates/brownfield-architecture-tmpl.yaml +32 -31
  282. package/xiaoma-core/templates/brownfield-prd-tmpl.yaml +14 -13
  283. package/xiaoma-core/templates/competitor-analysis-tmpl.yaml +20 -6
  284. package/xiaoma-core/templates/front-end-architecture-tmpl.yaml +22 -9
  285. package/xiaoma-core/templates/front-end-spec-tmpl.yaml +25 -24
  286. package/xiaoma-core/templates/fullstack-architecture-tmpl.yaml +123 -104
  287. package/xiaoma-core/templates/market-research-tmpl.yaml +3 -2
  288. package/xiaoma-core/templates/prd-tmpl.yaml +10 -9
  289. package/xiaoma-core/templates/project-brief-tmpl.yaml +5 -4
  290. package/xiaoma-core/templates/qa-gate-tmpl.yaml +103 -0
  291. package/xiaoma-core/templates/story-tmpl.yaml +13 -12
  292. package/xiaoma-core/workflows/brownfield-fullstack.yaml +13 -12
  293. package/xiaoma-core/workflows/brownfield-service.yaml +5 -4
  294. package/xiaoma-core/workflows/brownfield-ui.yaml +5 -4
  295. package/xiaoma-core/workflows/greenfield-fullstack.yaml +7 -6
  296. package/xiaoma-core/workflows/greenfield-service.yaml +5 -4
  297. package/xiaoma-core/workflows/greenfield-ui.yaml +6 -5
  298. package/.releaserc.json +0 -18
  299. package/README.md +0 -532
  300. package/dist/agents/analyst.txt +0 -2882
  301. package/dist/agents/architect.txt +0 -3543
  302. package/dist/agents/dev.txt +0 -428
  303. package/dist/agents/pm.txt +0 -2229
  304. package/dist/agents/po.txt +0 -1364
  305. package/dist/agents/qa.txt +0 -386
  306. package/dist/agents/sm.txt +0 -668
  307. package/dist/agents/ux-expert.txt +0 -701
  308. package/dist/agents/xiaoma-master.txt +0 -8756
  309. package/dist/agents/xiaoma-orchestrator.txt +0 -1490
  310. package/dist/teams/team-all.txt +0 -11062
  311. package/dist/teams/team-fullstack.txt +0 -10392
  312. package/dist/teams/team-ide-minimal.txt +0 -3507
  313. package/dist/teams/team-no-ui.txt +0 -8951
  314. package/docs/quick-start.md +0 -179
  315. package/tools/bmad-npx-wrapper.js +0 -39
  316. package/tools/installer/package-lock.json +0 -704
  317. package/tools/semantic-release-sync-installer.js +0 -30
  318. package/xiaoma-core/bmad-core/user-guide.md +0 -0
  319. package/xiaoma-core/enhanced-ide-development-workflow.md +0 -43
  320. package/xiaoma-core/user-guide.md +0 -251
  321. package/xiaoma-core/working-in-the-brownfield.md +0 -364
@@ -1,219 +1,32 @@
1
- #!/usr/bin/env node
2
-
3
1
  const { Command } = require('commander');
4
2
  const fs = require('fs-extra');
5
3
  const path = require('node:path');
6
- const { glob } = require('glob');
7
- const { minimatch } = require('minimatch');
4
+ const process = require('node:process');
5
+
6
+ // Modularized components
7
+ const { findProjectRoot } = require('./projectRoot.js');
8
+ const { promptYesNo, promptPath } = require('./prompts.js');
9
+ const { discoverFiles, filterFiles, aggregateFileContents } = require('./files.js');
10
+ const { generateXMLOutput } = require('./xml.js');
11
+ const { calculateStatistics } = require('./stats.js');
8
12
 
9
13
  /**
10
14
  * Recursively discover all files in a directory
11
15
  * @param {string} rootDir - The root directory to scan
12
16
  * @returns {Promise<string[]>} Array of file paths
13
17
  */
14
- async function discoverFiles(rootDir) {
15
- try {
16
- const gitignorePath = path.join(rootDir, '.gitignore');
17
- const gitignorePatterns = await parseGitignore(gitignorePath);
18
-
19
- // Common gitignore patterns that should always be ignored
20
- const commonIgnorePatterns = [
21
- // Version control
22
- '.git/**',
23
- '.svn/**',
24
- '.hg/**',
25
- '.bzr/**',
26
-
27
- // Dependencies
28
- 'node_modules/**',
29
- 'bower_components/**',
30
- 'vendor/**',
31
- 'packages/**',
32
-
33
- // Build outputs
34
- 'build/**',
35
- 'dist/**',
36
- 'out/**',
37
- 'target/**',
38
- 'bin/**',
39
- 'obj/**',
40
- 'release/**',
41
- 'debug/**',
42
-
43
- // Environment and config
44
- '.env',
45
- '.env.*',
46
- '*.env',
47
- '.config',
48
-
49
- // Logs
50
- 'logs/**',
51
- '*.log',
52
- 'npm-debug.log*',
53
- 'yarn-debug.log*',
54
- 'yarn-error.log*',
55
- 'lerna-debug.log*',
56
-
57
- // Coverage and testing
58
- 'coverage/**',
59
- '.nyc_output/**',
60
- '.coverage/**',
61
- 'test-results/**',
62
- 'junit.xml',
63
-
64
- // Cache directories
65
- '.cache/**',
66
- '.tmp/**',
67
- '.temp/**',
68
- 'tmp/**',
69
- 'temp/**',
70
- '.sass-cache/**',
71
- '.eslintcache',
72
- '.stylelintcache',
73
-
74
- // OS generated files
75
- '.DS_Store',
76
- '.DS_Store?',
77
- '._*',
78
- '.Spotlight-V100',
79
- '.Trashes',
80
- 'ehthumbs.db',
81
- 'Thumbs.db',
82
- 'desktop.ini',
83
-
84
- // IDE and editor files
85
- '.vscode/**',
86
- '.idea/**',
87
- '*.swp',
88
- '*.swo',
89
- '*~',
90
- '.project',
91
- '.classpath',
92
- '.settings/**',
93
- '*.sublime-project',
94
- '*.sublime-workspace',
95
-
96
- // Package manager files
97
- 'package-lock.json',
98
- 'yarn.lock',
99
- 'pnpm-lock.yaml',
100
- 'composer.lock',
101
- 'Pipfile.lock',
102
-
103
- // Runtime and compiled files
104
- '*.pyc',
105
- '*.pyo',
106
- '*.pyd',
107
- '__pycache__/**',
108
- '*.class',
109
- '*.jar',
110
- '*.war',
111
- '*.ear',
112
- '*.o',
113
- '*.so',
114
- '*.dll',
115
- '*.exe',
116
-
117
- // Documentation build
118
- '_site/**',
119
- '.jekyll-cache/**',
120
- '.jekyll-metadata',
121
-
122
- // Flattener specific outputs
123
- 'flattened-codebase.xml',
124
- 'repomix-output.xml'
125
- ];
126
-
127
- const combinedIgnores = [
128
- ...gitignorePatterns,
129
- ...commonIgnorePatterns
130
- ];
131
-
132
- // Use glob to recursively find all files, excluding common ignore patterns
133
- const files = await glob('**/*', {
134
- cwd: rootDir,
135
- nodir: true, // Only files, not directories
136
- dot: true, // Include hidden files
137
- follow: false, // Don't follow symbolic links
138
- ignore: combinedIgnores
139
- });
140
-
141
- return files.map(file => path.resolve(rootDir, file));
142
- } catch (error) {
143
- console.error('Error discovering files:', error.message);
144
- return [];
145
- }
146
- }
147
18
 
148
19
  /**
149
20
  * Parse .gitignore file and return ignore patterns
150
21
  * @param {string} gitignorePath - Path to .gitignore file
151
22
  * @returns {Promise<string[]>} Array of ignore patterns
152
23
  */
153
- async function parseGitignore(gitignorePath) {
154
- try {
155
- if (!await fs.pathExists(gitignorePath)) {
156
- return [];
157
- }
158
-
159
- const content = await fs.readFile(gitignorePath, 'utf8');
160
- return content
161
- .split('\n')
162
- .map(line => line.trim())
163
- .filter(line => line && !line.startsWith('#')) // Remove empty lines and comments
164
- .map(pattern => {
165
- // Convert gitignore patterns to glob patterns
166
- if (pattern.endsWith('/')) {
167
- return pattern + '**';
168
- }
169
- return pattern;
170
- });
171
- } catch (error) {
172
- console.error('Error parsing .gitignore:', error.message);
173
- return [];
174
- }
175
- }
176
24
 
177
25
  /**
178
26
  * Check if a file is binary using file command and heuristics
179
27
  * @param {string} filePath - Path to the file
180
28
  * @returns {Promise<boolean>} True if file is binary
181
29
  */
182
- async function isBinaryFile(filePath) {
183
- try {
184
- // First check by file extension
185
- const binaryExtensions = [
186
- '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg',
187
- '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
188
- '.zip', '.tar', '.gz', '.rar', '.7z',
189
- '.exe', '.dll', '.so', '.dylib',
190
- '.mp3', '.mp4', '.avi', '.mov', '.wav',
191
- '.ttf', '.otf', '.woff', '.woff2',
192
- '.bin', '.dat', '.db', '.sqlite'
193
- ];
194
-
195
- const ext = path.extname(filePath).toLowerCase();
196
- if (binaryExtensions.includes(ext)) {
197
- return true;
198
- }
199
-
200
- // For files without clear extensions, try to read a small sample
201
- const stats = await fs.stat(filePath);
202
- if (stats.size === 0) {
203
- return false; // Empty files are considered text
204
- }
205
-
206
- // Read first 1024 bytes to check for null bytes
207
- const sampleSize = Math.min(1024, stats.size);
208
- const buffer = await fs.readFile(filePath, { encoding: null, flag: 'r' });
209
- const sample = buffer.slice(0, sampleSize);
210
- // If we find null bytes, it's likely binary
211
- return sample.includes(0);
212
- } catch (error) {
213
- console.warn(`Warning: Could not determine if file is binary: ${filePath} - ${error.message}`);
214
- return false; // Default to text if we can't determine
215
- }
216
- }
217
30
 
218
31
  /**
219
32
  * Read and aggregate content from text files
@@ -222,68 +35,6 @@ async function isBinaryFile(filePath) {
222
35
  * @param {Object} spinner - Optional spinner instance for progress display
223
36
  * @returns {Promise<Object>} Object containing file contents and metadata
224
37
  */
225
- async function aggregateFileContents(files, rootDir, spinner = null) {
226
- const results = {
227
- textFiles: [],
228
- binaryFiles: [],
229
- errors: [],
230
- totalFiles: files.length,
231
- processedFiles: 0
232
- };
233
-
234
- for (const filePath of files) {
235
- try {
236
- const relativePath = path.relative(rootDir, filePath);
237
-
238
- // Update progress indicator
239
- if (spinner) {
240
- spinner.text = `Processing file ${results.processedFiles + 1}/${results.totalFiles}: ${relativePath}`;
241
- }
242
-
243
- const isBinary = await isBinaryFile(filePath);
244
-
245
- if (isBinary) {
246
- results.binaryFiles.push({
247
- path: relativePath,
248
- absolutePath: filePath,
249
- size: (await fs.stat(filePath)).size
250
- });
251
- } else {
252
- // Read text file content
253
- const content = await fs.readFile(filePath, 'utf8');
254
- results.textFiles.push({
255
- path: relativePath,
256
- absolutePath: filePath,
257
- content: content,
258
- size: content.length,
259
- lines: content.split('\n').length
260
- });
261
- }
262
-
263
- results.processedFiles++;
264
- } catch (error) {
265
- const relativePath = path.relative(rootDir, filePath);
266
- const errorInfo = {
267
- path: relativePath,
268
- absolutePath: filePath,
269
- error: error.message
270
- };
271
-
272
- results.errors.push(errorInfo);
273
-
274
- // Log warning without interfering with spinner
275
- if (spinner) {
276
- spinner.warn(`Warning: Could not read file ${relativePath}: ${error.message}`);
277
- } else {
278
- console.warn(`Warning: Could not read file ${relativePath}: ${error.message}`);
279
- }
280
-
281
- results.processedFiles++;
282
- }
283
- }
284
-
285
- return results;
286
- }
287
38
 
288
39
  /**
289
40
  * Generate XML output with aggregated file contents using streaming
@@ -291,111 +42,6 @@ async function aggregateFileContents(files, rootDir, spinner = null) {
291
42
  * @param {string} outputPath - The output file path
292
43
  * @returns {Promise<void>} Promise that resolves when writing is complete
293
44
  */
294
- async function generateXMLOutput(aggregatedContent, outputPath) {
295
- const { textFiles } = aggregatedContent;
296
-
297
- // Create write stream for efficient memory usage
298
- const writeStream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
299
-
300
- return new Promise((resolve, reject) => {
301
- writeStream.on('error', reject);
302
- writeStream.on('finish', resolve);
303
-
304
- // Write XML header
305
- writeStream.write('<?xml version="1.0" encoding="UTF-8"?>\n');
306
- writeStream.write('<files>\n');
307
-
308
- // Process files one by one to minimize memory usage
309
- let fileIndex = 0;
310
-
311
- const writeNextFile = () => {
312
- if (fileIndex >= textFiles.length) {
313
- // All files processed, close XML and stream
314
- writeStream.write('</files>\n');
315
- writeStream.end();
316
- return;
317
- }
318
-
319
- const file = textFiles[fileIndex];
320
- fileIndex++;
321
-
322
- // Write file opening tag
323
- writeStream.write(` <file path="${escapeXml(file.path)}">`);
324
-
325
- // Use CDATA for code content, handling CDATA end sequences properly
326
- if (file.content?.trim()) {
327
- const indentedContent = indentFileContent(file.content);
328
- if (file.content.includes(']]>')) {
329
- // If content contains ]]>, split it and wrap each part in CDATA
330
- writeStream.write(splitAndWrapCDATA(indentedContent));
331
- } else {
332
- writeStream.write(`<![CDATA[\n${indentedContent}\n ]]>`);
333
- }
334
- } else if (file.content) {
335
- // Handle empty or whitespace-only content
336
- const indentedContent = indentFileContent(file.content);
337
- writeStream.write(`<![CDATA[\n${indentedContent}\n ]]>`);
338
- }
339
-
340
- // Write file closing tag
341
- writeStream.write('</file>\n');
342
-
343
- // Continue with next file on next tick to avoid stack overflow
344
- setImmediate(writeNextFile);
345
- };
346
-
347
- // Start processing files
348
- writeNextFile();
349
- });
350
- }
351
-
352
- /**
353
- * Escape XML special characters for attributes
354
- * @param {string} str - String to escape
355
- * @returns {string} Escaped string
356
- */
357
- function escapeXml(str) {
358
- if (typeof str !== 'string') {
359
- return String(str);
360
- }
361
- return str
362
- .replace(/&/g, '&amp;')
363
- .replace(/</g, '&lt;')
364
- .replace(/>/g, '&gt;')
365
- .replace(/"/g, '&quot;')
366
- .replace(/'/g, '&apos;');
367
- }
368
-
369
- /**
370
- * Indent file content with 4 spaces for each line
371
- * @param {string} content - Content to indent
372
- * @returns {string} Indented content
373
- */
374
- function indentFileContent(content) {
375
- if (typeof content !== 'string') {
376
- return String(content);
377
- }
378
-
379
- // Split content into lines and add 4 spaces of indentation to each line
380
- return content.split('\n').map(line => ` ${line}`).join('\n');
381
- }
382
-
383
- /**
384
- * Split content containing ]]> and wrap each part in CDATA
385
- * @param {string} content - Content to process
386
- * @returns {string} Content with properly wrapped CDATA sections
387
- */
388
- function splitAndWrapCDATA(content) {
389
- if (typeof content !== 'string') {
390
- return String(content);
391
- }
392
-
393
- // Replace ]]> with ]]]]><![CDATA[> to escape it within CDATA
394
- const escapedContent = content.replace(/]]>/g, ']]]]><![CDATA[>');
395
- return `<![CDATA[
396
- ${escapedContent}
397
- ]]>`;
398
- }
399
45
 
400
46
  /**
401
47
  * Calculate statistics for the processed files
@@ -403,38 +49,6 @@ ${escapedContent}
403
49
  * @param {number} xmlFileSize - The size of the generated XML file in bytes
404
50
  * @returns {Object} Statistics object
405
51
  */
406
- function calculateStatistics(aggregatedContent, xmlFileSize) {
407
- const { textFiles, binaryFiles, errors } = aggregatedContent;
408
-
409
- // Calculate total file size in bytes
410
- const totalTextSize = textFiles.reduce((sum, file) => sum + file.size, 0);
411
- const totalBinarySize = binaryFiles.reduce((sum, file) => sum + file.size, 0);
412
- const totalSize = totalTextSize + totalBinarySize;
413
-
414
- // Calculate total lines of code
415
- const totalLines = textFiles.reduce((sum, file) => sum + file.lines, 0);
416
-
417
- // Estimate token count (rough approximation: 1 token โ‰ˆ 4 characters)
418
- const estimatedTokens = Math.ceil(xmlFileSize / 4);
419
-
420
- // Format file size
421
- const formatSize = (bytes) => {
422
- if (bytes < 1024) return `${bytes} B`;
423
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
424
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
425
- };
426
-
427
- return {
428
- totalFiles: textFiles.length + binaryFiles.length,
429
- textFiles: textFiles.length,
430
- binaryFiles: binaryFiles.length,
431
- errorFiles: errors.length,
432
- totalSize: formatSize(totalSize),
433
- xmlSize: formatSize(xmlFileSize),
434
- totalLines,
435
- estimatedTokens: estimatedTokens.toLocaleString()
436
- };
437
- }
438
52
 
439
53
  /**
440
54
  * Filter files based on .gitignore patterns
@@ -442,72 +56,73 @@ function calculateStatistics(aggregatedContent, xmlFileSize) {
442
56
  * @param {string} rootDir - The root directory
443
57
  * @returns {Promise<string[]>} Filtered array of file paths
444
58
  */
445
- async function filterFiles(files, rootDir) {
446
- const gitignorePath = path.join(rootDir, '.gitignore');
447
- const ignorePatterns = await parseGitignore(gitignorePath);
448
-
449
- if (ignorePatterns.length === 0) {
450
- return files;
451
- }
452
-
453
- // Convert absolute paths to relative for pattern matching
454
- const relativeFiles = files.map(file => path.relative(rootDir, file));
455
59
 
456
- // Separate positive and negative patterns
457
- const positivePatterns = ignorePatterns.filter(p => !p.startsWith('!'));
458
- const negativePatterns = ignorePatterns.filter(p => p.startsWith('!')).map(p => p.slice(1));
459
-
460
- // Filter out files that match ignore patterns
461
- const filteredRelative = [];
462
-
463
- for (const file of relativeFiles) {
464
- let shouldIgnore = false;
465
-
466
- // First check positive patterns (ignore these files)
467
- for (const pattern of positivePatterns) {
468
- if (minimatch(file, pattern)) {
469
- shouldIgnore = true;
470
- break;
471
- }
472
- }
473
-
474
- // Then check negative patterns (don't ignore these files even if they match positive patterns)
475
- if (shouldIgnore) {
476
- for (const pattern of negativePatterns) {
477
- if (minimatch(file, pattern)) {
478
- shouldIgnore = false;
479
- break;
480
- }
481
- }
482
- }
483
-
484
- if (!shouldIgnore) {
485
- filteredRelative.push(file);
486
- }
487
- }
488
-
489
- // Convert back to absolute paths
490
- return filteredRelative.map(file => path.resolve(rootDir, file));
491
- }
60
+ /**
61
+ * Attempt to find the project root by walking up from startDir
62
+ * Looks for common project markers like .git, package.json, pyproject.toml, etc.
63
+ * @param {string} startDir
64
+ * @returns {Promise<string|null>} project root directory or null if not found
65
+ */
492
66
 
493
67
  const program = new Command();
494
68
 
495
69
  program
496
- .name('xiaoma-flatten')
497
- .description('XiaoMa-Method codebase flattener tool')
70
+ .name('bmad-flatten')
71
+ .description('BMAD-METHODโ„ข codebase flattener tool')
498
72
  .version('1.0.0')
499
73
  .option('-i, --input <path>', 'Input directory to flatten', process.cwd())
500
74
  .option('-o, --output <path>', 'Output file path', 'flattened-codebase.xml')
501
75
  .action(async (options) => {
502
- const inputDir = path.resolve(options.input);
503
- const outputPath = path.resolve(options.output);
504
-
505
- console.log(`Flattening codebase from: ${inputDir}`);
506
- console.log(`Output file: ${outputPath}`);
76
+ let inputDir = path.resolve(options.input);
77
+ let outputPath = path.resolve(options.output);
78
+
79
+ // Detect if user explicitly provided -i/--input or -o/--output
80
+ const argv = process.argv.slice(2);
81
+ const userSpecifiedInput = argv.some(
82
+ (a) => a === '-i' || a === '--input' || a.startsWith('--input='),
83
+ );
84
+ const userSpecifiedOutput = argv.some(
85
+ (a) => a === '-o' || a === '--output' || a.startsWith('--output='),
86
+ );
87
+ const noPathArguments = !userSpecifiedInput && !userSpecifiedOutput;
88
+
89
+ if (noPathArguments) {
90
+ const detectedRoot = await findProjectRoot(process.cwd());
91
+ const suggestedOutput = detectedRoot
92
+ ? path.join(detectedRoot, 'flattened-codebase.xml')
93
+ : path.resolve('flattened-codebase.xml');
94
+
95
+ if (detectedRoot) {
96
+ const useDefaults = await promptYesNo(
97
+ `Detected project root at "${detectedRoot}". Use it as input and write output to "${suggestedOutput}"?`,
98
+ true,
99
+ );
100
+ if (useDefaults) {
101
+ inputDir = detectedRoot;
102
+ outputPath = suggestedOutput;
103
+ } else {
104
+ inputDir = await promptPath('Enter input directory path', process.cwd());
105
+ outputPath = await promptPath(
106
+ 'Enter output file path',
107
+ path.join(inputDir, 'flattened-codebase.xml'),
108
+ );
109
+ }
110
+ } else {
111
+ console.log('Could not auto-detect a project root.');
112
+ inputDir = await promptPath('Enter input directory path', process.cwd());
113
+ outputPath = await promptPath(
114
+ 'Enter output file path',
115
+ path.join(inputDir, 'flattened-codebase.xml'),
116
+ );
117
+ }
118
+ }
119
+
120
+ // Ensure output directory exists
121
+ await fs.ensureDir(path.dirname(outputPath));
507
122
 
508
123
  try {
509
124
  // Verify input directory exists
510
- if (!await fs.pathExists(inputDir)) {
125
+ if (!(await fs.pathExists(inputDir))) {
511
126
  console.error(`โŒ Error: Input directory does not exist: ${inputDir}`);
512
127
  process.exit(1);
513
128
  }
@@ -524,18 +139,17 @@ program
524
139
  // Process files with progress tracking
525
140
  console.log('Reading file contents');
526
141
  const processingSpinner = ora('๐Ÿ“„ Processing files...').start();
527
- const aggregatedContent = await aggregateFileContents(filteredFiles, inputDir, processingSpinner);
528
- processingSpinner.succeed(`โœ… Processed ${aggregatedContent.processedFiles}/${filteredFiles.length} files`);
529
-
530
- // Log processing results for test validation
531
- console.log(`Processed ${aggregatedContent.processedFiles}/${filteredFiles.length} files`);
142
+ const aggregatedContent = await aggregateFileContents(
143
+ filteredFiles,
144
+ inputDir,
145
+ processingSpinner,
146
+ );
147
+ processingSpinner.succeed(
148
+ `โœ… Processed ${aggregatedContent.processedFiles}/${filteredFiles.length} files`,
149
+ );
532
150
  if (aggregatedContent.errors.length > 0) {
533
151
  console.log(`Errors: ${aggregatedContent.errors.length}`);
534
152
  }
535
- console.log(`Text files: ${aggregatedContent.textFiles.length}`);
536
- if (aggregatedContent.binaryFiles.length > 0) {
537
- console.log(`Binary files: ${aggregatedContent.binaryFiles.length}`);
538
- }
539
153
 
540
154
  // Generate XML output using streaming
541
155
  const xmlSpinner = ora('๐Ÿ”ง Generating XML output...').start();
@@ -544,18 +158,402 @@ program
544
158
 
545
159
  // Calculate and display statistics
546
160
  const outputStats = await fs.stat(outputPath);
547
- const stats = calculateStatistics(aggregatedContent, outputStats.size);
161
+ const stats = await calculateStatistics(aggregatedContent, outputStats.size, inputDir);
548
162
 
549
163
  // Display completion summary
550
164
  console.log('\n๐Ÿ“Š Completion Summary:');
551
- console.log(`โœ… Successfully processed ${filteredFiles.length} files into ${path.basename(outputPath)}`);
165
+ console.log(
166
+ `โœ… Successfully processed ${filteredFiles.length} files into ${path.basename(outputPath)}`,
167
+ );
552
168
  console.log(`๐Ÿ“ Output file: ${outputPath}`);
553
169
  console.log(`๐Ÿ“ Total source size: ${stats.totalSize}`);
554
170
  console.log(`๐Ÿ“„ Generated XML size: ${stats.xmlSize}`);
555
171
  console.log(`๐Ÿ“ Total lines of code: ${stats.totalLines.toLocaleString()}`);
556
172
  console.log(`๐Ÿ”ข Estimated tokens: ${stats.estimatedTokens}`);
557
- console.log(`๐Ÿ“Š File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`);
173
+ console.log(
174
+ `๐Ÿ“Š File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors\n`,
175
+ );
176
+
177
+ // Ask user if they want detailed stats + markdown report
178
+ const generateDetailed = await promptYesNo(
179
+ 'Generate detailed stats (console + markdown) now?',
180
+ true,
181
+ );
182
+
183
+ if (generateDetailed) {
184
+ // Additional detailed stats
185
+ console.log('\n๐Ÿ“ˆ Size Percentiles:');
186
+ console.log(
187
+ ` Avg: ${Math.round(stats.avgFileSize).toLocaleString()} B, Median: ${Math.round(
188
+ stats.medianFileSize,
189
+ ).toLocaleString()} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
190
+ );
191
+
192
+ if (Array.isArray(stats.histogram) && stats.histogram.length > 0) {
193
+ console.log('\n๐Ÿงฎ Size Histogram:');
194
+ for (const b of stats.histogram.slice(0, 2)) {
195
+ console.log(` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`);
196
+ }
197
+ if (stats.histogram.length > 2) {
198
+ console.log(` โ€ฆ and ${stats.histogram.length - 2} more buckets`);
199
+ }
200
+ }
201
+
202
+ if (Array.isArray(stats.byExtension) && stats.byExtension.length > 0) {
203
+ const topExt = stats.byExtension.slice(0, 2);
204
+ console.log('\n๐Ÿ“ฆ Top Extensions:');
205
+ for (const e of topExt) {
206
+ const pct = stats.totalBytes ? (e.bytes / stats.totalBytes) * 100 : 0;
207
+ console.log(
208
+ ` ${e.ext}: ${e.count} files, ${e.bytes.toLocaleString()} bytes (${pct.toFixed(
209
+ 2,
210
+ )}%)`,
211
+ );
212
+ }
213
+ if (stats.byExtension.length > 2) {
214
+ console.log(` โ€ฆ and ${stats.byExtension.length - 2} more extensions`);
215
+ }
216
+ }
217
+
218
+ if (Array.isArray(stats.byDirectory) && stats.byDirectory.length > 0) {
219
+ const topDir = stats.byDirectory.slice(0, 2);
220
+ console.log('\n๐Ÿ“‚ Top Directories:');
221
+ for (const d of topDir) {
222
+ const pct = stats.totalBytes ? (d.bytes / stats.totalBytes) * 100 : 0;
223
+ console.log(
224
+ ` ${d.dir}: ${d.count} files, ${d.bytes.toLocaleString()} bytes (${pct.toFixed(
225
+ 2,
226
+ )}%)`,
227
+ );
228
+ }
229
+ if (stats.byDirectory.length > 2) {
230
+ console.log(` โ€ฆ and ${stats.byDirectory.length - 2} more directories`);
231
+ }
232
+ }
233
+
234
+ if (Array.isArray(stats.depthDistribution) && stats.depthDistribution.length > 0) {
235
+ console.log('\n๐ŸŒณ Depth Distribution:');
236
+ const dd = stats.depthDistribution.slice(0, 2);
237
+ let line = ' ' + dd.map((d) => `${d.depth}:${d.count}`).join(' ');
238
+ if (stats.depthDistribution.length > 2) {
239
+ line += ` โ€ฆ +${stats.depthDistribution.length - 2} more`;
240
+ }
241
+ console.log(line);
242
+ }
243
+
244
+ if (Array.isArray(stats.longestPaths) && stats.longestPaths.length > 0) {
245
+ console.log('\n๐Ÿงต Longest Paths:');
246
+ for (const p of stats.longestPaths.slice(0, 2)) {
247
+ console.log(` ${p.path} (${p.length} chars, ${p.size.toLocaleString()} bytes)`);
248
+ }
249
+ if (stats.longestPaths.length > 2) {
250
+ console.log(` โ€ฆ and ${stats.longestPaths.length - 2} more paths`);
251
+ }
252
+ }
253
+
254
+ if (stats.temporal) {
255
+ console.log('\nโฑ๏ธ Temporal:');
256
+ if (stats.temporal.oldest) {
257
+ console.log(
258
+ ` Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`,
259
+ );
260
+ }
261
+ if (stats.temporal.newest) {
262
+ console.log(
263
+ ` Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`,
264
+ );
265
+ }
266
+ if (Array.isArray(stats.temporal.ageBuckets)) {
267
+ console.log(' Age buckets:');
268
+ for (const b of stats.temporal.ageBuckets.slice(0, 2)) {
269
+ console.log(` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`);
270
+ }
271
+ if (stats.temporal.ageBuckets.length > 2) {
272
+ console.log(` โ€ฆ and ${stats.temporal.ageBuckets.length - 2} more buckets`);
273
+ }
274
+ }
275
+ }
276
+
277
+ if (stats.quality) {
278
+ console.log('\nโœ… Quality Signals:');
279
+ console.log(` Zero-byte files: ${stats.quality.zeroByteFiles}`);
280
+ console.log(` Empty text files: ${stats.quality.emptyTextFiles}`);
281
+ console.log(` Hidden files: ${stats.quality.hiddenFiles}`);
282
+ console.log(` Symlinks: ${stats.quality.symlinks}`);
283
+ console.log(
284
+ ` Large files (>= ${(stats.quality.largeThreshold / (1024 * 1024)).toFixed(
285
+ 0,
286
+ )} MB): ${stats.quality.largeFilesCount}`,
287
+ );
288
+ console.log(
289
+ ` Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
290
+ );
291
+ }
292
+
293
+ if (Array.isArray(stats.duplicateCandidates) && stats.duplicateCandidates.length > 0) {
294
+ console.log('\n๐Ÿงฌ Duplicate Candidates:');
295
+ for (const d of stats.duplicateCandidates.slice(0, 2)) {
296
+ console.log(` ${d.reason}: ${d.count} files @ ${d.size.toLocaleString()} bytes`);
297
+ }
298
+ if (stats.duplicateCandidates.length > 2) {
299
+ console.log(` โ€ฆ and ${stats.duplicateCandidates.length - 2} more groups`);
300
+ }
301
+ }
302
+
303
+ if (typeof stats.compressibilityRatio === 'number') {
304
+ console.log(
305
+ `\n๐Ÿ—œ๏ธ Compressibility ratio (sampled): ${(stats.compressibilityRatio * 100).toFixed(
306
+ 2,
307
+ )}%`,
308
+ );
309
+ }
558
310
 
311
+ if (stats.git && stats.git.isRepo) {
312
+ console.log('\n๐Ÿ”ง Git:');
313
+ console.log(
314
+ ` Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
315
+ );
316
+ console.log(
317
+ ` Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
318
+ );
319
+ if (Array.isArray(stats.git.lfsCandidates) && stats.git.lfsCandidates.length > 0) {
320
+ console.log(' LFS candidates (top 2):');
321
+ for (const f of stats.git.lfsCandidates.slice(0, 2)) {
322
+ console.log(` ${f.path} (${f.size.toLocaleString()} bytes)`);
323
+ }
324
+ if (stats.git.lfsCandidates.length > 2) {
325
+ console.log(` โ€ฆ and ${stats.git.lfsCandidates.length - 2} more`);
326
+ }
327
+ }
328
+ }
329
+
330
+ if (Array.isArray(stats.largestFiles) && stats.largestFiles.length > 0) {
331
+ console.log('\n๐Ÿ“š Largest Files (top 2):');
332
+ for (const f of stats.largestFiles.slice(0, 2)) {
333
+ // Show LOC for text files when available; omit ext and mtime
334
+ let locStr = '';
335
+ if (!f.isBinary && Array.isArray(aggregatedContent?.textFiles)) {
336
+ const tf = aggregatedContent.textFiles.find((t) => t.path === f.path);
337
+ if (tf && typeof tf.lines === 'number') {
338
+ locStr = `, LOC: ${tf.lines.toLocaleString()}`;
339
+ }
340
+ }
341
+ console.log(
342
+ ` ${f.path} โ€“ ${f.sizeFormatted} (${f.percentOfTotal.toFixed(2)}%)${locStr}`,
343
+ );
344
+ }
345
+ if (stats.largestFiles.length > 2) {
346
+ console.log(` โ€ฆ and ${stats.largestFiles.length - 2} more files`);
347
+ }
348
+ }
349
+
350
+ // Write a comprehensive markdown report next to the XML
351
+ {
352
+ const mdPath = outputPath.endsWith('.xml')
353
+ ? outputPath.replace(/\.xml$/i, '.stats.md')
354
+ : outputPath + '.stats.md';
355
+ try {
356
+ const pct = (num, den) => (den ? (num / den) * 100 : 0);
357
+ const md = [];
358
+ md.push(
359
+ `# ๐Ÿงพ Flatten Stats for ${path.basename(outputPath)}`,
360
+ '',
361
+ '## ๐Ÿ“Š Summary',
362
+ `- Total source size: ${stats.totalSize}`,
363
+ `- Generated XML size: ${stats.xmlSize}`,
364
+ `- Total lines of code: ${stats.totalLines.toLocaleString()}`,
365
+ `- Estimated tokens: ${stats.estimatedTokens}`,
366
+ `- File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`,
367
+ '',
368
+ '## ๐Ÿ“ˆ Size Percentiles',
369
+ `Avg: ${Math.round(stats.avgFileSize).toLocaleString()} B, Median: ${Math.round(
370
+ stats.medianFileSize,
371
+ ).toLocaleString()} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
372
+ '',
373
+ );
374
+
375
+ // Histogram
376
+ if (Array.isArray(stats.histogram) && stats.histogram.length > 0) {
377
+ md.push(
378
+ '## ๐Ÿงฎ Size Histogram',
379
+ '| Bucket | Files | Bytes |',
380
+ '| --- | ---: | ---: |',
381
+ );
382
+ for (const b of stats.histogram) {
383
+ md.push(`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`);
384
+ }
385
+ md.push('');
386
+ }
387
+
388
+ // Top Extensions
389
+ if (Array.isArray(stats.byExtension) && stats.byExtension.length > 0) {
390
+ md.push(
391
+ '## ๐Ÿ“ฆ Top Extensions by Bytes (Top 20)',
392
+ '| Ext | Files | Bytes | % of total |',
393
+ '| --- | ---: | ---: | ---: |',
394
+ );
395
+ for (const e of stats.byExtension.slice(0, 20)) {
396
+ const p = pct(e.bytes, stats.totalBytes);
397
+ md.push(
398
+ `| ${e.ext} | ${e.count} | ${e.bytes.toLocaleString()} | ${p.toFixed(2)}% |`,
399
+ );
400
+ }
401
+ md.push('');
402
+ }
403
+
404
+ // Top Directories
405
+ if (Array.isArray(stats.byDirectory) && stats.byDirectory.length > 0) {
406
+ md.push(
407
+ '## ๐Ÿ“‚ Top Directories by Bytes (Top 20)',
408
+ '| Directory | Files | Bytes | % of total |',
409
+ '| --- | ---: | ---: | ---: |',
410
+ );
411
+ for (const d of stats.byDirectory.slice(0, 20)) {
412
+ const p = pct(d.bytes, stats.totalBytes);
413
+ md.push(
414
+ `| ${d.dir} | ${d.count} | ${d.bytes.toLocaleString()} | ${p.toFixed(2)}% |`,
415
+ );
416
+ }
417
+ md.push('');
418
+ }
419
+
420
+ // Depth distribution
421
+ if (Array.isArray(stats.depthDistribution) && stats.depthDistribution.length > 0) {
422
+ md.push('## ๐ŸŒณ Depth Distribution', '| Depth | Count |', '| ---: | ---: |');
423
+ for (const d of stats.depthDistribution) {
424
+ md.push(`| ${d.depth} | ${d.count} |`);
425
+ }
426
+ md.push('');
427
+ }
428
+
429
+ // Longest paths
430
+ if (Array.isArray(stats.longestPaths) && stats.longestPaths.length > 0) {
431
+ md.push(
432
+ '## ๐Ÿงต Longest Paths (Top 25)',
433
+ '| Path | Length | Bytes |',
434
+ '| --- | ---: | ---: |',
435
+ );
436
+ for (const pth of stats.longestPaths) {
437
+ md.push(`| ${pth.path} | ${pth.length} | ${pth.size.toLocaleString()} |`);
438
+ }
439
+ md.push('');
440
+ }
441
+
442
+ // Temporal
443
+ if (stats.temporal) {
444
+ md.push('## โฑ๏ธ Temporal');
445
+ if (stats.temporal.oldest) {
446
+ md.push(`- Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`);
447
+ }
448
+ if (stats.temporal.newest) {
449
+ md.push(`- Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`);
450
+ }
451
+ if (Array.isArray(stats.temporal.ageBuckets)) {
452
+ md.push('', '| Age | Files | Bytes |', '| --- | ---: | ---: |');
453
+ for (const b of stats.temporal.ageBuckets) {
454
+ md.push(`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`);
455
+ }
456
+ }
457
+ md.push('');
458
+ }
459
+
460
+ // Quality signals
461
+ if (stats.quality) {
462
+ md.push(
463
+ '## โœ… Quality Signals',
464
+ `- Zero-byte files: ${stats.quality.zeroByteFiles}`,
465
+ `- Empty text files: ${stats.quality.emptyTextFiles}`,
466
+ `- Hidden files: ${stats.quality.hiddenFiles}`,
467
+ `- Symlinks: ${stats.quality.symlinks}`,
468
+ `- Large files (>= ${(stats.quality.largeThreshold / (1024 * 1024)).toFixed(0)} MB): ${stats.quality.largeFilesCount}`,
469
+ `- Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
470
+ '',
471
+ );
472
+ }
473
+
474
+ // Duplicates
475
+ if (Array.isArray(stats.duplicateCandidates) && stats.duplicateCandidates.length > 0) {
476
+ md.push(
477
+ '## ๐Ÿงฌ Duplicate Candidates',
478
+ '| Reason | Files | Size (bytes) |',
479
+ '| --- | ---: | ---: |',
480
+ );
481
+ for (const d of stats.duplicateCandidates) {
482
+ md.push(`| ${d.reason} | ${d.count} | ${d.size.toLocaleString()} |`);
483
+ }
484
+ md.push('', '### ๐Ÿงฌ Duplicate Groups Details');
485
+ let dupIndex = 1;
486
+ for (const d of stats.duplicateCandidates) {
487
+ md.push(
488
+ `#### Group ${dupIndex}: ${d.count} files @ ${d.size.toLocaleString()} bytes (${d.reason})`,
489
+ );
490
+ if (Array.isArray(d.files) && d.files.length > 0) {
491
+ for (const fp of d.files) {
492
+ md.push(`- ${fp}`);
493
+ }
494
+ } else {
495
+ md.push('- (file list unavailable)');
496
+ }
497
+ md.push('');
498
+ dupIndex++;
499
+ }
500
+ md.push('');
501
+ }
502
+
503
+ // Compressibility
504
+ if (typeof stats.compressibilityRatio === 'number') {
505
+ md.push(
506
+ '## ๐Ÿ—œ๏ธ Compressibility',
507
+ `Sampled compressibility ratio: ${(stats.compressibilityRatio * 100).toFixed(2)}%`,
508
+ '',
509
+ );
510
+ }
511
+
512
+ // Git
513
+ if (stats.git && stats.git.isRepo) {
514
+ md.push(
515
+ '## ๐Ÿ”ง Git',
516
+ `- Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
517
+ `- Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
518
+ );
519
+ if (Array.isArray(stats.git.lfsCandidates) && stats.git.lfsCandidates.length > 0) {
520
+ md.push('', '### ๐Ÿ“ฆ LFS Candidates (Top 20)', '| Path | Bytes |', '| --- | ---: |');
521
+ for (const f of stats.git.lfsCandidates.slice(0, 20)) {
522
+ md.push(`| ${f.path} | ${f.size.toLocaleString()} |`);
523
+ }
524
+ }
525
+ md.push('');
526
+ }
527
+
528
+ // Largest Files
529
+ if (Array.isArray(stats.largestFiles) && stats.largestFiles.length > 0) {
530
+ md.push(
531
+ '## ๐Ÿ“š Largest Files (Top 50)',
532
+ '| Path | Size | % of total | LOC |',
533
+ '| --- | ---: | ---: | ---: |',
534
+ );
535
+ for (const f of stats.largestFiles) {
536
+ let loc = '';
537
+ if (!f.isBinary && Array.isArray(aggregatedContent?.textFiles)) {
538
+ const tf = aggregatedContent.textFiles.find((t) => t.path === f.path);
539
+ if (tf && typeof tf.lines === 'number') {
540
+ loc = tf.lines.toLocaleString();
541
+ }
542
+ }
543
+ md.push(
544
+ `| ${f.path} | ${f.sizeFormatted} | ${f.percentOfTotal.toFixed(2)}% | ${loc} |`,
545
+ );
546
+ }
547
+ md.push('');
548
+ }
549
+
550
+ await fs.writeFile(mdPath, md.join('\n'));
551
+ console.log(`\n๐Ÿงพ Detailed stats report written to: ${mdPath}`);
552
+ } catch (error) {
553
+ console.warn(`โš ๏ธ Failed to write stats markdown: ${error.message}`);
554
+ }
555
+ }
556
+ }
559
557
  } catch (error) {
560
558
  console.error('โŒ Critical error:', error.message);
561
559
  console.error('An unexpected error occurred.');