bmad-method 4.37.0 โ†’ 4.39.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 (251) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +3 -3
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +3 -3
  3. package/.github/workflows/discord.yaml +11 -2
  4. package/.github/workflows/format-check.yaml +42 -0
  5. package/.github/workflows/manual-release.yaml +173 -0
  6. package/.husky/pre-commit +3 -0
  7. package/.vscode/settings.json +26 -1
  8. package/CHANGELOG.md +2 -23
  9. package/README.md +2 -0
  10. package/bmad-core/agent-teams/team-all.yaml +1 -1
  11. package/bmad-core/agents/analyst.md +16 -15
  12. package/bmad-core/agents/architect.md +11 -11
  13. package/bmad-core/agents/bmad-master.md +23 -22
  14. package/bmad-core/agents/bmad-orchestrator.md +13 -17
  15. package/bmad-core/agents/dev.md +14 -11
  16. package/bmad-core/agents/pm.md +15 -14
  17. package/bmad-core/agents/po.md +9 -8
  18. package/bmad-core/agents/qa.md +42 -22
  19. package/bmad-core/agents/sm.md +7 -6
  20. package/bmad-core/agents/ux-expert.md +6 -5
  21. package/bmad-core/core-config.yaml +2 -0
  22. package/bmad-core/data/bmad-kb.md +1 -1
  23. package/bmad-core/data/test-levels-framework.md +146 -0
  24. package/bmad-core/data/test-priorities-matrix.md +172 -0
  25. package/bmad-core/tasks/apply-qa-fixes.md +148 -0
  26. package/bmad-core/tasks/facilitate-brainstorming-session.md +1 -1
  27. package/bmad-core/tasks/nfr-assess.md +343 -0
  28. package/bmad-core/tasks/qa-gate.md +161 -0
  29. package/bmad-core/tasks/review-story.md +234 -74
  30. package/bmad-core/tasks/risk-profile.md +353 -0
  31. package/bmad-core/tasks/test-design.md +174 -0
  32. package/bmad-core/tasks/trace-requirements.md +264 -0
  33. package/bmad-core/templates/architecture-tmpl.yaml +49 -49
  34. package/bmad-core/templates/brainstorming-output-tmpl.yaml +5 -5
  35. package/bmad-core/templates/brownfield-architecture-tmpl.yaml +31 -31
  36. package/bmad-core/templates/brownfield-prd-tmpl.yaml +13 -13
  37. package/bmad-core/templates/competitor-analysis-tmpl.yaml +19 -6
  38. package/bmad-core/templates/front-end-architecture-tmpl.yaml +21 -9
  39. package/bmad-core/templates/front-end-spec-tmpl.yaml +24 -24
  40. package/bmad-core/templates/fullstack-architecture-tmpl.yaml +122 -104
  41. package/bmad-core/templates/market-research-tmpl.yaml +2 -2
  42. package/bmad-core/templates/prd-tmpl.yaml +9 -9
  43. package/bmad-core/templates/project-brief-tmpl.yaml +4 -4
  44. package/bmad-core/templates/qa-gate-tmpl.yaml +102 -0
  45. package/bmad-core/templates/story-tmpl.yaml +12 -12
  46. package/bmad-core/workflows/brownfield-fullstack.yaml +9 -9
  47. package/bmad-core/workflows/brownfield-service.yaml +1 -1
  48. package/bmad-core/workflows/brownfield-ui.yaml +1 -1
  49. package/bmad-core/workflows/greenfield-fullstack.yaml +1 -1
  50. package/bmad-core/workflows/greenfield-service.yaml +1 -1
  51. package/bmad-core/workflows/greenfield-ui.yaml +1 -1
  52. package/common/utils/bmad-doc-template.md +5 -5
  53. package/dist/agents/analyst.txt +1086 -1079
  54. package/dist/agents/architect.txt +1534 -1526
  55. package/dist/agents/bmad-master.txt +646 -632
  56. package/dist/agents/bmad-orchestrator.txt +40 -18
  57. package/dist/agents/dev.txt +158 -19
  58. package/dist/agents/pm.txt +1082 -1107
  59. package/dist/agents/po.txt +314 -332
  60. package/dist/agents/qa.txt +1754 -151
  61. package/dist/agents/sm.txt +88 -98
  62. package/dist/agents/ux-expert.txt +80 -87
  63. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +109 -146
  64. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +75 -86
  65. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +41 -48
  66. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +1903 -1941
  67. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +15 -50
  68. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +149 -195
  69. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +0 -15
  70. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +20 -37
  71. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +2660 -2752
  72. package/dist/expansion-packs/bmad-creative-writing/agents/beta-reader.txt +871 -0
  73. package/dist/expansion-packs/bmad-creative-writing/agents/book-critic.txt +78 -0
  74. package/dist/expansion-packs/bmad-creative-writing/agents/character-psychologist.txt +839 -0
  75. package/dist/expansion-packs/bmad-creative-writing/agents/cover-designer.txt +85 -0
  76. package/dist/expansion-packs/bmad-creative-writing/agents/dialog-specialist.txt +861 -0
  77. package/dist/expansion-packs/bmad-creative-writing/agents/editor.txt +796 -0
  78. package/dist/expansion-packs/bmad-creative-writing/agents/genre-specialist.txt +927 -0
  79. package/dist/expansion-packs/bmad-creative-writing/agents/narrative-designer.txt +842 -0
  80. package/dist/expansion-packs/bmad-creative-writing/agents/plot-architect.txt +1126 -0
  81. package/dist/expansion-packs/bmad-creative-writing/agents/world-builder.txt +864 -0
  82. package/dist/expansion-packs/bmad-creative-writing/teams/agent-team.txt +5917 -0
  83. package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +25 -27
  84. package/dist/teams/team-all.txt +5541 -3768
  85. package/dist/teams/team-fullstack.txt +3014 -2987
  86. package/dist/teams/team-ide-minimal.txt +2219 -469
  87. package/dist/teams/team-no-ui.txt +2993 -2966
  88. package/docs/enhanced-ide-development-workflow.md +220 -15
  89. package/docs/user-guide.md +271 -18
  90. package/docs/versioning-and-releases.md +122 -44
  91. package/docs/working-in-the-brownfield.md +264 -31
  92. package/eslint.config.mjs +119 -0
  93. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +4 -4
  94. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +1 -1
  95. package/expansion-packs/bmad-2d-phaser-game-dev/config.yaml +1 -1
  96. package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +26 -28
  97. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +50 -50
  98. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +23 -23
  99. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +24 -24
  100. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +42 -42
  101. package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
  102. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +5 -5
  103. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +1 -1
  104. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +3 -3
  105. package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +1 -1
  106. package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +1 -1
  107. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +23 -23
  108. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +63 -63
  109. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +20 -20
  110. package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
  111. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +5 -5
  112. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +1 -1
  113. package/expansion-packs/bmad-creative-writing/README.md +132 -0
  114. package/expansion-packs/bmad-creative-writing/agent-teams/agent-team.yaml +19 -0
  115. package/expansion-packs/bmad-creative-writing/agents/beta-reader.md +91 -0
  116. package/expansion-packs/bmad-creative-writing/agents/book-critic.md +35 -0
  117. package/expansion-packs/bmad-creative-writing/agents/character-psychologist.md +90 -0
  118. package/expansion-packs/bmad-creative-writing/agents/cover-designer.md +41 -0
  119. package/expansion-packs/bmad-creative-writing/agents/dialog-specialist.md +89 -0
  120. package/expansion-packs/bmad-creative-writing/agents/editor.md +90 -0
  121. package/expansion-packs/bmad-creative-writing/agents/genre-specialist.md +92 -0
  122. package/expansion-packs/bmad-creative-writing/agents/narrative-designer.md +90 -0
  123. package/expansion-packs/bmad-creative-writing/agents/plot-architect.md +92 -0
  124. package/expansion-packs/bmad-creative-writing/agents/world-builder.md +91 -0
  125. package/expansion-packs/bmad-creative-writing/checklists/beta-feedback-closure-checklist.md +16 -0
  126. package/expansion-packs/bmad-creative-writing/checklists/character-consistency-checklist.md +16 -0
  127. package/expansion-packs/bmad-creative-writing/checklists/comedic-timing-checklist.md +16 -0
  128. package/expansion-packs/bmad-creative-writing/checklists/cyberpunk-aesthetic-checklist.md +16 -0
  129. package/expansion-packs/bmad-creative-writing/checklists/ebook-formatting-checklist.md +15 -0
  130. package/expansion-packs/bmad-creative-writing/checklists/epic-poetry-meter-checklist.md +16 -0
  131. package/expansion-packs/bmad-creative-writing/checklists/fantasy-magic-system-checklist.md +16 -0
  132. package/expansion-packs/bmad-creative-writing/checklists/foreshadowing-payoff-checklist.md +15 -0
  133. package/expansion-packs/bmad-creative-writing/checklists/genre-tropes-checklist.md +15 -0
  134. package/expansion-packs/bmad-creative-writing/checklists/historical-accuracy-checklist.md +16 -0
  135. package/expansion-packs/bmad-creative-writing/checklists/horror-suspense-checklist.md +16 -0
  136. package/expansion-packs/bmad-creative-writing/checklists/kdp-cover-ready-checklist.md +18 -0
  137. package/expansion-packs/bmad-creative-writing/checklists/line-edit-quality-checklist.md +16 -0
  138. package/expansion-packs/bmad-creative-writing/checklists/marketing-copy-checklist.md +16 -0
  139. package/expansion-packs/bmad-creative-writing/checklists/mystery-clue-trail-checklist.md +16 -0
  140. package/expansion-packs/bmad-creative-writing/checklists/orbital-mechanics-checklist.md +16 -0
  141. package/expansion-packs/bmad-creative-writing/checklists/plot-structure-checklist.md +49 -0
  142. package/expansion-packs/bmad-creative-writing/checklists/publication-readiness-checklist.md +16 -0
  143. package/expansion-packs/bmad-creative-writing/checklists/romance-emotional-beats-checklist.md +16 -0
  144. package/expansion-packs/bmad-creative-writing/checklists/scene-quality-checklist.md +16 -0
  145. package/expansion-packs/bmad-creative-writing/checklists/scifi-technology-plausibility-checklist.md +15 -0
  146. package/expansion-packs/bmad-creative-writing/checklists/sensitivity-representation-checklist.md +16 -0
  147. package/expansion-packs/bmad-creative-writing/checklists/steampunk-gadget-checklist.md +16 -0
  148. package/expansion-packs/bmad-creative-writing/checklists/thriller-pacing-stakes-checklist.md +16 -0
  149. package/expansion-packs/bmad-creative-writing/checklists/timeline-continuity-checklist.md +16 -0
  150. package/expansion-packs/bmad-creative-writing/checklists/world-building-continuity-checklist.md +16 -0
  151. package/expansion-packs/bmad-creative-writing/checklists/ya-appropriateness-checklist.md +16 -0
  152. package/expansion-packs/bmad-creative-writing/config.yaml +11 -0
  153. package/expansion-packs/bmad-creative-writing/data/bmad-kb.md +197 -0
  154. package/expansion-packs/bmad-creative-writing/data/story-structures.md +58 -0
  155. package/expansion-packs/bmad-creative-writing/docs/brief.md +183 -0
  156. package/expansion-packs/bmad-creative-writing/tasks/advanced-elicitation.md +117 -0
  157. package/expansion-packs/bmad-creative-writing/tasks/analyze-reader-feedback.md +16 -0
  158. package/expansion-packs/bmad-creative-writing/tasks/analyze-story-structure.md +55 -0
  159. package/expansion-packs/bmad-creative-writing/tasks/assemble-kdp-package.md +22 -0
  160. package/expansion-packs/bmad-creative-writing/tasks/brainstorm-premise.md +16 -0
  161. package/expansion-packs/bmad-creative-writing/tasks/build-world.md +17 -0
  162. package/expansion-packs/bmad-creative-writing/tasks/character-depth-pass.md +15 -0
  163. package/expansion-packs/bmad-creative-writing/tasks/create-doc.md +101 -0
  164. package/expansion-packs/bmad-creative-writing/tasks/create-draft-section.md +19 -0
  165. package/expansion-packs/bmad-creative-writing/tasks/critical-review.md +19 -0
  166. package/expansion-packs/bmad-creative-writing/tasks/develop-character.md +17 -0
  167. package/expansion-packs/bmad-creative-writing/tasks/execute-checklist.md +93 -0
  168. package/expansion-packs/bmad-creative-writing/tasks/expand-premise.md +16 -0
  169. package/expansion-packs/bmad-creative-writing/tasks/expand-synopsis.md +16 -0
  170. package/expansion-packs/bmad-creative-writing/tasks/final-polish.md +16 -0
  171. package/expansion-packs/bmad-creative-writing/tasks/generate-cover-brief.md +18 -0
  172. package/expansion-packs/bmad-creative-writing/tasks/generate-cover-prompts.md +19 -0
  173. package/expansion-packs/bmad-creative-writing/tasks/generate-scene-list.md +16 -0
  174. package/expansion-packs/bmad-creative-writing/tasks/incorporate-feedback.md +18 -0
  175. package/expansion-packs/bmad-creative-writing/tasks/outline-scenes.md +16 -0
  176. package/expansion-packs/bmad-creative-writing/tasks/provide-feedback.md +17 -0
  177. package/expansion-packs/bmad-creative-writing/tasks/publish-chapter.md +16 -0
  178. package/expansion-packs/bmad-creative-writing/tasks/quick-feedback.md +15 -0
  179. package/expansion-packs/bmad-creative-writing/tasks/select-next-arc.md +16 -0
  180. package/expansion-packs/bmad-creative-writing/tasks/workshop-dialog.md +51 -0
  181. package/expansion-packs/bmad-creative-writing/templates/beta-feedback-form.yaml +96 -0
  182. package/expansion-packs/bmad-creative-writing/templates/chapter-draft-tmpl.yaml +81 -0
  183. package/expansion-packs/bmad-creative-writing/templates/character-profile-tmpl.yaml +92 -0
  184. package/expansion-packs/bmad-creative-writing/templates/cover-design-brief-tmpl.yaml +97 -0
  185. package/expansion-packs/bmad-creative-writing/templates/premise-brief-tmpl.yaml +77 -0
  186. package/expansion-packs/bmad-creative-writing/templates/scene-list-tmpl.yaml +54 -0
  187. package/expansion-packs/bmad-creative-writing/templates/story-outline-tmpl.yaml +96 -0
  188. package/expansion-packs/bmad-creative-writing/templates/world-guide-tmpl.yaml +88 -0
  189. package/expansion-packs/bmad-creative-writing/workflows/book-cover-design-workflow.md +176 -0
  190. package/expansion-packs/bmad-creative-writing/workflows/novel-greenfield-workflow.yaml +58 -0
  191. package/expansion-packs/bmad-creative-writing/workflows/novel-serial-workflow.yaml +51 -0
  192. package/expansion-packs/bmad-creative-writing/workflows/novel-snowflake-workflow.yaml +69 -0
  193. package/expansion-packs/bmad-creative-writing/workflows/novel-writing.yaml +92 -0
  194. package/expansion-packs/bmad-creative-writing/workflows/screenplay-development.yaml +86 -0
  195. package/expansion-packs/bmad-creative-writing/workflows/series-planning.yaml +79 -0
  196. package/expansion-packs/bmad-creative-writing/workflows/short-story-creation.yaml +65 -0
  197. package/expansion-packs/bmad-infrastructure-devops/config.yaml +1 -1
  198. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +20 -20
  199. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +7 -7
  200. package/package.json +62 -39
  201. package/prettier.config.mjs +32 -0
  202. package/sync-version.sh +23 -0
  203. package/tools/bmad-npx-wrapper.js +10 -10
  204. package/tools/builders/web-builder.js +124 -130
  205. package/tools/bump-all-versions.js +42 -33
  206. package/tools/bump-expansion-version.js +23 -16
  207. package/tools/cli.js +10 -12
  208. package/tools/flattener/aggregate.js +10 -10
  209. package/tools/flattener/binary.js +44 -17
  210. package/tools/flattener/discovery.js +19 -18
  211. package/tools/flattener/files.js +6 -6
  212. package/tools/flattener/ignoreRules.js +125 -125
  213. package/tools/flattener/main.js +426 -70
  214. package/tools/flattener/projectRoot.js +186 -25
  215. package/tools/flattener/prompts.js +9 -9
  216. package/tools/flattener/stats.helpers.js +395 -0
  217. package/tools/flattener/stats.js +64 -14
  218. package/tools/flattener/test-matrix.js +413 -0
  219. package/tools/flattener/xml.js +33 -31
  220. package/tools/installer/bin/bmad.js +156 -113
  221. package/tools/installer/config/ide-agent-config.yaml +1 -1
  222. package/tools/installer/config/install.config.yaml +13 -3
  223. package/tools/installer/lib/config-loader.js +46 -42
  224. package/tools/installer/lib/file-manager.js +91 -113
  225. package/tools/installer/lib/ide-base-setup.js +57 -56
  226. package/tools/installer/lib/ide-setup.js +545 -399
  227. package/tools/installer/lib/installer.js +875 -714
  228. package/tools/installer/lib/memory-profiler.js +54 -53
  229. package/tools/installer/lib/module-manager.js +19 -15
  230. package/tools/installer/lib/resource-locator.js +26 -28
  231. package/tools/installer/package.json +19 -19
  232. package/tools/lib/dependency-resolver.js +26 -30
  233. package/tools/lib/yaml-utils.js +7 -7
  234. package/tools/preview-release-notes.js +66 -0
  235. package/tools/shared/bannerArt.js +3 -3
  236. package/tools/sync-installer-version.js +7 -9
  237. package/tools/update-expansion-version.js +14 -15
  238. package/tools/upgraders/v3-to-v4-upgrader.js +203 -294
  239. package/tools/version-bump.js +41 -26
  240. package/tools/yaml-format.js +56 -43
  241. package/.github/workflows/release.yaml +0 -60
  242. package/.releaserc.json +0 -21
  243. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/Complete AI Agent System - Flowchart.svg +0 -102
  244. 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 +0 -13
  245. 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 +0 -13
  246. 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 +0 -25
  247. 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 +0 -34
  248. 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 +0 -70
  249. 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 +0 -26
  250. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/README.md +0 -109
  251. package/tools/semantic-release-sync-installer.js +0 -30
