bmad-method 5.0.0-beta.1 โ 5.0.0-beta.2
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/CHANGELOG.md +2 -19
- package/package.json +1 -1
- package/tools/flattener/main.js +474 -15
- package/tools/flattener/projectRoot.js +182 -23
- package/tools/flattener/stats.helpers.js +331 -0
- package/tools/flattener/stats.js +64 -14
- package/tools/flattener/test-matrix.js +405 -0
- package/tools/installer/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
# [5.0.0-beta.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
### Bug Fixes
|
|
5
|
-
|
|
6
|
-
* add permissions and authentication for promotion workflow ([7f016d0](https://github.com/bmadcode/BMAD-METHOD/commit/7f016d0020705c2a048b656eeaaf9bd1762e4914))
|
|
7
|
-
* resolve CommonJS import compatibility for chalk, inquirer, and ora ([#442](https://github.com/bmadcode/BMAD-METHOD/issues/442)) ([33269c8](https://github.com/bmadcode/BMAD-METHOD/commit/33269c888d930d197ab47a3ec1d8a66c5469c43b))
|
|
8
|
-
* update package-lock.json for semver dependency ([6cb2fa6](https://github.com/bmadcode/BMAD-METHOD/commit/6cb2fa68b305dfe7eac052cd32d84839c57fb321))
|
|
9
|
-
* update versions for dual publishing beta releases ([e0dcbcf](https://github.com/bmadcode/BMAD-METHOD/commit/e0dcbcf5277ac33a824b445060177fd3e71f13d4))
|
|
1
|
+
# [5.0.0-beta.2](https://github.com/bmadcode/BMAD-METHOD/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2025-08-16)
|
|
10
2
|
|
|
11
3
|
|
|
12
4
|
### Features
|
|
13
5
|
|
|
14
|
-
*
|
|
15
|
-
* publish stable release 5.0.0 ([93426c2](https://github.com/bmadcode/BMAD-METHOD/commit/93426c2d2f046ce37a9c491d1f55fe9f7a2566d8))
|
|
16
|
-
* transform QA agent into Test Architect with advanced quality caโฆ ([#433](https://github.com/bmadcode/BMAD-METHOD/issues/433)) ([0b61175](https://github.com/bmadcode/BMAD-METHOD/commit/0b61175d98e6def508cc82bb4539e7f37f8f6e1a))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
### BREAKING CHANGES
|
|
20
|
-
|
|
21
|
-
* Promote beta features to stable release for v5.0.0
|
|
22
|
-
|
|
23
|
-
This commit ensures the stable release gets properly published to NPM and GitHub releases.
|
|
6
|
+
* **flattener:** prompt for detailed stats; polish .stats.md with emojis ([#422](https://github.com/bmadcode/BMAD-METHOD/issues/422)) ([fab9d5e](https://github.com/bmadcode/BMAD-METHOD/commit/fab9d5e1f55d7876b6909002415af89508cc41a7))
|
|
24
7
|
|
|
25
8
|
## [4.36.2](https://github.com/bmadcode/BMAD-METHOD/compare/v4.36.1...v4.36.2) (2025-08-10)
|
|
26
9
|
|
package/package.json
CHANGED
package/tools/flattener/main.js
CHANGED
|
@@ -127,19 +127,11 @@ program
|
|
|
127
127
|
path.join(inputDir, "flattened-codebase.xml"),
|
|
128
128
|
);
|
|
129
129
|
}
|
|
130
|
-
} else {
|
|
131
|
-
console.error(
|
|
132
|
-
"Could not auto-detect a project root and no arguments were provided. Please specify -i/--input and -o/--output.",
|
|
133
|
-
);
|
|
134
|
-
process.exit(1);
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
// Ensure output directory exists
|
|
138
133
|
await fs.ensureDir(path.dirname(outputPath));
|
|
139
134
|
|
|
140
|
-
console.log(`Flattening codebase from: ${inputDir}`);
|
|
141
|
-
console.log(`Output file: ${outputPath}`);
|
|
142
|
-
|
|
143
135
|
try {
|
|
144
136
|
// Verify input directory exists
|
|
145
137
|
if (!await fs.pathExists(inputDir)) {
|
|
@@ -159,7 +151,6 @@ program
|
|
|
159
151
|
);
|
|
160
152
|
|
|
161
153
|
// Process files with progress tracking
|
|
162
|
-
console.log("Reading file contents");
|
|
163
154
|
const processingSpinner = ora("๐ Processing files...").start();
|
|
164
155
|
const aggregatedContent = await aggregateFileContents(
|
|
165
156
|
filteredFiles,
|
|
@@ -172,10 +163,6 @@ program
|
|
|
172
163
|
if (aggregatedContent.errors.length > 0) {
|
|
173
164
|
console.log(`Errors: ${aggregatedContent.errors.length}`);
|
|
174
165
|
}
|
|
175
|
-
console.log(`Text files: ${aggregatedContent.textFiles.length}`);
|
|
176
|
-
if (aggregatedContent.binaryFiles.length > 0) {
|
|
177
|
-
console.log(`Binary files: ${aggregatedContent.binaryFiles.length}`);
|
|
178
|
-
}
|
|
179
166
|
|
|
180
167
|
// Generate XML output using streaming
|
|
181
168
|
const xmlSpinner = ora("๐ง Generating XML output...").start();
|
|
@@ -184,7 +171,11 @@ program
|
|
|
184
171
|
|
|
185
172
|
// Calculate and display statistics
|
|
186
173
|
const outputStats = await fs.stat(outputPath);
|
|
187
|
-
const stats = calculateStatistics(
|
|
174
|
+
const stats = await calculateStatistics(
|
|
175
|
+
aggregatedContent,
|
|
176
|
+
outputStats.size,
|
|
177
|
+
inputDir,
|
|
178
|
+
);
|
|
188
179
|
|
|
189
180
|
// Display completion summary
|
|
190
181
|
console.log("\n๐ Completion Summary:");
|
|
@@ -201,8 +192,476 @@ program
|
|
|
201
192
|
);
|
|
202
193
|
console.log(`๐ข Estimated tokens: ${stats.estimatedTokens}`);
|
|
203
194
|
console.log(
|
|
204
|
-
`๐ File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`,
|
|
195
|
+
`๐ File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors\n`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Ask user if they want detailed stats + markdown report
|
|
199
|
+
const generateDetailed = await promptYesNo(
|
|
200
|
+
"Generate detailed stats (console + markdown) now?",
|
|
201
|
+
true,
|
|
205
202
|
);
|
|
203
|
+
|
|
204
|
+
if (generateDetailed) {
|
|
205
|
+
// Additional detailed stats
|
|
206
|
+
console.log("\n๐ Size Percentiles:");
|
|
207
|
+
console.log(
|
|
208
|
+
` Avg: ${
|
|
209
|
+
Math.round(stats.avgFileSize).toLocaleString()
|
|
210
|
+
} B, Median: ${
|
|
211
|
+
Math.round(stats.medianFileSize).toLocaleString()
|
|
212
|
+
} B, p90: ${stats.p90.toLocaleString()} B, p95: ${stats.p95.toLocaleString()} B, p99: ${stats.p99.toLocaleString()} B`,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (Array.isArray(stats.histogram) && stats.histogram.length) {
|
|
216
|
+
console.log("\n๐งฎ Size Histogram:");
|
|
217
|
+
for (const b of stats.histogram.slice(0, 2)) {
|
|
218
|
+
console.log(
|
|
219
|
+
` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (stats.histogram.length > 2) {
|
|
223
|
+
console.log(` โฆ and ${stats.histogram.length - 2} more buckets`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (Array.isArray(stats.byExtension) && stats.byExtension.length) {
|
|
228
|
+
const topExt = stats.byExtension.slice(0, 2);
|
|
229
|
+
console.log("\n๐ฆ Top Extensions:");
|
|
230
|
+
for (const e of topExt) {
|
|
231
|
+
const pct = stats.totalBytes
|
|
232
|
+
? ((e.bytes / stats.totalBytes) * 100)
|
|
233
|
+
: 0;
|
|
234
|
+
console.log(
|
|
235
|
+
` ${e.ext}: ${e.count} files, ${e.bytes.toLocaleString()} bytes (${
|
|
236
|
+
pct.toFixed(2)
|
|
237
|
+
}%)`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (stats.byExtension.length > 2) {
|
|
241
|
+
console.log(
|
|
242
|
+
` โฆ and ${stats.byExtension.length - 2} more extensions`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (Array.isArray(stats.byDirectory) && stats.byDirectory.length) {
|
|
248
|
+
const topDir = stats.byDirectory.slice(0, 2);
|
|
249
|
+
console.log("\n๐ Top Directories:");
|
|
250
|
+
for (const d of topDir) {
|
|
251
|
+
const pct = stats.totalBytes
|
|
252
|
+
? ((d.bytes / stats.totalBytes) * 100)
|
|
253
|
+
: 0;
|
|
254
|
+
console.log(
|
|
255
|
+
` ${d.dir}: ${d.count} files, ${d.bytes.toLocaleString()} bytes (${
|
|
256
|
+
pct.toFixed(2)
|
|
257
|
+
}%)`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (stats.byDirectory.length > 2) {
|
|
261
|
+
console.log(
|
|
262
|
+
` โฆ and ${stats.byDirectory.length - 2} more directories`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
Array.isArray(stats.depthDistribution) &&
|
|
269
|
+
stats.depthDistribution.length
|
|
270
|
+
) {
|
|
271
|
+
console.log("\n๐ณ Depth Distribution:");
|
|
272
|
+
const dd = stats.depthDistribution.slice(0, 2);
|
|
273
|
+
let line = " " + dd.map((d) => `${d.depth}:${d.count}`).join(" ");
|
|
274
|
+
if (stats.depthDistribution.length > 2) {
|
|
275
|
+
line += ` โฆ +${stats.depthDistribution.length - 2} more`;
|
|
276
|
+
}
|
|
277
|
+
console.log(line);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (Array.isArray(stats.longestPaths) && stats.longestPaths.length) {
|
|
281
|
+
console.log("\n๐งต Longest Paths:");
|
|
282
|
+
for (const p of stats.longestPaths.slice(0, 2)) {
|
|
283
|
+
console.log(
|
|
284
|
+
` ${p.path} (${p.length} chars, ${p.size.toLocaleString()} bytes)`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (stats.longestPaths.length > 2) {
|
|
288
|
+
console.log(` โฆ and ${stats.longestPaths.length - 2} more paths`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (stats.temporal) {
|
|
293
|
+
console.log("\nโฑ๏ธ Temporal:");
|
|
294
|
+
if (stats.temporal.oldest) {
|
|
295
|
+
console.log(
|
|
296
|
+
` Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (stats.temporal.newest) {
|
|
300
|
+
console.log(
|
|
301
|
+
` Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(stats.temporal.ageBuckets)) {
|
|
305
|
+
console.log(" Age buckets:");
|
|
306
|
+
for (const b of stats.temporal.ageBuckets.slice(0, 2)) {
|
|
307
|
+
console.log(
|
|
308
|
+
` ${b.label}: ${b.count} files, ${b.bytes.toLocaleString()} bytes`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (stats.temporal.ageBuckets.length > 2) {
|
|
312
|
+
console.log(
|
|
313
|
+
` โฆ and ${
|
|
314
|
+
stats.temporal.ageBuckets.length - 2
|
|
315
|
+
} more buckets`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (stats.quality) {
|
|
322
|
+
console.log("\nโ
Quality Signals:");
|
|
323
|
+
console.log(` Zero-byte files: ${stats.quality.zeroByteFiles}`);
|
|
324
|
+
console.log(` Empty text files: ${stats.quality.emptyTextFiles}`);
|
|
325
|
+
console.log(` Hidden files: ${stats.quality.hiddenFiles}`);
|
|
326
|
+
console.log(` Symlinks: ${stats.quality.symlinks}`);
|
|
327
|
+
console.log(
|
|
328
|
+
` Large files (>= ${
|
|
329
|
+
(stats.quality.largeThreshold / (1024 * 1024)).toFixed(0)
|
|
330
|
+
} MB): ${stats.quality.largeFilesCount}`,
|
|
331
|
+
);
|
|
332
|
+
console.log(
|
|
333
|
+
` Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (
|
|
338
|
+
Array.isArray(stats.duplicateCandidates) &&
|
|
339
|
+
stats.duplicateCandidates.length
|
|
340
|
+
) {
|
|
341
|
+
console.log("\n๐งฌ Duplicate Candidates:");
|
|
342
|
+
for (const d of stats.duplicateCandidates.slice(0, 2)) {
|
|
343
|
+
console.log(
|
|
344
|
+
` ${d.reason}: ${d.count} files @ ${d.size.toLocaleString()} bytes`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (stats.duplicateCandidates.length > 2) {
|
|
348
|
+
console.log(
|
|
349
|
+
` โฆ and ${stats.duplicateCandidates.length - 2} more groups`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (typeof stats.compressibilityRatio === "number") {
|
|
355
|
+
console.log(
|
|
356
|
+
`\n๐๏ธ Compressibility ratio (sampled): ${
|
|
357
|
+
(stats.compressibilityRatio * 100).toFixed(2)
|
|
358
|
+
}%`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (stats.git && stats.git.isRepo) {
|
|
363
|
+
console.log("\n๐ง Git:");
|
|
364
|
+
console.log(
|
|
365
|
+
` Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
|
|
366
|
+
);
|
|
367
|
+
console.log(
|
|
368
|
+
` Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
|
|
369
|
+
);
|
|
370
|
+
if (
|
|
371
|
+
Array.isArray(stats.git.lfsCandidates) &&
|
|
372
|
+
stats.git.lfsCandidates.length
|
|
373
|
+
) {
|
|
374
|
+
console.log(" LFS candidates (top 2):");
|
|
375
|
+
for (const f of stats.git.lfsCandidates.slice(0, 2)) {
|
|
376
|
+
console.log(` ${f.path} (${f.size.toLocaleString()} bytes)`);
|
|
377
|
+
}
|
|
378
|
+
if (stats.git.lfsCandidates.length > 2) {
|
|
379
|
+
console.log(
|
|
380
|
+
` โฆ and ${stats.git.lfsCandidates.length - 2} more`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (Array.isArray(stats.largestFiles) && stats.largestFiles.length) {
|
|
387
|
+
console.log("\n๐ Largest Files (top 2):");
|
|
388
|
+
for (const f of stats.largestFiles.slice(0, 2)) {
|
|
389
|
+
// Show LOC for text files when available; omit ext and mtime
|
|
390
|
+
let locStr = "";
|
|
391
|
+
if (!f.isBinary && Array.isArray(aggregatedContent?.textFiles)) {
|
|
392
|
+
const tf = aggregatedContent.textFiles.find((t) =>
|
|
393
|
+
t.path === f.path
|
|
394
|
+
);
|
|
395
|
+
if (tf && typeof tf.lines === "number") {
|
|
396
|
+
locStr = `, LOC: ${tf.lines.toLocaleString()}`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
console.log(
|
|
400
|
+
` ${f.path} โ ${f.sizeFormatted} (${
|
|
401
|
+
f.percentOfTotal.toFixed(2)
|
|
402
|
+
}%)${locStr}`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (stats.largestFiles.length > 2) {
|
|
406
|
+
console.log(` โฆ and ${stats.largestFiles.length - 2} more files`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Write a comprehensive markdown report next to the XML
|
|
411
|
+
{
|
|
412
|
+
const mdPath = outputPath.endsWith(".xml")
|
|
413
|
+
? outputPath.replace(/\.xml$/i, ".stats.md")
|
|
414
|
+
: outputPath + ".stats.md";
|
|
415
|
+
try {
|
|
416
|
+
const pct = (num, den) => (den ? ((num / den) * 100) : 0);
|
|
417
|
+
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
|
+
md.push(
|
|
424
|
+
`- Total lines of code: ${stats.totalLines.toLocaleString()}`,
|
|
425
|
+
);
|
|
426
|
+
md.push(`- Estimated tokens: ${stats.estimatedTokens}`);
|
|
427
|
+
md.push(
|
|
428
|
+
`- File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`,
|
|
429
|
+
);
|
|
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
|
+
|
|
443
|
+
// Histogram
|
|
444
|
+
if (Array.isArray(stats.histogram) && stats.histogram.length) {
|
|
445
|
+
md.push("## ๐งฎ Size Histogram");
|
|
446
|
+
md.push("| Bucket | Files | Bytes |");
|
|
447
|
+
md.push("| --- | ---: | ---: |");
|
|
448
|
+
for (const b of stats.histogram) {
|
|
449
|
+
md.push(
|
|
450
|
+
`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
md.push("");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Top Extensions
|
|
457
|
+
if (Array.isArray(stats.byExtension) && stats.byExtension.length) {
|
|
458
|
+
md.push("## ๐ฆ Top Extensions by Bytes (Top 20)");
|
|
459
|
+
md.push("| Ext | Files | Bytes | % of total |");
|
|
460
|
+
md.push("| --- | ---: | ---: | ---: |");
|
|
461
|
+
for (const e of stats.byExtension.slice(0, 20)) {
|
|
462
|
+
const p = pct(e.bytes, stats.totalBytes);
|
|
463
|
+
md.push(
|
|
464
|
+
`| ${e.ext} | ${e.count} | ${e.bytes.toLocaleString()} | ${
|
|
465
|
+
p.toFixed(2)
|
|
466
|
+
}% |`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
md.push("");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Top Directories
|
|
473
|
+
if (Array.isArray(stats.byDirectory) && stats.byDirectory.length) {
|
|
474
|
+
md.push("## ๐ Top Directories by Bytes (Top 20)");
|
|
475
|
+
md.push("| Directory | Files | Bytes | % of total |");
|
|
476
|
+
md.push("| --- | ---: | ---: | ---: |");
|
|
477
|
+
for (const d of stats.byDirectory.slice(0, 20)) {
|
|
478
|
+
const p = pct(d.bytes, stats.totalBytes);
|
|
479
|
+
md.push(
|
|
480
|
+
`| ${d.dir} | ${d.count} | ${d.bytes.toLocaleString()} | ${
|
|
481
|
+
p.toFixed(2)
|
|
482
|
+
}% |`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
md.push("");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Depth distribution
|
|
489
|
+
if (
|
|
490
|
+
Array.isArray(stats.depthDistribution) &&
|
|
491
|
+
stats.depthDistribution.length
|
|
492
|
+
) {
|
|
493
|
+
md.push("## ๐ณ Depth Distribution");
|
|
494
|
+
md.push("| Depth | Count |");
|
|
495
|
+
md.push("| ---: | ---: |");
|
|
496
|
+
for (const d of stats.depthDistribution) {
|
|
497
|
+
md.push(`| ${d.depth} | ${d.count} |`);
|
|
498
|
+
}
|
|
499
|
+
md.push("");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Longest paths
|
|
503
|
+
if (
|
|
504
|
+
Array.isArray(stats.longestPaths) && stats.longestPaths.length
|
|
505
|
+
) {
|
|
506
|
+
md.push("## ๐งต Longest Paths (Top 25)");
|
|
507
|
+
md.push("| Path | Length | Bytes |");
|
|
508
|
+
md.push("| --- | ---: | ---: |");
|
|
509
|
+
for (const pth of stats.longestPaths) {
|
|
510
|
+
md.push(
|
|
511
|
+
`| ${pth.path} | ${pth.length} | ${pth.size.toLocaleString()} |`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
md.push("");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Temporal
|
|
518
|
+
if (stats.temporal) {
|
|
519
|
+
md.push("## โฑ๏ธ Temporal");
|
|
520
|
+
if (stats.temporal.oldest) {
|
|
521
|
+
md.push(
|
|
522
|
+
`- Oldest: ${stats.temporal.oldest.path} (${stats.temporal.oldest.mtime})`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
if (stats.temporal.newest) {
|
|
526
|
+
md.push(
|
|
527
|
+
`- Newest: ${stats.temporal.newest.path} (${stats.temporal.newest.mtime})`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (Array.isArray(stats.temporal.ageBuckets)) {
|
|
531
|
+
md.push("");
|
|
532
|
+
md.push("| Age | Files | Bytes |");
|
|
533
|
+
md.push("| --- | ---: | ---: |");
|
|
534
|
+
for (const b of stats.temporal.ageBuckets) {
|
|
535
|
+
md.push(
|
|
536
|
+
`| ${b.label} | ${b.count} | ${b.bytes.toLocaleString()} |`,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
md.push("");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Quality signals
|
|
544
|
+
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
|
+
md.push(
|
|
556
|
+
`- Suspiciously large files (>= 100 MB): ${stats.quality.suspiciousLargeFilesCount}`,
|
|
557
|
+
);
|
|
558
|
+
md.push("");
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Duplicates
|
|
562
|
+
if (
|
|
563
|
+
Array.isArray(stats.duplicateCandidates) &&
|
|
564
|
+
stats.duplicateCandidates.length
|
|
565
|
+
) {
|
|
566
|
+
md.push("## ๐งฌ Duplicate Candidates");
|
|
567
|
+
md.push("| Reason | Files | Size (bytes) |");
|
|
568
|
+
md.push("| --- | ---: | ---: |");
|
|
569
|
+
for (const d of stats.duplicateCandidates) {
|
|
570
|
+
md.push(
|
|
571
|
+
`| ${d.reason} | ${d.count} | ${d.size.toLocaleString()} |`,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
md.push("");
|
|
575
|
+
// Detailed listing of duplicate file names and locations
|
|
576
|
+
md.push("### ๐งฌ Duplicate Groups Details");
|
|
577
|
+
let dupIndex = 1;
|
|
578
|
+
for (const d of stats.duplicateCandidates) {
|
|
579
|
+
md.push(
|
|
580
|
+
`#### Group ${dupIndex}: ${d.count} files @ ${d.size.toLocaleString()} bytes (${d.reason})`,
|
|
581
|
+
);
|
|
582
|
+
if (Array.isArray(d.files) && d.files.length) {
|
|
583
|
+
for (const fp of d.files) {
|
|
584
|
+
md.push(`- ${fp}`);
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
md.push("- (file list unavailable)");
|
|
588
|
+
}
|
|
589
|
+
md.push("");
|
|
590
|
+
dupIndex++;
|
|
591
|
+
}
|
|
592
|
+
md.push("");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Compressibility
|
|
596
|
+
if (typeof stats.compressibilityRatio === "number") {
|
|
597
|
+
md.push("## ๐๏ธ Compressibility");
|
|
598
|
+
md.push(
|
|
599
|
+
`Sampled compressibility ratio: ${
|
|
600
|
+
(stats.compressibilityRatio * 100).toFixed(2)
|
|
601
|
+
}%`,
|
|
602
|
+
);
|
|
603
|
+
md.push("");
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Git
|
|
607
|
+
if (stats.git && stats.git.isRepo) {
|
|
608
|
+
md.push("## ๐ง Git");
|
|
609
|
+
md.push(
|
|
610
|
+
`- Tracked: ${stats.git.trackedCount} files, ${stats.git.trackedBytes.toLocaleString()} bytes`,
|
|
611
|
+
);
|
|
612
|
+
md.push(
|
|
613
|
+
`- Untracked: ${stats.git.untrackedCount} files, ${stats.git.untrackedBytes.toLocaleString()} bytes`,
|
|
614
|
+
);
|
|
615
|
+
if (
|
|
616
|
+
Array.isArray(stats.git.lfsCandidates) &&
|
|
617
|
+
stats.git.lfsCandidates.length
|
|
618
|
+
) {
|
|
619
|
+
md.push("");
|
|
620
|
+
md.push("### ๐ฆ LFS Candidates (Top 20)");
|
|
621
|
+
md.push("| Path | Bytes |");
|
|
622
|
+
md.push("| --- | ---: |");
|
|
623
|
+
for (const f of stats.git.lfsCandidates.slice(0, 20)) {
|
|
624
|
+
md.push(`| ${f.path} | ${f.size.toLocaleString()} |`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
md.push("");
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Largest Files
|
|
631
|
+
if (
|
|
632
|
+
Array.isArray(stats.largestFiles) && stats.largestFiles.length
|
|
633
|
+
) {
|
|
634
|
+
md.push("## ๐ Largest Files (Top 50)");
|
|
635
|
+
md.push("| Path | Size | % of total | LOC |");
|
|
636
|
+
md.push("| --- | ---: | ---: | ---: |");
|
|
637
|
+
for (const f of stats.largestFiles) {
|
|
638
|
+
let loc = "";
|
|
639
|
+
if (
|
|
640
|
+
!f.isBinary && Array.isArray(aggregatedContent?.textFiles)
|
|
641
|
+
) {
|
|
642
|
+
const tf = aggregatedContent.textFiles.find((t) =>
|
|
643
|
+
t.path === f.path
|
|
644
|
+
);
|
|
645
|
+
if (tf && typeof tf.lines === "number") {
|
|
646
|
+
loc = tf.lines.toLocaleString();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
md.push(
|
|
650
|
+
`| ${f.path} | ${f.sizeFormatted} | ${
|
|
651
|
+
f.percentOfTotal.toFixed(2)
|
|
652
|
+
}% | ${loc} |`,
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
md.push("");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
await fs.writeFile(mdPath, md.join("\n"));
|
|
659
|
+
console.log(`\n๐งพ Detailed stats report written to: ${mdPath}`);
|
|
660
|
+
} catch (e) {
|
|
661
|
+
console.warn(`โ ๏ธ Failed to write stats markdown: ${e.message}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
206
665
|
} catch (error) {
|
|
207
666
|
console.error("โ Critical error:", error.message);
|
|
208
667
|
console.error("An unexpected error occurred.");
|