bmad-method 5.0.0-beta.2 โ 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +3 -3
- package/.github/ISSUE_TEMPLATE/feature_request.md +3 -3
- package/.github/workflows/discord.yaml +11 -2
- package/.github/workflows/format-check.yaml +42 -0
- package/.github/workflows/manual-release.yaml +173 -0
- package/.husky/pre-commit +3 -0
- package/.vscode/settings.json +26 -1
- package/CHANGELOG.md +0 -11
- package/README.md +2 -0
- package/bmad-core/agent-teams/team-all.yaml +1 -1
- package/bmad-core/agents/bmad-orchestrator.md +1 -1
- package/bmad-core/agents/dev.md +4 -4
- package/bmad-core/data/bmad-kb.md +1 -1
- package/bmad-core/data/test-levels-framework.md +12 -12
- package/bmad-core/tasks/facilitate-brainstorming-session.md +1 -1
- package/bmad-core/tasks/nfr-assess.md +10 -10
- package/bmad-core/tasks/qa-gate.md +23 -23
- package/bmad-core/tasks/review-story.md +18 -18
- package/bmad-core/tasks/risk-profile.md +25 -25
- package/bmad-core/tasks/test-design.md +9 -9
- package/bmad-core/tasks/trace-requirements.md +21 -21
- package/bmad-core/templates/architecture-tmpl.yaml +49 -49
- package/bmad-core/templates/brainstorming-output-tmpl.yaml +5 -5
- package/bmad-core/templates/brownfield-architecture-tmpl.yaml +31 -31
- package/bmad-core/templates/brownfield-prd-tmpl.yaml +13 -13
- package/bmad-core/templates/competitor-analysis-tmpl.yaml +19 -6
- package/bmad-core/templates/front-end-architecture-tmpl.yaml +21 -9
- package/bmad-core/templates/front-end-spec-tmpl.yaml +24 -24
- package/bmad-core/templates/fullstack-architecture-tmpl.yaml +122 -104
- package/bmad-core/templates/market-research-tmpl.yaml +2 -2
- package/bmad-core/templates/prd-tmpl.yaml +9 -9
- package/bmad-core/templates/project-brief-tmpl.yaml +4 -4
- package/bmad-core/templates/qa-gate-tmpl.yaml +9 -9
- package/bmad-core/templates/story-tmpl.yaml +12 -12
- package/bmad-core/workflows/brownfield-fullstack.yaml +9 -9
- package/bmad-core/workflows/brownfield-service.yaml +1 -1
- package/bmad-core/workflows/brownfield-ui.yaml +1 -1
- package/bmad-core/workflows/greenfield-fullstack.yaml +1 -1
- package/bmad-core/workflows/greenfield-service.yaml +1 -1
- package/bmad-core/workflows/greenfield-ui.yaml +1 -1
- package/common/utils/bmad-doc-template.md +5 -5
- package/dist/agents/analyst.txt +28 -15
- package/dist/agents/architect.txt +220 -190
- package/dist/agents/bmad-master.txt +298 -255
- package/dist/agents/bmad-orchestrator.txt +1 -1
- package/dist/agents/pm.txt +20 -20
- package/dist/agents/po.txt +11 -11
- package/dist/agents/qa.txt +275 -618
- package/dist/agents/sm.txt +11 -11
- package/dist/agents/ux-expert.txt +23 -23
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +109 -109
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +75 -77
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +41 -41
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +483 -474
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +1 -1
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +149 -149
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +20 -20
- package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +371 -358
- package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +25 -25
- package/dist/teams/team-all.txt +581 -881
- package/dist/teams/team-fullstack.txt +316 -273
- package/dist/teams/team-ide-minimal.txt +276 -619
- package/dist/teams/team-no-ui.txt +281 -238
- package/docs/versioning-and-releases.md +114 -44
- package/eslint.config.mjs +119 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.4 Deployment Configuration/1.4.2 - cloudbuild.yaml +26 -26
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +4 -4
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +1 -1
- package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +26 -28
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +50 -50
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +23 -23
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +24 -24
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +42 -42
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +5 -5
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +1 -1
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +3 -3
- package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +1 -1
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +23 -23
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +63 -63
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +20 -20
- package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +5 -5
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +1 -1
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +20 -20
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +7 -7
- package/package.json +62 -39
- package/prettier.config.mjs +32 -0
- package/release_notes.md +30 -0
- package/tools/bmad-npx-wrapper.js +10 -10
- package/tools/builders/web-builder.js +124 -130
- package/tools/bump-all-versions.js +42 -33
- package/tools/bump-expansion-version.js +23 -16
- package/tools/cli.js +10 -12
- package/tools/flattener/aggregate.js +10 -10
- package/tools/flattener/binary.js +44 -17
- package/tools/flattener/discovery.js +19 -18
- package/tools/flattener/files.js +6 -6
- package/tools/flattener/ignoreRules.js +125 -125
- package/tools/flattener/main.js +201 -304
- package/tools/flattener/projectRoot.js +75 -73
- package/tools/flattener/prompts.js +9 -9
- package/tools/flattener/stats.helpers.js +131 -67
- package/tools/flattener/stats.js +3 -3
- package/tools/flattener/test-matrix.js +201 -193
- package/tools/flattener/xml.js +33 -31
- package/tools/installer/bin/bmad.js +130 -89
- package/tools/installer/config/ide-agent-config.yaml +1 -1
- package/tools/installer/config/install.config.yaml +2 -2
- package/tools/installer/lib/config-loader.js +46 -42
- package/tools/installer/lib/file-manager.js +91 -113
- package/tools/installer/lib/ide-base-setup.js +57 -56
- package/tools/installer/lib/ide-setup.js +375 -343
- package/tools/installer/lib/installer.js +875 -714
- 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 +26 -28
- package/tools/installer/package.json +19 -19
- package/tools/lib/dependency-resolver.js +26 -30
- package/tools/lib/yaml-utils.js +7 -7
- package/tools/preview-release-notes.js +66 -0
- package/tools/shared/bannerArt.js +3 -3
- package/tools/sync-installer-version.js +7 -9
- 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/yaml-format.js +56 -43
- package/.github/workflows/promote-to-stable.yml +0 -144
- package/.github/workflows/release.yaml +0 -60
- package/.releaserc.json +0 -21
- package/tools/semantic-release-sync-installer.js +0 -30
package/tools/flattener/main.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
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(
|
|
10
|
-
const { promptYesNo, promptPath } = require(
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
77
|
-
.description(
|
|
78
|
-
.version(
|
|
79
|
-
.option(
|
|
80
|
-
.option(
|
|
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(
|
|
88
|
-
a ===
|
|
81
|
+
const userSpecifiedInput = argv.some(
|
|
82
|
+
(a) => a === '-i' || a === '--input' || a.startsWith('--input='),
|
|
89
83
|
);
|
|
90
|
-
const userSpecifiedOutput = argv.some(
|
|
91
|
-
a ===
|
|
84
|
+
const userSpecifiedOutput = argv.some(
|
|
85
|
+
(a) => a === '-o' || a === '--output' || a.startsWith('--output='),
|
|
92
86
|
);
|
|
93
|
-
const
|
|
87
|
+
const noPathArguments = !userSpecifiedInput && !userSpecifiedOutput;
|
|
94
88
|
|
|
95
|
-
if (
|
|
89
|
+
if (noPathArguments) {
|
|
96
90
|
const detectedRoot = await findProjectRoot(process.cwd());
|
|
97
91
|
const suggestedOutput = detectedRoot
|
|
98
|
-
? path.join(detectedRoot,
|
|
99
|
-
: path.resolve(
|
|
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,26 +101,25 @@ 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
|
-
|
|
116
|
-
path.join(inputDir,
|
|
106
|
+
'Enter output file path',
|
|
107
|
+
path.join(inputDir, 'flattened-codebase.xml'),
|
|
117
108
|
);
|
|
118
109
|
}
|
|
119
110
|
} else {
|
|
120
|
-
console.log(
|
|
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
|
-
|
|
127
|
-
path.join(inputDir,
|
|
114
|
+
'Enter output file path',
|
|
115
|
+
path.join(inputDir, 'flattened-codebase.xml'),
|
|
128
116
|
);
|
|
129
117
|
}
|
|
118
|
+
} else {
|
|
119
|
+
console.error(
|
|
120
|
+
'Could not auto-detect a project root and no arguments were provided. Please specify -i/--input and -o/--output.',
|
|
121
|
+
);
|
|
122
|
+
process.exit(1);
|
|
130
123
|
}
|
|
131
124
|
|
|
132
125
|
// Ensure output directory exists
|
|
@@ -134,24 +127,23 @@ program
|
|
|
134
127
|
|
|
135
128
|
try {
|
|
136
129
|
// Verify input directory exists
|
|
137
|
-
if (!await fs.pathExists(inputDir)) {
|
|
130
|
+
if (!(await fs.pathExists(inputDir))) {
|
|
138
131
|
console.error(`โ Error: Input directory does not exist: ${inputDir}`);
|
|
139
132
|
process.exit(1);
|
|
140
133
|
}
|
|
141
134
|
|
|
142
135
|
// Import ora dynamically
|
|
143
|
-
const { default: ora } = await import(
|
|
136
|
+
const { default: ora } = await import('ora');
|
|
144
137
|
|
|
145
138
|
// Start file discovery with spinner
|
|
146
|
-
const discoverySpinner = ora(
|
|
139
|
+
const discoverySpinner = ora('๐ Discovering files...').start();
|
|
147
140
|
const files = await discoverFiles(inputDir);
|
|
148
141
|
const filteredFiles = await filterFiles(files, inputDir);
|
|
149
|
-
discoverySpinner.succeed(
|
|
150
|
-
`๐ Found ${filteredFiles.length} files to include`,
|
|
151
|
-
);
|
|
142
|
+
discoverySpinner.succeed(`๐ Found ${filteredFiles.length} files to include`);
|
|
152
143
|
|
|
153
144
|
// Process files with progress tracking
|
|
154
|
-
|
|
145
|
+
console.log('Reading file contents');
|
|
146
|
+
const processingSpinner = ora('๐ Processing files...').start();
|
|
155
147
|
const aggregatedContent = await aggregateFileContents(
|
|
156
148
|
filteredFiles,
|
|
157
149
|
inputDir,
|
|
@@ -165,31 +157,23 @@ program
|
|
|
165
157
|
}
|
|
166
158
|
|
|
167
159
|
// Generate XML output using streaming
|
|
168
|
-
const xmlSpinner = ora(
|
|
160
|
+
const xmlSpinner = ora('๐ง Generating XML output...').start();
|
|
169
161
|
await generateXMLOutput(aggregatedContent, outputPath);
|
|
170
|
-
xmlSpinner.succeed(
|
|
162
|
+
xmlSpinner.succeed('๐ XML generation completed');
|
|
171
163
|
|
|
172
164
|
// Calculate and display statistics
|
|
173
165
|
const outputStats = await fs.stat(outputPath);
|
|
174
|
-
const stats = await calculateStatistics(
|
|
175
|
-
aggregatedContent,
|
|
176
|
-
outputStats.size,
|
|
177
|
-
inputDir,
|
|
178
|
-
);
|
|
166
|
+
const stats = await calculateStatistics(aggregatedContent, outputStats.size, inputDir);
|
|
179
167
|
|
|
180
168
|
// Display completion summary
|
|
181
|
-
console.log(
|
|
169
|
+
console.log('\n๐ Completion Summary:');
|
|
182
170
|
console.log(
|
|
183
|
-
`โ
Successfully processed ${filteredFiles.length} files into ${
|
|
184
|
-
path.basename(outputPath)
|
|
185
|
-
}`,
|
|
171
|
+
`โ
Successfully processed ${filteredFiles.length} files into ${path.basename(outputPath)}`,
|
|
186
172
|
);
|
|
187
173
|
console.log(`๐ Output file: ${outputPath}`);
|
|
188
174
|
console.log(`๐ Total source size: ${stats.totalSize}`);
|
|
189
175
|
console.log(`๐ Generated XML size: ${stats.xmlSize}`);
|
|
190
|
-
console.log(
|
|
191
|
-
`๐ Total lines of code: ${stats.totalLines.toLocaleString()}`,
|
|
192
|
-
);
|
|
176
|
+
console.log(`๐ Total lines of code: ${stats.totalLines.toLocaleString()}`);
|
|
193
177
|
console.log(`๐ข Estimated tokens: ${stats.estimatedTokens}`);
|
|
194
178
|
console.log(
|
|
195
179
|
`๐ File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors\n`,
|
|
@@ -197,92 +181,75 @@ program
|
|
|
197
181
|
|
|
198
182
|
// Ask user if they want detailed stats + markdown report
|
|
199
183
|
const generateDetailed = await promptYesNo(
|
|
200
|
-
|
|
184
|
+
'Generate detailed stats (console + markdown) now?',
|
|
201
185
|
true,
|
|
202
186
|
);
|
|
203
187
|
|
|
204
188
|
if (generateDetailed) {
|
|
205
189
|
// Additional detailed stats
|
|
206
|
-
console.log(
|
|
190
|
+
console.log('\n๐ Size Percentiles:');
|
|
207
191
|
console.log(
|
|
208
|
-
` Avg: ${
|
|
209
|
-
|
|
210
|
-
} B,
|
|
211
|
-
Math.round(stats.medianFileSize).toLocaleString()
|
|
212
|
-
} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
|
|
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`,
|
|
213
195
|
);
|
|
214
196
|
|
|
215
|
-
if (Array.isArray(stats.histogram) && stats.histogram.length) {
|
|
216
|
-
console.log(
|
|
197
|
+
if (Array.isArray(stats.histogram) && stats.histogram.length > 0) {
|
|
198
|
+
console.log('\n๐งฎ Size Histogram:');
|
|
217
199
|
for (const b of stats.histogram.slice(0, 2)) {
|
|
218
|
-
console.log(
|
|
219
|
-
` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`,
|
|
220
|
-
);
|
|
200
|
+
console.log(` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`);
|
|
221
201
|
}
|
|
222
202
|
if (stats.histogram.length > 2) {
|
|
223
203
|
console.log(` โฆ and ${stats.histogram.length - 2} more buckets`);
|
|
224
204
|
}
|
|
225
205
|
}
|
|
226
206
|
|
|
227
|
-
if (Array.isArray(stats.byExtension) && stats.byExtension.length) {
|
|
207
|
+
if (Array.isArray(stats.byExtension) && stats.byExtension.length > 0) {
|
|
228
208
|
const topExt = stats.byExtension.slice(0, 2);
|
|
229
|
-
console.log(
|
|
209
|
+
console.log('\n๐ฆ Top Extensions:');
|
|
230
210
|
for (const e of topExt) {
|
|
231
|
-
const pct = stats.totalBytes
|
|
232
|
-
? ((e.bytes / stats.totalBytes) * 100)
|
|
233
|
-
: 0;
|
|
211
|
+
const pct = stats.totalBytes ? (e.bytes / stats.totalBytes) * 100 : 0;
|
|
234
212
|
console.log(
|
|
235
|
-
` ${e.ext}: ${e.count} files, ${e.bytes.toLocaleString()} bytes (${
|
|
236
|
-
|
|
237
|
-
}%)`,
|
|
213
|
+
` ${e.ext}: ${e.count} files, ${e.bytes.toLocaleString()} bytes (${pct.toFixed(
|
|
214
|
+
2,
|
|
215
|
+
)}%)`,
|
|
238
216
|
);
|
|
239
217
|
}
|
|
240
218
|
if (stats.byExtension.length > 2) {
|
|
241
|
-
console.log(
|
|
242
|
-
` โฆ and ${stats.byExtension.length - 2} more extensions`,
|
|
243
|
-
);
|
|
219
|
+
console.log(` โฆ and ${stats.byExtension.length - 2} more extensions`);
|
|
244
220
|
}
|
|
245
221
|
}
|
|
246
222
|
|
|
247
|
-
if (Array.isArray(stats.byDirectory) && stats.byDirectory.length) {
|
|
223
|
+
if (Array.isArray(stats.byDirectory) && stats.byDirectory.length > 0) {
|
|
248
224
|
const topDir = stats.byDirectory.slice(0, 2);
|
|
249
|
-
console.log(
|
|
225
|
+
console.log('\n๐ Top Directories:');
|
|
250
226
|
for (const d of topDir) {
|
|
251
|
-
const pct = stats.totalBytes
|
|
252
|
-
? ((d.bytes / stats.totalBytes) * 100)
|
|
253
|
-
: 0;
|
|
227
|
+
const pct = stats.totalBytes ? (d.bytes / stats.totalBytes) * 100 : 0;
|
|
254
228
|
console.log(
|
|
255
|
-
` ${d.dir}: ${d.count} files, ${d.bytes.toLocaleString()} bytes (${
|
|
256
|
-
|
|
257
|
-
}%)`,
|
|
229
|
+
` ${d.dir}: ${d.count} files, ${d.bytes.toLocaleString()} bytes (${pct.toFixed(
|
|
230
|
+
2,
|
|
231
|
+
)}%)`,
|
|
258
232
|
);
|
|
259
233
|
}
|
|
260
234
|
if (stats.byDirectory.length > 2) {
|
|
261
|
-
console.log(
|
|
262
|
-
` โฆ and ${stats.byDirectory.length - 2} more directories`,
|
|
263
|
-
);
|
|
235
|
+
console.log(` โฆ and ${stats.byDirectory.length - 2} more directories`);
|
|
264
236
|
}
|
|
265
237
|
}
|
|
266
238
|
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
stats.depthDistribution.length
|
|
270
|
-
) {
|
|
271
|
-
console.log("\n๐ณ Depth Distribution:");
|
|
239
|
+
if (Array.isArray(stats.depthDistribution) && stats.depthDistribution.length > 0) {
|
|
240
|
+
console.log('\n๐ณ Depth Distribution:');
|
|
272
241
|
const dd = stats.depthDistribution.slice(0, 2);
|
|
273
|
-
let line =
|
|
242
|
+
let line = ' ' + dd.map((d) => `${d.depth}:${d.count}`).join(' ');
|
|
274
243
|
if (stats.depthDistribution.length > 2) {
|
|
275
244
|
line += ` โฆ +${stats.depthDistribution.length - 2} more`;
|
|
276
245
|
}
|
|
277
246
|
console.log(line);
|
|
278
247
|
}
|
|
279
248
|
|
|
280
|
-
if (Array.isArray(stats.longestPaths) && stats.longestPaths.length) {
|
|
281
|
-
console.log(
|
|
249
|
+
if (Array.isArray(stats.longestPaths) && stats.longestPaths.length > 0) {
|
|
250
|
+
console.log('\n๐งต Longest Paths:');
|
|
282
251
|
for (const p of stats.longestPaths.slice(0, 2)) {
|
|
283
|
-
console.log(
|
|
284
|
-
` ${p.path} (${p.length} chars, ${p.size.toLocaleString()} bytes)`,
|
|
285
|
-
);
|
|
252
|
+
console.log(` ${p.path} (${p.length} chars, ${p.size.toLocaleString()} bytes)`);
|
|
286
253
|
}
|
|
287
254
|
if (stats.longestPaths.length > 2) {
|
|
288
255
|
console.log(` โฆ and ${stats.longestPaths.length - 2} more paths`);
|
|
@@ -290,7 +257,7 @@ program
|
|
|
290
257
|
}
|
|
291
258
|
|
|
292
259
|
if (stats.temporal) {
|
|
293
|
-
console.log(
|
|
260
|
+
console.log('\nโฑ๏ธ Temporal:');
|
|
294
261
|
if (stats.temporal.oldest) {
|
|
295
262
|
console.log(
|
|
296
263
|
` Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`,
|
|
@@ -302,104 +269,82 @@ program
|
|
|
302
269
|
);
|
|
303
270
|
}
|
|
304
271
|
if (Array.isArray(stats.temporal.ageBuckets)) {
|
|
305
|
-
console.log(
|
|
272
|
+
console.log(' Age buckets:');
|
|
306
273
|
for (const b of stats.temporal.ageBuckets.slice(0, 2)) {
|
|
307
|
-
console.log(
|
|
308
|
-
` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`,
|
|
309
|
-
);
|
|
274
|
+
console.log(` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`);
|
|
310
275
|
}
|
|
311
276
|
if (stats.temporal.ageBuckets.length > 2) {
|
|
312
|
-
console.log(
|
|
313
|
-
` โฆ and ${
|
|
314
|
-
stats.temporal.ageBuckets.length - 2
|
|
315
|
-
} more buckets`,
|
|
316
|
-
);
|
|
277
|
+
console.log(` โฆ and ${stats.temporal.ageBuckets.length - 2} more buckets`);
|
|
317
278
|
}
|
|
318
279
|
}
|
|
319
280
|
}
|
|
320
281
|
|
|
321
282
|
if (stats.quality) {
|
|
322
|
-
console.log(
|
|
283
|
+
console.log('\nโ
Quality Signals:');
|
|
323
284
|
console.log(` Zero-byte files: ${stats.quality.zeroByteFiles}`);
|
|
324
285
|
console.log(` Empty text files: ${stats.quality.emptyTextFiles}`);
|
|
325
286
|
console.log(` Hidden files: ${stats.quality.hiddenFiles}`);
|
|
326
287
|
console.log(` Symlinks: ${stats.quality.symlinks}`);
|
|
327
288
|
console.log(
|
|
328
|
-
` Large files (>= ${
|
|
329
|
-
|
|
330
|
-
} MB): ${stats.quality.largeFilesCount}`,
|
|
289
|
+
` Large files (>= ${(stats.quality.largeThreshold / (1024 * 1024)).toFixed(
|
|
290
|
+
0,
|
|
291
|
+
)} MB): ${stats.quality.largeFilesCount}`,
|
|
331
292
|
);
|
|
332
293
|
console.log(
|
|
333
294
|
` Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
|
|
334
295
|
);
|
|
335
296
|
}
|
|
336
297
|
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
stats.duplicateCandidates.length
|
|
340
|
-
) {
|
|
341
|
-
console.log("\n๐งฌ Duplicate Candidates:");
|
|
298
|
+
if (Array.isArray(stats.duplicateCandidates) && stats.duplicateCandidates.length > 0) {
|
|
299
|
+
console.log('\n๐งฌ Duplicate Candidates:');
|
|
342
300
|
for (const d of stats.duplicateCandidates.slice(0, 2)) {
|
|
343
|
-
console.log(
|
|
344
|
-
` ${d.reason}: ${d.count} files @ ${d.size.toLocaleString()} bytes`,
|
|
345
|
-
);
|
|
301
|
+
console.log(` ${d.reason}: ${d.count} files @ ${d.size.toLocaleString()} bytes`);
|
|
346
302
|
}
|
|
347
303
|
if (stats.duplicateCandidates.length > 2) {
|
|
348
|
-
console.log(
|
|
349
|
-
` โฆ and ${stats.duplicateCandidates.length - 2} more groups`,
|
|
350
|
-
);
|
|
304
|
+
console.log(` โฆ and ${stats.duplicateCandidates.length - 2} more groups`);
|
|
351
305
|
}
|
|
352
306
|
}
|
|
353
307
|
|
|
354
|
-
if (typeof stats.compressibilityRatio ===
|
|
308
|
+
if (typeof stats.compressibilityRatio === 'number') {
|
|
355
309
|
console.log(
|
|
356
|
-
`\n๐๏ธ Compressibility ratio (sampled): ${
|
|
357
|
-
|
|
358
|
-
}%`,
|
|
310
|
+
`\n๐๏ธ Compressibility ratio (sampled): ${(stats.compressibilityRatio * 100).toFixed(
|
|
311
|
+
2,
|
|
312
|
+
)}%`,
|
|
359
313
|
);
|
|
360
314
|
}
|
|
361
315
|
|
|
362
316
|
if (stats.git && stats.git.isRepo) {
|
|
363
|
-
console.log(
|
|
317
|
+
console.log('\n๐ง Git:');
|
|
364
318
|
console.log(
|
|
365
319
|
` Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
|
|
366
320
|
);
|
|
367
321
|
console.log(
|
|
368
322
|
` Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
|
|
369
323
|
);
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
stats.git.lfsCandidates.length
|
|
373
|
-
) {
|
|
374
|
-
console.log(" LFS candidates (top 2):");
|
|
324
|
+
if (Array.isArray(stats.git.lfsCandidates) && stats.git.lfsCandidates.length > 0) {
|
|
325
|
+
console.log(' LFS candidates (top 2):');
|
|
375
326
|
for (const f of stats.git.lfsCandidates.slice(0, 2)) {
|
|
376
327
|
console.log(` ${f.path} (${f.size.toLocaleString()} bytes)`);
|
|
377
328
|
}
|
|
378
329
|
if (stats.git.lfsCandidates.length > 2) {
|
|
379
|
-
console.log(
|
|
380
|
-
` โฆ and ${stats.git.lfsCandidates.length - 2} more`,
|
|
381
|
-
);
|
|
330
|
+
console.log(` โฆ and ${stats.git.lfsCandidates.length - 2} more`);
|
|
382
331
|
}
|
|
383
332
|
}
|
|
384
333
|
}
|
|
385
334
|
|
|
386
|
-
if (Array.isArray(stats.largestFiles) && stats.largestFiles.length) {
|
|
387
|
-
console.log(
|
|
335
|
+
if (Array.isArray(stats.largestFiles) && stats.largestFiles.length > 0) {
|
|
336
|
+
console.log('\n๐ Largest Files (top 2):');
|
|
388
337
|
for (const f of stats.largestFiles.slice(0, 2)) {
|
|
389
338
|
// Show LOC for text files when available; omit ext and mtime
|
|
390
|
-
let locStr =
|
|
339
|
+
let locStr = '';
|
|
391
340
|
if (!f.isBinary && Array.isArray(aggregatedContent?.textFiles)) {
|
|
392
|
-
const tf = aggregatedContent.textFiles.find((t) =>
|
|
393
|
-
|
|
394
|
-
);
|
|
395
|
-
if (tf && typeof tf.lines === "number") {
|
|
341
|
+
const tf = aggregatedContent.textFiles.find((t) => t.path === f.path);
|
|
342
|
+
if (tf && typeof tf.lines === 'number') {
|
|
396
343
|
locStr = `, LOC: ${tf.lines.toLocaleString()}`;
|
|
397
344
|
}
|
|
398
345
|
}
|
|
399
346
|
console.log(
|
|
400
|
-
` ${f.path} โ ${f.sizeFormatted} (${
|
|
401
|
-
f.percentOfTotal.toFixed(2)
|
|
402
|
-
}%)${locStr}`,
|
|
347
|
+
` ${f.path} โ ${f.sizeFormatted} (${f.percentOfTotal.toFixed(2)}%)${locStr}`,
|
|
403
348
|
);
|
|
404
349
|
}
|
|
405
350
|
if (stats.largestFiles.length > 2) {
|
|
@@ -409,262 +354,214 @@ program
|
|
|
409
354
|
|
|
410
355
|
// Write a comprehensive markdown report next to the XML
|
|
411
356
|
{
|
|
412
|
-
const mdPath = outputPath.endsWith(
|
|
413
|
-
? outputPath.replace(/\.xml$/i,
|
|
414
|
-
: outputPath +
|
|
357
|
+
const mdPath = outputPath.endsWith('.xml')
|
|
358
|
+
? outputPath.replace(/\.xml$/i, '.stats.md')
|
|
359
|
+
: outputPath + '.stats.md';
|
|
415
360
|
try {
|
|
416
|
-
const pct = (num, den) => (den ? (
|
|
361
|
+
const pct = (num, den) => (den ? (num / den) * 100 : 0);
|
|
417
362
|
const md = [];
|
|
418
|
-
md.push(`# ๐งพ Flatten Stats for ${path.basename(outputPath)}`);
|
|
419
|
-
md.push("");
|
|
420
|
-
md.push("## ๐ Summary");
|
|
421
|
-
md.push(`- Total source size: ${stats.totalSize}`);
|
|
422
|
-
md.push(`- Generated XML size: ${stats.xmlSize}`);
|
|
423
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}`,
|
|
424
369
|
`- Total lines of code: ${stats.totalLines.toLocaleString()}`,
|
|
425
|
-
|
|
426
|
-
md.push(`- Estimated tokens: ${stats.estimatedTokens}`);
|
|
427
|
-
md.push(
|
|
370
|
+
`- Estimated tokens: ${stats.estimatedTokens}`,
|
|
428
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
|
+
'',
|
|
429
378
|
);
|
|
430
|
-
md.push("");
|
|
431
|
-
|
|
432
|
-
// Percentiles
|
|
433
|
-
md.push("## ๐ Size Percentiles");
|
|
434
|
-
md.push(
|
|
435
|
-
`Avg: ${
|
|
436
|
-
Math.round(stats.avgFileSize).toLocaleString()
|
|
437
|
-
} B, Median: ${
|
|
438
|
-
Math.round(stats.medianFileSize).toLocaleString()
|
|
439
|
-
} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
|
|
440
|
-
);
|
|
441
|
-
md.push("");
|
|
442
379
|
|
|
443
380
|
// Histogram
|
|
444
|
-
if (Array.isArray(stats.histogram) && stats.histogram.length) {
|
|
445
|
-
md.push(
|
|
446
|
-
|
|
447
|
-
|
|
381
|
+
if (Array.isArray(stats.histogram) && stats.histogram.length > 0) {
|
|
382
|
+
md.push(
|
|
383
|
+
'## ๐งฎ Size Histogram',
|
|
384
|
+
'| Bucket | Files | Bytes |',
|
|
385
|
+
'| --- | ---: | ---: |',
|
|
386
|
+
);
|
|
448
387
|
for (const b of stats.histogram) {
|
|
449
|
-
md.push(
|
|
450
|
-
`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`,
|
|
451
|
-
);
|
|
388
|
+
md.push(`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`);
|
|
452
389
|
}
|
|
453
|
-
md.push(
|
|
390
|
+
md.push('');
|
|
454
391
|
}
|
|
455
392
|
|
|
456
393
|
// Top Extensions
|
|
457
|
-
if (Array.isArray(stats.byExtension) && stats.byExtension.length) {
|
|
458
|
-
md.push(
|
|
459
|
-
|
|
460
|
-
|
|
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
|
+
);
|
|
461
400
|
for (const e of stats.byExtension.slice(0, 20)) {
|
|
462
401
|
const p = pct(e.bytes, stats.totalBytes);
|
|
463
402
|
md.push(
|
|
464
|
-
`| ${e.ext} | ${e.count} | ${e.bytes.toLocaleString()} | ${
|
|
465
|
-
p.toFixed(2)
|
|
466
|
-
}% |`,
|
|
403
|
+
`| ${e.ext} | ${e.count} | ${e.bytes.toLocaleString()} | ${p.toFixed(2)}% |`,
|
|
467
404
|
);
|
|
468
405
|
}
|
|
469
|
-
md.push(
|
|
406
|
+
md.push('');
|
|
470
407
|
}
|
|
471
408
|
|
|
472
409
|
// Top Directories
|
|
473
|
-
if (Array.isArray(stats.byDirectory) && stats.byDirectory.length) {
|
|
474
|
-
md.push(
|
|
475
|
-
|
|
476
|
-
|
|
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
|
+
);
|
|
477
416
|
for (const d of stats.byDirectory.slice(0, 20)) {
|
|
478
417
|
const p = pct(d.bytes, stats.totalBytes);
|
|
479
418
|
md.push(
|
|
480
|
-
`| ${d.dir} | ${d.count} | ${d.bytes.toLocaleString()} | ${
|
|
481
|
-
p.toFixed(2)
|
|
482
|
-
}% |`,
|
|
419
|
+
`| ${d.dir} | ${d.count} | ${d.bytes.toLocaleString()} | ${p.toFixed(2)}% |`,
|
|
483
420
|
);
|
|
484
421
|
}
|
|
485
|
-
md.push(
|
|
422
|
+
md.push('');
|
|
486
423
|
}
|
|
487
424
|
|
|
488
425
|
// Depth distribution
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
stats.depthDistribution.length
|
|
492
|
-
) {
|
|
493
|
-
md.push("## ๐ณ Depth Distribution");
|
|
494
|
-
md.push("| Depth | Count |");
|
|
495
|
-
md.push("| ---: | ---: |");
|
|
426
|
+
if (Array.isArray(stats.depthDistribution) && stats.depthDistribution.length > 0) {
|
|
427
|
+
md.push('## ๐ณ Depth Distribution', '| Depth | Count |', '| ---: | ---: |');
|
|
496
428
|
for (const d of stats.depthDistribution) {
|
|
497
429
|
md.push(`| ${d.depth} | ${d.count} |`);
|
|
498
430
|
}
|
|
499
|
-
md.push(
|
|
431
|
+
md.push('');
|
|
500
432
|
}
|
|
501
433
|
|
|
502
434
|
// Longest paths
|
|
503
|
-
if (
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
435
|
+
if (Array.isArray(stats.longestPaths) && stats.longestPaths.length > 0) {
|
|
436
|
+
md.push(
|
|
437
|
+
'## ๐งต Longest Paths (Top 25)',
|
|
438
|
+
'| Path | Length | Bytes |',
|
|
439
|
+
'| --- | ---: | ---: |',
|
|
440
|
+
);
|
|
509
441
|
for (const pth of stats.longestPaths) {
|
|
510
|
-
md.push(
|
|
511
|
-
`| ${pth.path} | ${pth.length} | ${pth.size.toLocaleString()} |`,
|
|
512
|
-
);
|
|
442
|
+
md.push(`| ${pth.path} | ${pth.length} | ${pth.size.toLocaleString()} |`);
|
|
513
443
|
}
|
|
514
|
-
md.push(
|
|
444
|
+
md.push('');
|
|
515
445
|
}
|
|
516
446
|
|
|
517
447
|
// Temporal
|
|
518
448
|
if (stats.temporal) {
|
|
519
|
-
md.push(
|
|
449
|
+
md.push('## โฑ๏ธ Temporal');
|
|
520
450
|
if (stats.temporal.oldest) {
|
|
521
|
-
md.push(
|
|
522
|
-
`- Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`,
|
|
523
|
-
);
|
|
451
|
+
md.push(`- Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`);
|
|
524
452
|
}
|
|
525
453
|
if (stats.temporal.newest) {
|
|
526
|
-
md.push(
|
|
527
|
-
`- Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`,
|
|
528
|
-
);
|
|
454
|
+
md.push(`- Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`);
|
|
529
455
|
}
|
|
530
456
|
if (Array.isArray(stats.temporal.ageBuckets)) {
|
|
531
|
-
md.push(
|
|
532
|
-
md.push("| Age | Files | Bytes |");
|
|
533
|
-
md.push("| --- | ---: | ---: |");
|
|
457
|
+
md.push('', '| Age | Files | Bytes |', '| --- | ---: | ---: |');
|
|
534
458
|
for (const b of stats.temporal.ageBuckets) {
|
|
535
|
-
md.push(
|
|
536
|
-
`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`,
|
|
537
|
-
);
|
|
459
|
+
md.push(`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`);
|
|
538
460
|
}
|
|
539
461
|
}
|
|
540
|
-
md.push(
|
|
462
|
+
md.push('');
|
|
541
463
|
}
|
|
542
464
|
|
|
543
465
|
// Quality signals
|
|
544
466
|
if (stats.quality) {
|
|
545
|
-
md.push("## โ
Quality Signals");
|
|
546
|
-
md.push(`- Zero-byte files: ${stats.quality.zeroByteFiles}`);
|
|
547
|
-
md.push(`- Empty text files: ${stats.quality.emptyTextFiles}`);
|
|
548
|
-
md.push(`- Hidden files: ${stats.quality.hiddenFiles}`);
|
|
549
|
-
md.push(`- Symlinks: ${stats.quality.symlinks}`);
|
|
550
|
-
md.push(
|
|
551
|
-
`- Large files (>= ${
|
|
552
|
-
(stats.quality.largeThreshold / (1024 * 1024)).toFixed(0)
|
|
553
|
-
} MB): ${stats.quality.largeFilesCount}`,
|
|
554
|
-
);
|
|
555
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}`,
|
|
556
474
|
`- Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
|
|
475
|
+
'',
|
|
557
476
|
);
|
|
558
|
-
md.push("");
|
|
559
477
|
}
|
|
560
478
|
|
|
561
479
|
// Duplicates
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
md.push("| --- | ---: | ---: |");
|
|
480
|
+
if (Array.isArray(stats.duplicateCandidates) && stats.duplicateCandidates.length > 0) {
|
|
481
|
+
md.push(
|
|
482
|
+
'## ๐งฌ Duplicate Candidates',
|
|
483
|
+
'| Reason | Files | Size (bytes) |',
|
|
484
|
+
'| --- | ---: | ---: |',
|
|
485
|
+
);
|
|
569
486
|
for (const d of stats.duplicateCandidates) {
|
|
570
|
-
md.push(
|
|
571
|
-
`| ${d.reason} | ${d.count} | ${d.size.toLocaleString()} |`,
|
|
572
|
-
);
|
|
487
|
+
md.push(`| ${d.reason} | ${d.count} | ${d.size.toLocaleString()} |`);
|
|
573
488
|
}
|
|
574
|
-
md.push(
|
|
575
|
-
// Detailed listing of duplicate file names and locations
|
|
576
|
-
md.push("### ๐งฌ Duplicate Groups Details");
|
|
489
|
+
md.push('', '### ๐งฌ Duplicate Groups Details');
|
|
577
490
|
let dupIndex = 1;
|
|
578
491
|
for (const d of stats.duplicateCandidates) {
|
|
579
492
|
md.push(
|
|
580
493
|
`#### Group ${dupIndex}: ${d.count} files @ ${d.size.toLocaleString()} bytes (${d.reason})`,
|
|
581
494
|
);
|
|
582
|
-
if (Array.isArray(d.files) && d.files.length) {
|
|
495
|
+
if (Array.isArray(d.files) && d.files.length > 0) {
|
|
583
496
|
for (const fp of d.files) {
|
|
584
497
|
md.push(`- ${fp}`);
|
|
585
498
|
}
|
|
586
499
|
} else {
|
|
587
|
-
md.push(
|
|
500
|
+
md.push('- (file list unavailable)');
|
|
588
501
|
}
|
|
589
|
-
md.push(
|
|
502
|
+
md.push('');
|
|
590
503
|
dupIndex++;
|
|
591
504
|
}
|
|
592
|
-
md.push(
|
|
505
|
+
md.push('');
|
|
593
506
|
}
|
|
594
507
|
|
|
595
508
|
// Compressibility
|
|
596
|
-
if (typeof stats.compressibilityRatio ===
|
|
597
|
-
md.push("## ๐๏ธ Compressibility");
|
|
509
|
+
if (typeof stats.compressibilityRatio === 'number') {
|
|
598
510
|
md.push(
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
511
|
+
'## ๐๏ธ Compressibility',
|
|
512
|
+
`Sampled compressibility ratio: ${(stats.compressibilityRatio * 100).toFixed(2)}%`,
|
|
513
|
+
'',
|
|
602
514
|
);
|
|
603
|
-
md.push("");
|
|
604
515
|
}
|
|
605
516
|
|
|
606
517
|
// Git
|
|
607
518
|
if (stats.git && stats.git.isRepo) {
|
|
608
|
-
md.push("## ๐ง Git");
|
|
609
519
|
md.push(
|
|
520
|
+
'## ๐ง Git',
|
|
610
521
|
`- Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
|
|
611
|
-
);
|
|
612
|
-
md.push(
|
|
613
522
|
`- Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
|
|
614
523
|
);
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
stats.git.lfsCandidates.length
|
|
618
|
-
) {
|
|
619
|
-
md.push("");
|
|
620
|
-
md.push("### ๐ฆ LFS Candidates (Top 20)");
|
|
621
|
-
md.push("| Path | Bytes |");
|
|
622
|
-
md.push("| --- | ---: |");
|
|
524
|
+
if (Array.isArray(stats.git.lfsCandidates) && stats.git.lfsCandidates.length > 0) {
|
|
525
|
+
md.push('', '### ๐ฆ LFS Candidates (Top 20)', '| Path | Bytes |', '| --- | ---: |');
|
|
623
526
|
for (const f of stats.git.lfsCandidates.slice(0, 20)) {
|
|
624
527
|
md.push(`| ${f.path} | ${f.size.toLocaleString()} |`);
|
|
625
528
|
}
|
|
626
529
|
}
|
|
627
|
-
md.push(
|
|
530
|
+
md.push('');
|
|
628
531
|
}
|
|
629
532
|
|
|
630
533
|
// Largest Files
|
|
631
|
-
if (
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
+
);
|
|
637
540
|
for (const f of stats.largestFiles) {
|
|
638
|
-
let loc =
|
|
639
|
-
if (
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const tf = aggregatedContent.textFiles.find((t) =>
|
|
643
|
-
t.path === f.path
|
|
644
|
-
);
|
|
645
|
-
if (tf && typeof tf.lines === "number") {
|
|
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') {
|
|
646
545
|
loc = tf.lines.toLocaleString();
|
|
647
546
|
}
|
|
648
547
|
}
|
|
649
548
|
md.push(
|
|
650
|
-
`| ${f.path} | ${f.sizeFormatted} | ${
|
|
651
|
-
f.percentOfTotal.toFixed(2)
|
|
652
|
-
}% | ${loc} |`,
|
|
549
|
+
`| ${f.path} | ${f.sizeFormatted} | ${f.percentOfTotal.toFixed(2)}% | ${loc} |`,
|
|
653
550
|
);
|
|
654
551
|
}
|
|
655
|
-
md.push(
|
|
552
|
+
md.push('');
|
|
656
553
|
}
|
|
657
554
|
|
|
658
|
-
await fs.writeFile(mdPath, md.join(
|
|
555
|
+
await fs.writeFile(mdPath, md.join('\n'));
|
|
659
556
|
console.log(`\n๐งพ Detailed stats report written to: ${mdPath}`);
|
|
660
|
-
} catch (
|
|
661
|
-
console.warn(`โ ๏ธ Failed to write stats markdown: ${
|
|
557
|
+
} catch (error) {
|
|
558
|
+
console.warn(`โ ๏ธ Failed to write stats markdown: ${error.message}`);
|
|
662
559
|
}
|
|
663
560
|
}
|
|
664
561
|
}
|
|
665
562
|
} catch (error) {
|
|
666
|
-
console.error(
|
|
667
|
-
console.error(
|
|
563
|
+
console.error('โ Critical error:', error.message);
|
|
564
|
+
console.error('An unexpected error occurred.');
|
|
668
565
|
process.exit(1);
|
|
669
566
|
}
|
|
670
567
|
});
|