@@ -1,20 +1,14 @@
1
- #!/usr/bin/env node
2
-
3
- const { Command } = require("commander");
4
- const fs = require("fs-extra");
5
- const path = require("node:path");
6
- const process = require("node:process");
1
+ const { Command } = require('commander');
2
+ const fs = require('fs-extra');
3
+ const path = require('node:path');
4
+ const process = require('node:process');
7
5
 
8
6
  // Modularized components
9
- const { findProjectRoot } = require("./projectRoot.js");
10
- const { promptYesNo, promptPath } = require("./prompts.js");
11
- const {
12
- discoverFiles,
13
- filterFiles,
14
- aggregateFileContents,
15
- } = require("./files.js");
16
- const { generateXMLOutput } = require("./xml.js");
17
- const { calculateStatistics } = require("./stats.js");
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');
18
12
 
19
13
  /**
20
14
  * Recursively discover all files in a directory
@@ -73,30 +67,30 @@ const { calculateStatistics } = require("./stats.js");
73
67
  const program = new Command();
74
68
 
75
69
  program
76
- .name("bmad-flatten")
77
- .description("BMad-Method codebase flattener tool")
78
- .version("1.0.0")
79
- .option("-i, --input <path>", "Input directory to flatten", process.cwd())
80
- .option("-o, --output <path>", "Output file path", "flattened-codebase.xml")
70
+ .name('bmad-flatten')
71
+ .description('BMad-Method codebase flattener tool')
72
+ .version('1.0.0')
73
+ .option('-i, --input <path>', 'Input directory to flatten', process.cwd())
74
+ .option('-o, --output <path>', 'Output file path', 'flattened-codebase.xml')
81
75
  .action(async (options) => {
82
76
  let inputDir = path.resolve(options.input);
83
77
  let outputPath = path.resolve(options.output);
84
78
 
85
79
  // Detect if user explicitly provided -i/--input or -o/--output
86
80
  const argv = process.argv.slice(2);
87
- const userSpecifiedInput = argv.some((a) =>
88
- a === "-i" || a === "--input" || a.startsWith("--input=")
81
+ const userSpecifiedInput = argv.some(
82
+ (a) => a === '-i' || a === '--input' || a.startsWith('--input='),
89
83
  );
90
- const userSpecifiedOutput = argv.some((a) =>
91
- a === "-o" || a === "--output" || a.startsWith("--output=")
84
+ const userSpecifiedOutput = argv.some(
85
+ (a) => a === '-o' || a === '--output' || a.startsWith('--output='),
92
86
  );
93
- const noPathArgs = !userSpecifiedInput && !userSpecifiedOutput;
87
+ const noPathArguments = !userSpecifiedInput && !userSpecifiedOutput;
94
88
 
95
- if (noPathArgs) {
89
+ if (noPathArguments) {
96
90
  const detectedRoot = await findProjectRoot(process.cwd());
97
91
  const suggestedOutput = detectedRoot
98
- ? path.join(detectedRoot, "flattened-codebase.xml")
99
- : path.resolve("flattened-codebase.xml");
92
+ ? path.join(detectedRoot, 'flattened-codebase.xml')
93
+ : path.resolve('flattened-codebase.xml');
100
94
 
101
95
  if (detectedRoot) {
102
96
  const useDefaults = await promptYesNo(
@@ -107,29 +101,23 @@ program
107
101
  inputDir = detectedRoot;
108
102
  outputPath = suggestedOutput;
109
103
  } else {
110
- inputDir = await promptPath(
111
- "Enter input directory path",
112
- process.cwd(),
113
- );
104
+ inputDir = await promptPath('Enter input directory path', process.cwd());
114
105
  outputPath = await promptPath(
115
- "Enter output file path",
116
- path.join(inputDir, "flattened-codebase.xml"),
106
+ 'Enter output file path',
107
+ path.join(inputDir, 'flattened-codebase.xml'),
117
108
  );
118
109
  }
119
110
  } else {
120
- console.log("Could not auto-detect a project root.");
121
- inputDir = await promptPath(
122
- "Enter input directory path",
123
- process.cwd(),
124
- );
111
+ console.log('Could not auto-detect a project root.');
112
+ inputDir = await promptPath('Enter input directory path', process.cwd());
125
113
  outputPath = await promptPath(
126
- "Enter output file path",
127
- path.join(inputDir, "flattened-codebase.xml"),
114
+ 'Enter output file path',
115
+ path.join(inputDir, 'flattened-codebase.xml'),
128
116
  );
129
117
  }
130
118
  } else {
131
119
  console.error(
132
- "Could not auto-detect a project root and no arguments were provided. Please specify -i/--input and -o/--output.",
120
+ 'Could not auto-detect a project root and no arguments were provided. Please specify -i/--input and -o/--output.',
133
121
  );
134
122
  process.exit(1);
135
123
  }
@@ -137,30 +125,25 @@ program
137
125
  // Ensure output directory exists
138
126
  await fs.ensureDir(path.dirname(outputPath));
139
127
 
140
- console.log(`Flattening codebase from: ${inputDir}`);
141
- console.log(`Output file: ${outputPath}`);
142
-
143
128
  try {
144
129
  // Verify input directory exists
145
- if (!await fs.pathExists(inputDir)) {
130
+ if (!(await fs.pathExists(inputDir))) {
146
131
  console.error(`โŒ Error: Input directory does not exist: ${inputDir}`);
147
132
  process.exit(1);
148
133
  }
149
134
 
150
135
  // Import ora dynamically
151
- const { default: ora } = await import("ora");
136
+ const { default: ora } = await import('ora');
152
137
 
153
138
  // Start file discovery with spinner
154
- const discoverySpinner = ora("๐Ÿ” Discovering files...").start();
139
+ const discoverySpinner = ora('๐Ÿ” Discovering files...').start();
155
140
  const files = await discoverFiles(inputDir);
156
141
  const filteredFiles = await filterFiles(files, inputDir);
157
- discoverySpinner.succeed(
158
- `๐Ÿ“ Found ${filteredFiles.length} files to include`,
159
- );
142
+ discoverySpinner.succeed(`๐Ÿ“ Found ${filteredFiles.length} files to include`);
160
143
 
161
144
  // Process files with progress tracking
162
- console.log("Reading file contents");
163
- const processingSpinner = ora("๐Ÿ“„ Processing files...").start();
145
+ console.log('Reading file contents');
146
+ const processingSpinner = ora('๐Ÿ“„ Processing files...').start();
164
147
  const aggregatedContent = await aggregateFileContents(
165
148
  filteredFiles,
166
149
  inputDir,
@@ -172,40 +155,413 @@ program
172
155
  if (aggregatedContent.errors.length > 0) {
173
156
  console.log(`Errors: ${aggregatedContent.errors.length}`);
174
157
  }
175
- console.log(`Text files: ${aggregatedContent.textFiles.length}`);
176
- if (aggregatedContent.binaryFiles.length > 0) {
177
- console.log(`Binary files: ${aggregatedContent.binaryFiles.length}`);
178
- }
179
158
 
180
159
  // Generate XML output using streaming
181
- const xmlSpinner = ora("๐Ÿ”ง Generating XML output...").start();
160
+ const xmlSpinner = ora('๐Ÿ”ง Generating XML output...').start();
182
161
  await generateXMLOutput(aggregatedContent, outputPath);
183
- xmlSpinner.succeed("๐Ÿ“ XML generation completed");
162
+ xmlSpinner.succeed('๐Ÿ“ XML generation completed');
184
163
 
185
164
  // Calculate and display statistics
186
165
  const outputStats = await fs.stat(outputPath);
187
- const stats = calculateStatistics(aggregatedContent, outputStats.size);
166
+ const stats = await calculateStatistics(aggregatedContent, outputStats.size, inputDir);
188
167
 
189
168
  // Display completion summary
190
- console.log("\n๐Ÿ“Š Completion Summary:");
169
+ console.log('\n๐Ÿ“Š Completion Summary:');
191
170
  console.log(
192
- `โœ… Successfully processed ${filteredFiles.length} files into ${
193
- path.basename(outputPath)
194
- }`,
171
+ `โœ… Successfully processed ${filteredFiles.length} files into ${path.basename(outputPath)}`,
195
172
  );
196
173
  console.log(`๐Ÿ“ Output file: ${outputPath}`);
197
174
  console.log(`๐Ÿ“ Total source size: ${stats.totalSize}`);
198
175
  console.log(`๐Ÿ“„ Generated XML size: ${stats.xmlSize}`);
199
- console.log(
200
- `๐Ÿ“ Total lines of code: ${stats.totalLines.toLocaleString()}`,
201
- );
176
+ console.log(`๐Ÿ“ Total lines of code: ${stats.totalLines.toLocaleString()}`);
202
177
  console.log(`๐Ÿ”ข Estimated tokens: ${stats.estimatedTokens}`);
203
178
  console.log(
204
- `๐Ÿ“Š File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`,
179
+ `๐Ÿ“Š File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors\n`,
180
+ );
181
+
182
+ // Ask user if they want detailed stats + markdown report
183
+ const generateDetailed = await promptYesNo(
184
+ 'Generate detailed stats (console + markdown) now?',
185
+ true,
205
186
  );
187
+
188
+ if (generateDetailed) {
189
+ // Additional detailed stats
190
+ console.log('\n๐Ÿ“ˆ Size Percentiles:');
191
+ console.log(
192
+ ` Avg: ${Math.round(stats.avgFileSize).toLocaleString()} B, Median: ${Math.round(
193
+ stats.medianFileSize,
194
+ ).toLocaleString()} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
195
+ );
196
+
197
+ if (Array.isArray(stats.histogram) && stats.histogram.length > 0) {
198
+ console.log('\n๐Ÿงฎ Size Histogram:');
199
+ for (const b of stats.histogram.slice(0, 2)) {
200
+ console.log(` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`);
201
+ }
202
+ if (stats.histogram.length > 2) {
203
+ console.log(` โ€ฆ and ${stats.histogram.length - 2} more buckets`);
204
+ }
205
+ }
206
+
207
+ if (Array.isArray(stats.byExtension) && stats.byExtension.length > 0) {
208
+ const topExt = stats.byExtension.slice(0, 2);
209
+ console.log('\n๐Ÿ“ฆ Top Extensions:');
210
+ for (const e of topExt) {
211
+ const pct = stats.totalBytes ? (e.bytes / stats.totalBytes) * 100 : 0;
212
+ console.log(
213
+ ` ${e.ext}: ${e.count} files, ${e.bytes.toLocaleString()} bytes (${pct.toFixed(
214
+ 2,
215
+ )}%)`,
216
+ );
217
+ }
218
+ if (stats.byExtension.length > 2) {
219
+ console.log(` โ€ฆ and ${stats.byExtension.length - 2} more extensions`);
220
+ }
221
+ }
222
+
223
+ if (Array.isArray(stats.byDirectory) && stats.byDirectory.length > 0) {
224
+ const topDir = stats.byDirectory.slice(0, 2);
225
+ console.log('\n๐Ÿ“‚ Top Directories:');
226
+ for (const d of topDir) {
227
+ const pct = stats.totalBytes ? (d.bytes / stats.totalBytes) * 100 : 0;
228
+ console.log(
229
+ ` ${d.dir}: ${d.count} files, ${d.bytes.toLocaleString()} bytes (${pct.toFixed(
230
+ 2,
231
+ )}%)`,
232
+ );
233
+ }
234
+ if (stats.byDirectory.length > 2) {
235
+ console.log(` โ€ฆ and ${stats.byDirectory.length - 2} more directories`);
236
+ }
237
+ }
238
+
239
+ if (Array.isArray(stats.depthDistribution) && stats.depthDistribution.length > 0) {
240
+ console.log('\n๐ŸŒณ Depth Distribution:');
241
+ const dd = stats.depthDistribution.slice(0, 2);
242
+ let line = ' ' + dd.map((d) => `${d.depth}:${d.count}`).join(' ');
243
+ if (stats.depthDistribution.length > 2) {
244
+ line += ` โ€ฆ +${stats.depthDistribution.length - 2} more`;
245
+ }
246
+ console.log(line);
247
+ }
248
+
249
+ if (Array.isArray(stats.longestPaths) && stats.longestPaths.length > 0) {
250
+ console.log('\n๐Ÿงต Longest Paths:');
251
+ for (const p of stats.longestPaths.slice(0, 2)) {
252
+ console.log(` ${p.path} (${p.length} chars, ${p.size.toLocaleString()} bytes)`);
253
+ }
254
+ if (stats.longestPaths.length > 2) {
255
+ console.log(` โ€ฆ and ${stats.longestPaths.length - 2} more paths`);
256
+ }
257
+ }
258
+
259
+ if (stats.temporal) {
260
+ console.log('\nโฑ๏ธ Temporal:');
261
+ if (stats.temporal.oldest) {
262
+ console.log(
263
+ ` Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`,
264
+ );
265
+ }
266
+ if (stats.temporal.newest) {
267
+ console.log(
268
+ ` Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`,
269
+ );
270
+ }
271
+ if (Array.isArray(stats.temporal.ageBuckets)) {
272
+ console.log(' Age buckets:');
273
+ for (const b of stats.temporal.ageBuckets.slice(0, 2)) {
274
+ console.log(` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`);
275
+ }
276
+ if (stats.temporal.ageBuckets.length > 2) {
277
+ console.log(` โ€ฆ and ${stats.temporal.ageBuckets.length - 2} more buckets`);
278
+ }
279
+ }
280
+ }
281
+
282
+ if (stats.quality) {
283
+ console.log('\nโœ… Quality Signals:');
284
+ console.log(` Zero-byte files: ${stats.quality.zeroByteFiles}`);
285
+ console.log(` Empty text files: ${stats.quality.emptyTextFiles}`);
286
+ console.log(` Hidden files: ${stats.quality.hiddenFiles}`);
287
+ console.log(` Symlinks: ${stats.quality.symlinks}`);
288
+ console.log(
289
+ ` Large files (>= ${(stats.quality.largeThreshold / (1024 * 1024)).toFixed(
290
+ 0,
291
+ )} MB): ${stats.quality.largeFilesCount}`,
292
+ );
293
+ console.log(
294
+ ` Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
295
+ );
296
+ }
297
+
298
+ if (Array.isArray(stats.duplicateCandidates) && stats.duplicateCandidates.length > 0) {
299
+ console.log('\n๐Ÿงฌ Duplicate Candidates:');
300
+ for (const d of stats.duplicateCandidates.slice(0, 2)) {
301
+ console.log(` ${d.reason}: ${d.count} files @ ${d.size.toLocaleString()} bytes`);
302
+ }
303
+ if (stats.duplicateCandidates.length > 2) {
304
+ console.log(` โ€ฆ and ${stats.duplicateCandidates.length - 2} more groups`);
305
+ }
306
+ }
307
+
308
+ if (typeof stats.compressibilityRatio === 'number') {
309
+ console.log(
310
+ `\n๐Ÿ—œ๏ธ Compressibility ratio (sampled): ${(stats.compressibilityRatio * 100).toFixed(
311
+ 2,
312
+ )}%`,
313
+ );
314
+ }
315
+
316
+ if (stats.git && stats.git.isRepo) {
317
+ console.log('\n๐Ÿ”ง Git:');
318
+ console.log(
319
+ ` Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
320
+ );
321
+ console.log(
322
+ ` Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
323
+ );
324
+ if (Array.isArray(stats.git.lfsCandidates) && stats.git.lfsCandidates.length > 0) {
325
+ console.log(' LFS candidates (top 2):');
326
+ for (const f of stats.git.lfsCandidates.slice(0, 2)) {
327
+ console.log(` ${f.path} (${f.size.toLocaleString()} bytes)`);
328
+ }
329
+ if (stats.git.lfsCandidates.length > 2) {
330
+ console.log(` โ€ฆ and ${stats.git.lfsCandidates.length - 2} more`);
331
+ }
332
+ }
333
+ }
334
+
335
+ if (Array.isArray(stats.largestFiles) && stats.largestFiles.length > 0) {
336
+ console.log('\n๐Ÿ“š Largest Files (top 2):');
337
+ for (const f of stats.largestFiles.slice(0, 2)) {
338
+ // Show LOC for text files when available; omit ext and mtime
339
+ let locStr = '';
340
+ if (!f.isBinary && Array.isArray(aggregatedContent?.textFiles)) {
341
+ const tf = aggregatedContent.textFiles.find((t) => t.path === f.path);
342
+ if (tf && typeof tf.lines === 'number') {
343
+ locStr = `, LOC: ${tf.lines.toLocaleString()}`;
344
+ }
345
+ }
346
+ console.log(
347
+ ` ${f.path} โ€“ ${f.sizeFormatted} (${f.percentOfTotal.toFixed(2)}%)${locStr}`,
348
+ );
349
+ }
350
+ if (stats.largestFiles.length > 2) {
351
+ console.log(` โ€ฆ and ${stats.largestFiles.length - 2} more files`);
352
+ }
353
+ }
354
+
355
+ // Write a comprehensive markdown report next to the XML
356
+ {
357
+ const mdPath = outputPath.endsWith('.xml')
358
+ ? outputPath.replace(/\.xml$/i, '.stats.md')
359
+ : outputPath + '.stats.md';
360
+ try {
361
+ const pct = (num, den) => (den ? (num / den) * 100 : 0);
362
+ const md = [];
363
+ md.push(
364
+ `# ๐Ÿงพ Flatten Stats for ${path.basename(outputPath)}`,
365
+ '',
366
+ '## ๐Ÿ“Š Summary',
367
+ `- Total source size: ${stats.totalSize}`,
368
+ `- Generated XML size: ${stats.xmlSize}`,
369
+ `- Total lines of code: ${stats.totalLines.toLocaleString()}`,
370
+ `- Estimated tokens: ${stats.estimatedTokens}`,
371
+ `- File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`,
372
+ '',
373
+ '## ๐Ÿ“ˆ Size Percentiles',
374
+ `Avg: ${Math.round(stats.avgFileSize).toLocaleString()} B, Median: ${Math.round(
375
+ stats.medianFileSize,
376
+ ).toLocaleString()} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
377
+ '',
378
+ );
379
+
380
+ // Histogram
381
+ if (Array.isArray(stats.histogram) && stats.histogram.length > 0) {
382
+ md.push(
383
+ '## ๐Ÿงฎ Size Histogram',
384
+ '| Bucket | Files | Bytes |',
385
+ '| --- | ---: | ---: |',
386
+ );
387
+ for (const b of stats.histogram) {
388
+ md.push(`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`);
389
+ }
390
+ md.push('');
391
+ }
392
+
393
+ // Top Extensions
394
+ if (Array.isArray(stats.byExtension) && stats.byExtension.length > 0) {
395
+ md.push(
396
+ '## ๐Ÿ“ฆ Top Extensions by Bytes (Top 20)',
397
+ '| Ext | Files | Bytes | % of total |',
398
+ '| --- | ---: | ---: | ---: |',
399
+ );
400
+ for (const e of stats.byExtension.slice(0, 20)) {
401
+ const p = pct(e.bytes, stats.totalBytes);
402
+ md.push(
403
+ `| ${e.ext} | ${e.count} | ${e.bytes.toLocaleString()} | ${p.toFixed(2)}% |`,
404
+ );
405
+ }
406
+ md.push('');
407
+ }
408
+
409
+ // Top Directories
410
+ if (Array.isArray(stats.byDirectory) && stats.byDirectory.length > 0) {
411
+ md.push(
412
+ '## ๐Ÿ“‚ Top Directories by Bytes (Top 20)',
413
+ '| Directory | Files | Bytes | % of total |',
414
+ '| --- | ---: | ---: | ---: |',
415
+ );
416
+ for (const d of stats.byDirectory.slice(0, 20)) {
417
+ const p = pct(d.bytes, stats.totalBytes);
418
+ md.push(
419
+ `| ${d.dir} | ${d.count} | ${d.bytes.toLocaleString()} | ${p.toFixed(2)}% |`,
420
+ );
421
+ }
422
+ md.push('');
423
+ }
424
+
425
+ // Depth distribution
426
+ if (Array.isArray(stats.depthDistribution) && stats.depthDistribution.length > 0) {
427
+ md.push('## ๐ŸŒณ Depth Distribution', '| Depth | Count |', '| ---: | ---: |');
428
+ for (const d of stats.depthDistribution) {
429
+ md.push(`| ${d.depth} | ${d.count} |`);
430
+ }
431
+ md.push('');
432
+ }
433
+
434
+ // Longest paths
435
+ if (Array.isArray(stats.longestPaths) && stats.longestPaths.length > 0) {
436
+ md.push(
437
+ '## ๐Ÿงต Longest Paths (Top 25)',
438
+ '| Path | Length | Bytes |',
439
+ '| --- | ---: | ---: |',
440
+ );
441
+ for (const pth of stats.longestPaths) {
442
+ md.push(`| ${pth.path} | ${pth.length} | ${pth.size.toLocaleString()} |`);
443
+ }
444
+ md.push('');
445
+ }
446
+
447
+ // Temporal
448
+ if (stats.temporal) {
449
+ md.push('## โฑ๏ธ Temporal');
450
+ if (stats.temporal.oldest) {
451
+ md.push(`- Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`);
452
+ }
453
+ if (stats.temporal.newest) {
454
+ md.push(`- Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`);
455
+ }
456
+ if (Array.isArray(stats.temporal.ageBuckets)) {
457
+ md.push('', '| Age | Files | Bytes |', '| --- | ---: | ---: |');
458
+ for (const b of stats.temporal.ageBuckets) {
459
+ md.push(`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`);
460
+ }
461
+ }
462
+ md.push('');
463
+ }
464
+
465
+ // Quality signals
466
+ if (stats.quality) {
467
+ md.push(
468
+ '## โœ… Quality Signals',
469
+ `- Zero-byte files: ${stats.quality.zeroByteFiles}`,
470
+ `- Empty text files: ${stats.quality.emptyTextFiles}`,
471
+ `- Hidden files: ${stats.quality.hiddenFiles}`,
472
+ `- Symlinks: ${stats.quality.symlinks}`,
473
+ `- Large files (>= ${(stats.quality.largeThreshold / (1024 * 1024)).toFixed(0)} MB): ${stats.quality.largeFilesCount}`,
474
+ `- Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
475
+ '',
476
+ );
477
+ }
478
+
479
+ // Duplicates
480
+ if (Array.isArray(stats.duplicateCandidates) && stats.duplicateCandidates.length > 0) {
481
+ md.push(
482
+ '## ๐Ÿงฌ Duplicate Candidates',
483
+ '| Reason | Files | Size (bytes) |',
484
+ '| --- | ---: | ---: |',
485
+ );
486
+ for (const d of stats.duplicateCandidates) {
487
+ md.push(`| ${d.reason} | ${d.count} | ${d.size.toLocaleString()} |`);
488
+ }
489
+ md.push('', '### ๐Ÿงฌ Duplicate Groups Details');
490
+ let dupIndex = 1;
491
+ for (const d of stats.duplicateCandidates) {
492
+ md.push(
493
+ `#### Group ${dupIndex}: ${d.count} files @ ${d.size.toLocaleString()} bytes (${d.reason})`,
494
+ );
495
+ if (Array.isArray(d.files) && d.files.length > 0) {
496
+ for (const fp of d.files) {
497
+ md.push(`- ${fp}`);
498
+ }
499
+ } else {
500
+ md.push('- (file list unavailable)');
501
+ }
502
+ md.push('');
503
+ dupIndex++;
504
+ }
505
+ md.push('');
506
+ }
507
+
508
+ // Compressibility
509
+ if (typeof stats.compressibilityRatio === 'number') {
510
+ md.push(
511
+ '## ๐Ÿ—œ๏ธ Compressibility',
512
+ `Sampled compressibility ratio: ${(stats.compressibilityRatio * 100).toFixed(2)}%`,
513
+ '',
514
+ );
515
+ }
516
+
517
+ // Git
518
+ if (stats.git && stats.git.isRepo) {
519
+ md.push(
520
+ '## ๐Ÿ”ง Git',
521
+ `- Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
522
+ `- Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
523
+ );
524
+ if (Array.isArray(stats.git.lfsCandidates) && stats.git.lfsCandidates.length > 0) {
525
+ md.push('', '### ๐Ÿ“ฆ LFS Candidates (Top 20)', '| Path | Bytes |', '| --- | ---: |');
526
+ for (const f of stats.git.lfsCandidates.slice(0, 20)) {
527
+ md.push(`| ${f.path} | ${f.size.toLocaleString()} |`);
528
+ }
529
+ }
530
+ md.push('');
531
+ }
532
+
533
+ // Largest Files
534
+ if (Array.isArray(stats.largestFiles) && stats.largestFiles.length > 0) {
535
+ md.push(
536
+ '## ๐Ÿ“š Largest Files (Top 50)',
537
+ '| Path | Size | % of total | LOC |',
538
+ '| --- | ---: | ---: | ---: |',
539
+ );
540
+ for (const f of stats.largestFiles) {
541
+ let loc = '';
542
+ if (!f.isBinary && Array.isArray(aggregatedContent?.textFiles)) {
543
+ const tf = aggregatedContent.textFiles.find((t) => t.path === f.path);
544
+ if (tf && typeof tf.lines === 'number') {
545
+ loc = tf.lines.toLocaleString();
546
+ }
547
+ }
548
+ md.push(
549
+ `| ${f.path} | ${f.sizeFormatted} | ${f.percentOfTotal.toFixed(2)}% | ${loc} |`,
550
+ );
551
+ }
552
+ md.push('');
553
+ }
554
+
555
+ await fs.writeFile(mdPath, md.join('\n'));
556
+ console.log(`\n๐Ÿงพ Detailed stats report written to: ${mdPath}`);
557
+ } catch (error) {
558
+ console.warn(`โš ๏ธ Failed to write stats markdown: ${error.message}`);
559
+ }
560
+ }
561
+ }
206
562
  } catch (error) {
207
- console.error("โŒ Critical error:", error.message);
208
- console.error("An unexpected error occurred.");
563
+ console.error('โŒ Critical error:', error.message);
564
+ console.error('An unexpected error occurred.');
209
565
  process.exit(1);
210
566
  }
211
567
  });