@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.
- package/.github/FORK_GUIDE.md +106 -0
- package/.github/FUNDING.yaml +15 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/workflows/discord.yaml +26 -0
- package/.github/workflows/format-check.yaml +44 -0
- package/.github/workflows/manual-release.yaml +174 -0
- package/.github/workflows/pr-validation.yaml +55 -0
- package/.husky/pre-commit +3 -0
- package/.vscode/settings.json +26 -1
- package/CHANGELOG.md +686 -0
- package/CONTRIBUTING.md +250 -0
- package/LICENSE +6 -1
- package/common/tasks/create-doc.md +2 -0
- package/common/tasks/execute-checklist.md +2 -7
- package/common/utils/bmad-doc-template.md +7 -5
- package/common/utils/workflow-management.md +2 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +2103 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +1627 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +822 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +8486 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +3210 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +3244 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +317 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +982 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +12854 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/beta-reader.txt +921 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/book-critic.txt +81 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/character-psychologist.txt +886 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/cover-designer.txt +85 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/dialog-specialist.txt +903 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/editor.txt +837 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/genre-specialist.txt +989 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/narrative-designer.txt +888 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/plot-architect.txt +1173 -0
- package/dist/expansion-packs/bmad-creative-writing/agents/world-builder.txt +914 -0
- package/dist/expansion-packs/bmad-creative-writing/teams/agent-team.txt +6071 -0
- package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +2079 -0
- package/docs/GUIDING-PRINCIPLES.md +91 -0
- package/docs/core-architecture.md +219 -0
- package/docs/enhanced-ide-development-workflow.md +248 -0
- package/docs/expansion-packs.md +200 -0
- package/docs/how-to-contribute-with-pull-requests.md +158 -0
- package/docs/user-guide.md +530 -0
- package/docs/versioning-and-releases.md +155 -0
- package/docs/versions.md +48 -0
- package/docs/working-in-the-brownfield.md +597 -0
- package/eslint.config.mjs +119 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agent-teams/phaser-2d-nodejs-game-team.yaml +14 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.md +73 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +80 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +66 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-design-checklist.md +203 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-story-dod-checklist.md +162 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/config.yaml +9 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/data/bmad-kb.md +252 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +649 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/tasks/advanced-elicitation.md +112 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/tasks/create-game-story.md +218 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/tasks/game-design-brainstorming.md +292 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +614 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +357 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +344 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +254 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +485 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +184 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +176 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +15 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.md +82 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.md +79 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +80 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.md +67 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +393 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +205 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +203 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +126 -0
- package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +7 -0
- package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +771 -0
- package/expansion-packs/bmad-2d-unity-game-dev/data/development-guidelines.md +588 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/advanced-elicitation.md +112 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/correct-course-game.md +143 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/create-game-story.md +186 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/game-design-brainstorming.md +292 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/validate-game-story.md +202 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +1031 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +357 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +706 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +257 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +485 -0
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +184 -0
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +176 -0
- package/expansion-packs/bmad-creative-writing/README.md +146 -0
- package/expansion-packs/bmad-creative-writing/agent-teams/agent-team.yaml +20 -0
- package/expansion-packs/bmad-creative-writing/agents/beta-reader.md +94 -0
- package/expansion-packs/bmad-creative-writing/agents/book-critic.md +40 -0
- package/expansion-packs/bmad-creative-writing/agents/character-psychologist.md +93 -0
- package/expansion-packs/bmad-creative-writing/agents/cover-designer.md +46 -0
- package/expansion-packs/bmad-creative-writing/agents/dialog-specialist.md +92 -0
- package/expansion-packs/bmad-creative-writing/agents/editor.md +93 -0
- package/expansion-packs/bmad-creative-writing/agents/genre-specialist.md +95 -0
- package/expansion-packs/bmad-creative-writing/agents/narrative-designer.md +93 -0
- package/expansion-packs/bmad-creative-writing/agents/plot-architect.md +95 -0
- package/expansion-packs/bmad-creative-writing/agents/world-builder.md +94 -0
- package/expansion-packs/bmad-creative-writing/checklists/beta-feedback-closure-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/character-consistency-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/comedic-timing-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/cyberpunk-aesthetic-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/ebook-formatting-checklist.md +21 -0
- package/expansion-packs/bmad-creative-writing/checklists/epic-poetry-meter-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/fantasy-magic-system-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/foreshadowing-payoff-checklist.md +22 -0
- package/expansion-packs/bmad-creative-writing/checklists/genre-tropes-checklist.md +22 -0
- package/expansion-packs/bmad-creative-writing/checklists/historical-accuracy-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/horror-suspense-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/kdp-cover-ready-checklist.md +25 -0
- package/expansion-packs/bmad-creative-writing/checklists/line-edit-quality-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/marketing-copy-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/mystery-clue-trail-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/orbital-mechanics-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/plot-structure-checklist.md +59 -0
- package/expansion-packs/bmad-creative-writing/checklists/publication-readiness-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/romance-emotional-beats-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/scene-quality-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/scifi-technology-plausibility-checklist.md +22 -0
- package/expansion-packs/bmad-creative-writing/checklists/sensitivity-representation-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/steampunk-gadget-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/thriller-pacing-stakes-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/timeline-continuity-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/world-building-continuity-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/checklists/ya-appropriateness-checklist.md +23 -0
- package/expansion-packs/bmad-creative-writing/config.yaml +12 -0
- package/expansion-packs/bmad-creative-writing/data/bmad-kb.md +209 -0
- package/expansion-packs/bmad-creative-writing/data/story-structures.md +67 -0
- package/expansion-packs/bmad-creative-writing/docs/brief.md +212 -0
- package/expansion-packs/bmad-creative-writing/tasks/advanced-elicitation.md +119 -0
- package/expansion-packs/bmad-creative-writing/tasks/analyze-reader-feedback.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/analyze-story-structure.md +67 -0
- package/expansion-packs/bmad-creative-writing/tasks/assemble-kdp-package.md +29 -0
- package/expansion-packs/bmad-creative-writing/tasks/brainstorm-premise.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/build-world.md +24 -0
- package/expansion-packs/bmad-creative-writing/tasks/character-depth-pass.md +22 -0
- package/expansion-packs/bmad-creative-writing/tasks/create-doc.md +103 -0
- package/expansion-packs/bmad-creative-writing/tasks/create-draft-section.md +26 -0
- package/expansion-packs/bmad-creative-writing/tasks/critical-review.md +26 -0
- package/expansion-packs/bmad-creative-writing/tasks/develop-character.md +24 -0
- package/expansion-packs/bmad-creative-writing/tasks/execute-checklist.md +88 -0
- package/expansion-packs/bmad-creative-writing/tasks/expand-premise.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/expand-synopsis.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/final-polish.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/generate-cover-brief.md +25 -0
- package/expansion-packs/bmad-creative-writing/tasks/generate-cover-prompts.md +26 -0
- package/expansion-packs/bmad-creative-writing/tasks/generate-scene-list.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/incorporate-feedback.md +25 -0
- package/expansion-packs/bmad-creative-writing/tasks/outline-scenes.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/provide-feedback.md +24 -0
- package/expansion-packs/bmad-creative-writing/tasks/publish-chapter.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/quick-feedback.md +22 -0
- package/expansion-packs/bmad-creative-writing/tasks/select-next-arc.md +23 -0
- package/expansion-packs/bmad-creative-writing/tasks/workshop-dialog.md +64 -0
- package/expansion-packs/bmad-creative-writing/templates/beta-feedback-form.yaml +97 -0
- package/expansion-packs/bmad-creative-writing/templates/chapter-draft-tmpl.yaml +82 -0
- package/expansion-packs/bmad-creative-writing/templates/character-profile-tmpl.yaml +92 -0
- package/expansion-packs/bmad-creative-writing/templates/cover-design-brief-tmpl.yaml +98 -0
- package/expansion-packs/bmad-creative-writing/templates/premise-brief-tmpl.yaml +78 -0
- package/expansion-packs/bmad-creative-writing/templates/scene-list-tmpl.yaml +55 -0
- package/expansion-packs/bmad-creative-writing/templates/story-outline-tmpl.yaml +96 -0
- package/expansion-packs/bmad-creative-writing/templates/world-guide-tmpl.yaml +89 -0
- package/expansion-packs/bmad-creative-writing/workflows/book-cover-design-workflow.md +218 -0
- package/expansion-packs/bmad-creative-writing/workflows/novel-greenfield-workflow.yaml +56 -0
- package/expansion-packs/bmad-creative-writing/workflows/novel-serial-workflow.yaml +50 -0
- package/expansion-packs/bmad-creative-writing/workflows/novel-snowflake-workflow.yaml +69 -0
- package/expansion-packs/bmad-creative-writing/workflows/novel-writing.yaml +91 -0
- package/expansion-packs/bmad-creative-writing/workflows/screenplay-development.yaml +85 -0
- package/expansion-packs/bmad-creative-writing/workflows/series-planning.yaml +78 -0
- package/expansion-packs/bmad-creative-writing/workflows/short-story-creation.yaml +64 -0
- package/expansion-packs/bmad-infrastructure-devops/README.md +147 -0
- package/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.md +73 -0
- package/expansion-packs/bmad-infrastructure-devops/checklists/infrastructure-checklist.md +486 -0
- package/expansion-packs/bmad-infrastructure-devops/config.yaml +10 -0
- package/expansion-packs/bmad-infrastructure-devops/data/bmad-kb.md +307 -0
- package/expansion-packs/bmad-infrastructure-devops/tasks/review-infrastructure.md +161 -0
- package/expansion-packs/bmad-infrastructure-devops/tasks/validate-infrastructure.md +155 -0
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +425 -0
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +630 -0
- package/implement-fork-friendly-ci.sh +229 -0
- package/package.json +75 -45
- package/prettier.config.mjs +32 -0
- package/test.md +1 -0
- package/tools/builders/web-builder.js +143 -149
- package/tools/bump-all-versions.js +42 -33
- package/tools/bump-expansion-version.js +23 -16
- package/tools/cli.js +15 -15
- package/tools/flattener/aggregate.js +76 -0
- package/tools/flattener/binary.js +80 -0
- package/tools/flattener/discovery.js +71 -0
- package/tools/flattener/files.js +35 -0
- package/tools/flattener/ignoreRules.js +176 -0
- package/tools/flattener/main.js +458 -460
- package/tools/flattener/projectRoot.js +206 -0
- package/tools/flattener/prompts.js +44 -0
- package/tools/flattener/stats.helpers.js +395 -0
- package/tools/flattener/stats.js +80 -0
- package/tools/flattener/test-matrix.js +413 -0
- package/tools/flattener/xml.js +88 -0
- package/tools/installer/README.md +1 -1
- package/tools/installer/bin/xiaoma.js +392 -99
- package/tools/installer/config/ide-agent-config.yaml +1 -1
- package/tools/installer/config/install.config.yaml +60 -9
- package/tools/installer/lib/config-loader.js +55 -51
- package/tools/installer/lib/file-manager.js +92 -114
- package/tools/installer/lib/ide-base-setup.js +57 -56
- package/tools/installer/lib/ide-setup.js +821 -414
- package/tools/installer/lib/installer.js +924 -696
- package/tools/installer/lib/memory-profiler.js +54 -53
- package/tools/installer/lib/module-manager.js +19 -15
- package/tools/installer/lib/resource-locator.js +31 -33
- package/tools/installer/package.json +24 -23
- package/tools/lib/dependency-resolver.js +39 -43
- package/tools/lib/yaml-utils.js +7 -7
- package/tools/md-assets/web-agent-startup-instructions.md +6 -6
- package/tools/preview-release-notes.js +66 -0
- package/tools/setup-hooks.sh +37 -0
- package/tools/shared/bannerArt.js +105 -0
- package/tools/sync-installer-version.js +7 -9
- package/tools/sync-version.sh +23 -0
- package/tools/update-expansion-version.js +14 -15
- package/tools/upgraders/v3-to-v4-upgrader.js +203 -294
- package/tools/version-bump.js +41 -26
- package/tools/xiaoma-npx-wrapper.js +14 -14
- package/tools/yaml-format.js +56 -43
- package/xiaoma-core/agent-teams/team-all.yaml +3 -2
- package/xiaoma-core/agent-teams/team-fullstack.yaml +2 -1
- package/xiaoma-core/agent-teams/team-ide-minimal.yaml +1 -0
- package/xiaoma-core/agent-teams/team-no-ui.yaml +2 -1
- package/xiaoma-core/agents/analyst.md +20 -17
- package/xiaoma-core/agents/architect.md +15 -14
- package/xiaoma-core/agents/{xiaoma-master.md โ bmad-master.md} +29 -27
- package/xiaoma-core/agents/{xiaoma-orchestrator.md โ bmad-orchestrator.md} +36 -39
- package/xiaoma-core/agents/dev.md +23 -18
- package/xiaoma-core/agents/pm.md +18 -15
- package/xiaoma-core/agents/po.md +13 -10
- package/xiaoma-core/agents/qa.md +46 -24
- package/xiaoma-core/agents/sm.md +11 -8
- package/xiaoma-core/agents/ux-expert.md +10 -7
- package/xiaoma-core/checklists/architect-checklist.md +2 -5
- package/xiaoma-core/checklists/change-checklist.md +4 -2
- package/xiaoma-core/checklists/pm-checklist.md +2 -5
- package/xiaoma-core/checklists/po-master-checklist.md +2 -9
- package/xiaoma-core/checklists/story-dod-checklist.md +2 -7
- package/xiaoma-core/checklists/story-draft-checklist.md +2 -3
- package/xiaoma-core/core-config.yaml +4 -1
- package/xiaoma-core/data/{xiaoma-kb.md โ bmad-kb.md} +48 -42
- package/xiaoma-core/data/brainstorming-techniques.md +2 -0
- package/xiaoma-core/data/elicitation-methods.md +22 -0
- package/xiaoma-core/data/technical-preferences.md +2 -0
- package/xiaoma-core/data/test-levels-framework.md +148 -0
- package/xiaoma-core/data/test-priorities-matrix.md +174 -0
- package/xiaoma-core/tasks/advanced-elicitation.md +2 -0
- package/xiaoma-core/tasks/apply-qa-fixes.md +150 -0
- package/xiaoma-core/tasks/brownfield-create-epic.md +2 -0
- package/xiaoma-core/tasks/brownfield-create-story.md +2 -0
- package/xiaoma-core/tasks/correct-course.md +2 -0
- package/xiaoma-core/tasks/create-brownfield-story.md +14 -4
- package/xiaoma-core/tasks/create-deep-research-prompt.md +2 -11
- package/xiaoma-core/tasks/create-next-story.md +3 -1
- package/xiaoma-core/tasks/document-project.md +17 -13
- package/xiaoma-core/tasks/facilitate-brainstorming-session.md +5 -3
- package/xiaoma-core/tasks/generate-ai-frontend-prompt.md +2 -0
- package/xiaoma-core/tasks/index-docs.md +2 -6
- package/xiaoma-core/tasks/kb-mode-interaction.md +17 -15
- package/xiaoma-core/tasks/nfr-assess.md +345 -0
- package/xiaoma-core/tasks/qa-gate.md +163 -0
- package/xiaoma-core/tasks/review-story.md +245 -74
- package/xiaoma-core/tasks/risk-profile.md +355 -0
- package/xiaoma-core/tasks/shard-doc.md +2 -2
- package/xiaoma-core/tasks/test-design.md +176 -0
- package/xiaoma-core/tasks/trace-requirements.md +266 -0
- package/xiaoma-core/tasks/validate-next-story.md +5 -3
- package/xiaoma-core/templates/architecture-tmpl.yaml +50 -49
- package/xiaoma-core/templates/brainstorming-output-tmpl.yaml +5 -5
- package/xiaoma-core/templates/brownfield-architecture-tmpl.yaml +32 -31
- package/xiaoma-core/templates/brownfield-prd-tmpl.yaml +14 -13
- package/xiaoma-core/templates/competitor-analysis-tmpl.yaml +20 -6
- package/xiaoma-core/templates/front-end-architecture-tmpl.yaml +22 -9
- package/xiaoma-core/templates/front-end-spec-tmpl.yaml +25 -24
- package/xiaoma-core/templates/fullstack-architecture-tmpl.yaml +123 -104
- package/xiaoma-core/templates/market-research-tmpl.yaml +3 -2
- package/xiaoma-core/templates/prd-tmpl.yaml +10 -9
- package/xiaoma-core/templates/project-brief-tmpl.yaml +5 -4
- package/xiaoma-core/templates/qa-gate-tmpl.yaml +103 -0
- package/xiaoma-core/templates/story-tmpl.yaml +13 -12
- package/xiaoma-core/workflows/brownfield-fullstack.yaml +13 -12
- package/xiaoma-core/workflows/brownfield-service.yaml +5 -4
- package/xiaoma-core/workflows/brownfield-ui.yaml +5 -4
- package/xiaoma-core/workflows/greenfield-fullstack.yaml +7 -6
- package/xiaoma-core/workflows/greenfield-service.yaml +5 -4
- package/xiaoma-core/workflows/greenfield-ui.yaml +6 -5
- package/.releaserc.json +0 -18
- package/README.md +0 -532
- package/dist/agents/analyst.txt +0 -2882
- package/dist/agents/architect.txt +0 -3543
- package/dist/agents/dev.txt +0 -428
- package/dist/agents/pm.txt +0 -2229
- package/dist/agents/po.txt +0 -1364
- package/dist/agents/qa.txt +0 -386
- package/dist/agents/sm.txt +0 -668
- package/dist/agents/ux-expert.txt +0 -701
- package/dist/agents/xiaoma-master.txt +0 -8756
- package/dist/agents/xiaoma-orchestrator.txt +0 -1490
- package/dist/teams/team-all.txt +0 -11062
- package/dist/teams/team-fullstack.txt +0 -10392
- package/dist/teams/team-ide-minimal.txt +0 -3507
- package/dist/teams/team-no-ui.txt +0 -8951
- package/docs/quick-start.md +0 -179
- package/tools/bmad-npx-wrapper.js +0 -39
- package/tools/installer/package-lock.json +0 -704
- package/tools/semantic-release-sync-installer.js +0 -30
- package/xiaoma-core/bmad-core/user-guide.md +0 -0
- package/xiaoma-core/enhanced-ide-development-workflow.md +0 -43
- package/xiaoma-core/user-guide.md +0 -251
- package/xiaoma-core/working-in-the-brownfield.md +0 -364
package/tools/flattener/main.js
CHANGED
|
@@ -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
|
|
7
|
-
|
|
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, '&')
|
|
363
|
-
.replace(/</g, '<')
|
|
364
|
-
.replace(/>/g, '>')
|
|
365
|
-
.replace(/"/g, '"')
|
|
366
|
-
.replace(/'/g, ''');
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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('
|
|
497
|
-
.description('
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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(
|
|
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(
|
|
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.');
|