@wbern/obscene 1.0.1 → 1.1.0
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/dist/cli.js +85 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -344,6 +344,62 @@ function getNestingDepths(filePaths) {
|
|
|
344
344
|
}
|
|
345
345
|
return depths;
|
|
346
346
|
}
|
|
347
|
+
var RRF_K = 10;
|
|
348
|
+
function computeComposite(rankings, churn, top) {
|
|
349
|
+
const fileScores = /* @__PURE__ */ new Map();
|
|
350
|
+
for (const ranking of Object.values(rankings)) {
|
|
351
|
+
for (let i = 0; i < ranking.entries.length; i++) {
|
|
352
|
+
const file = ranking.entries[i].file;
|
|
353
|
+
const rrf = 1 / (RRF_K + i + 1);
|
|
354
|
+
const existing = fileScores.get(file);
|
|
355
|
+
if (existing) {
|
|
356
|
+
existing.score += rrf;
|
|
357
|
+
existing.dims += 1;
|
|
358
|
+
} else {
|
|
359
|
+
fileScores.set(file, { score: rrf, dims: 1 });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const entries = [];
|
|
364
|
+
for (const [file, data] of fileScores) {
|
|
365
|
+
entries.push({
|
|
366
|
+
file,
|
|
367
|
+
score: Math.round(data.score * 1e4) / 1e4,
|
|
368
|
+
percentOfTotal: 0,
|
|
369
|
+
tier: "stable",
|
|
370
|
+
churn: churn.get(file) ?? 0,
|
|
371
|
+
dimensionCount: data.dims
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
entries.sort((a, b) => b.score - a.score);
|
|
375
|
+
const totalScore = entries.reduce((sum, e) => sum + e.score, 0);
|
|
376
|
+
if (totalScore === 0) {
|
|
377
|
+
return {
|
|
378
|
+
label: "Combined",
|
|
379
|
+
scoreFormula: "reciprocal rank fusion across all dimensions",
|
|
380
|
+
totalScore: 0,
|
|
381
|
+
tierCounts: { danger: 0, watch: 0, stable: 0 },
|
|
382
|
+
totalEntries: 0,
|
|
383
|
+
showing: 0,
|
|
384
|
+
entries: []
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
assignTiers(entries, totalScore);
|
|
388
|
+
const limited = top > 0 ? entries.slice(0, top) : entries;
|
|
389
|
+
const tierCounts = { danger: 0, watch: 0, stable: 0 };
|
|
390
|
+
for (const e of entries) {
|
|
391
|
+
tierCounts[e.tier]++;
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
label: "Combined",
|
|
395
|
+
scoreFormula: "reciprocal rank fusion across all dimensions",
|
|
396
|
+
totalScore: Math.round(totalScore * 1e4) / 1e4,
|
|
397
|
+
tierCounts,
|
|
398
|
+
totalEntries: entries.length,
|
|
399
|
+
showing: limited.length,
|
|
400
|
+
entries: limited
|
|
401
|
+
};
|
|
402
|
+
}
|
|
347
403
|
|
|
348
404
|
// src/color.ts
|
|
349
405
|
import pc from "picocolors";
|
|
@@ -590,10 +646,29 @@ function formatCouplingTable(output) {
|
|
|
590
646
|
lines.push("Docs: https://github.com/wbern/obscene#metrics");
|
|
591
647
|
return lines.join("\n");
|
|
592
648
|
}
|
|
649
|
+
function formatCompositeTable(output) {
|
|
650
|
+
const lines = [];
|
|
651
|
+
lines.push(
|
|
652
|
+
`${output.label} \u2014 Total score: ${output.totalScore.toLocaleString()}`
|
|
653
|
+
);
|
|
654
|
+
lines.push(
|
|
655
|
+
...tierSummary(output.tierCounts, output.showing, output.totalEntries)
|
|
656
|
+
);
|
|
657
|
+
lines.push("");
|
|
658
|
+
lines.push(
|
|
659
|
+
padRight("File", 50) + padLeft("Score", 9) + padLeft("Churn", 7) + padLeft("Dims", 6) + padLeft("Tier", 12)
|
|
660
|
+
);
|
|
661
|
+
lines.push("\u2500".repeat(84));
|
|
662
|
+
for (const entry of output.entries) {
|
|
663
|
+
const rawRow = padRight(truncate(entry.file, 48), 50) + padLeft(entry.score.toFixed(4), 9) + padLeft(String(entry.churn), 7) + padLeft(`${entry.dimensionCount}/4`, 6) + padLeft(tierLabel(entry.tier), 12);
|
|
664
|
+
lines.push(colorRow(entry.tier, rawRow));
|
|
665
|
+
}
|
|
666
|
+
return lines.join("\n");
|
|
667
|
+
}
|
|
593
668
|
|
|
594
669
|
// src/cli.ts
|
|
595
670
|
var program = new Command();
|
|
596
|
-
program.name("obscene").description("Identify hotspot files \u2014 complex code that changes frequently").version("1.0
|
|
671
|
+
program.name("obscene").description("Identify hotspot files \u2014 complex code that changes frequently").version("1.1.0");
|
|
597
672
|
var REPORT_GUIDE = {
|
|
598
673
|
complexity: "Cyclomatic complexity (branch/loop count). NOT a quality judgment \u2014 a 500-line parser will naturally score high. Compare density, not raw values.",
|
|
599
674
|
complexityDensity: "Complexity per line of code. Normalizes for file size. >0.25 suggests dense logic worth reviewing; <0.10 is typical for straightforward code.",
|
|
@@ -605,6 +680,7 @@ var HOTSPOTS_GUIDE = {
|
|
|
605
680
|
nesting: "maxNesting \xD7 churn. Deeply nested code that changes often is harder to reason about.",
|
|
606
681
|
defects: "defects \xD7 churn. Files with fix: commits that also churn heavily may contain latent bugs.",
|
|
607
682
|
authors: "authors \xD7 churn. Files touched by many authors and changing often may lack clear ownership.",
|
|
683
|
+
composite: "Combined ranking using Reciprocal Rank Fusion (RRF) across all dimensions. Files appearing near the top of multiple rankings score highest.",
|
|
608
684
|
tier: "Relative ranking within THIS codebase (top 50% = danger, next 30% = watch, bottom 20% = stable). NOT an absolute quality grade."
|
|
609
685
|
};
|
|
610
686
|
var COUPLING_GUIDE = {
|
|
@@ -695,15 +771,22 @@ function runHotspots(opts) {
|
|
|
695
771
|
authors,
|
|
696
772
|
top
|
|
697
773
|
);
|
|
774
|
+
const composite = computeComposite(rankings, churn, top);
|
|
698
775
|
const output = {
|
|
699
776
|
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
700
777
|
guide: HOTSPOTS_GUIDE,
|
|
701
778
|
churnWindow: `${months} months`,
|
|
702
|
-
rankings
|
|
779
|
+
rankings,
|
|
780
|
+
composite
|
|
703
781
|
};
|
|
704
782
|
if (opts.format === "table") {
|
|
705
783
|
process.stdout.write(`${formatHotspotsTable(output)}
|
|
706
784
|
`);
|
|
785
|
+
if (composite.entries.length > 0) {
|
|
786
|
+
process.stdout.write(`
|
|
787
|
+
${formatCompositeTable(composite)}
|
|
788
|
+
`);
|
|
789
|
+
}
|
|
707
790
|
} else {
|
|
708
791
|
process.stdout.write(`${JSON.stringify(output, null, 2)}
|
|
709
792
|
`);
|