codebrief 1.1.1 → 1.2.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/index.js +142 -73
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2983,47 +2983,21 @@ function printSummary(files, ctx, snapshot, analysis) {
|
|
|
2983
2983
|
const savings = Math.round(
|
|
2984
2984
|
(explorationTokens - afterTotal) / explorationTokens * 100
|
|
2985
2985
|
);
|
|
2986
|
-
const
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
const
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
const afterContentWidth = maxCostLabel + 3 + maxCostDesc + 2 + maxCostValue;
|
|
2997
|
-
const beforeValue = `~${formatNumber(explorationTokens)} tokens`;
|
|
2998
|
-
const beforeLabel = "Exploration to understand codebase";
|
|
2999
|
-
const beforeGap = afterContentWidth - beforeLabel.length - beforeValue.length;
|
|
3000
|
-
console.log(pc.dim(" Before (no context files):"));
|
|
3001
|
-
if (beforeGap >= 2) {
|
|
3002
|
-
console.log(
|
|
3003
|
-
` ${beforeLabel}${" ".repeat(beforeGap)}${pc.red(beforeValue)}`
|
|
3004
|
-
);
|
|
3005
|
-
} else {
|
|
3006
|
-
console.log(
|
|
3007
|
-
` ${beforeLabel} ${pc.red(beforeValue)}`
|
|
3008
|
-
);
|
|
3009
|
-
}
|
|
3010
|
-
console.log("");
|
|
3011
|
-
console.log(pc.dim(" After:"));
|
|
3012
|
-
for (const row of costRows) {
|
|
3013
|
-
console.log(
|
|
3014
|
-
` ${row.label.padEnd(maxCostLabel)} ${row.desc.padEnd(maxCostDesc)} ${pc.green(row.value.padStart(maxCostValue))}`
|
|
3015
|
-
);
|
|
3016
|
-
}
|
|
3017
|
-
const totalText = `Total: ~${formatNumber(afterTotal)} tokens`;
|
|
3018
|
-
const totalPad = afterContentWidth > totalText.length ? " ".repeat(afterContentWidth - totalText.length) : "";
|
|
3019
|
-
console.log(
|
|
3020
|
-
` ${totalPad}${pc.bold(totalText)}`
|
|
3021
|
-
);
|
|
2986
|
+
const BAR_MAX = 40;
|
|
2987
|
+
const maxVal = Math.max(explorationTokens, afterTotal);
|
|
2988
|
+
const beforeBarLen = Math.max(1, Math.round(explorationTokens / maxVal * BAR_MAX));
|
|
2989
|
+
const afterBarLen = Math.max(1, Math.round(afterTotal / maxVal * BAR_MAX));
|
|
2990
|
+
const BLOCK = "\u2588";
|
|
2991
|
+
const beforeBar = BLOCK.repeat(beforeBarLen);
|
|
2992
|
+
const afterBar = BLOCK.repeat(afterBarLen);
|
|
2993
|
+
const afterPad = " ".repeat(Math.max(0, beforeBarLen - afterBarLen));
|
|
2994
|
+
console.log(` Before: ${pc.red(beforeBar)} ~${formatNumber(explorationTokens)} tokens`);
|
|
2995
|
+
console.log(` After: ${pc.green(afterBar)}${afterPad} ~${formatNumber(afterTotal)} tokens`);
|
|
3022
2996
|
console.log("");
|
|
3023
2997
|
if (savings > 0) {
|
|
3024
2998
|
console.log(
|
|
3025
2999
|
pc.green(
|
|
3026
|
-
` Estimated savings: ~${savings}% fewer tokens
|
|
3000
|
+
` Estimated savings: ~${savings}% fewer tokens`
|
|
3027
3001
|
)
|
|
3028
3002
|
);
|
|
3029
3003
|
}
|
|
@@ -3067,20 +3041,39 @@ function printSummary(files, ctx, snapshot, analysis) {
|
|
|
3067
3041
|
);
|
|
3068
3042
|
}
|
|
3069
3043
|
}
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
);
|
|
3044
|
+
if (analysis) {
|
|
3045
|
+
const findings = [];
|
|
3046
|
+
if (analysis.circularDeps.length > 0) {
|
|
3047
|
+
for (const c of analysis.circularDeps.slice(0, 3)) {
|
|
3048
|
+
const names = c.chain.map((f) => f.split("/").pop()?.replace(/\.[jt]sx?$/, "") ?? f);
|
|
3049
|
+
findings.push(`${analysis.circularDeps.length > 1 ? "" : ""}1 circular dependency chain (${names.slice(0, 2).join(" \u2194 ")})`);
|
|
3050
|
+
}
|
|
3051
|
+
if (analysis.circularDeps.length > 1) {
|
|
3052
|
+
findings[0] = `${analysis.circularDeps.length} circular dependency chain${analysis.circularDeps.length === 1 ? "" : "s"}`;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
const highInstabilityFiles = analysis.instabilities.filter((f) => f.instability > 0.8);
|
|
3056
|
+
if (highInstabilityFiles.length > 0) {
|
|
3057
|
+
findings.push(`${highInstabilityFiles.length} high-instability file${highInstabilityFiles.length === 1 ? "" : "s"}`);
|
|
3058
|
+
}
|
|
3059
|
+
const ec = analysis.exportCoverage;
|
|
3060
|
+
if (ec && ec.length > 0) {
|
|
3061
|
+
const totalExports = ec.reduce((sum, e) => sum + e.totalExports, 0);
|
|
3062
|
+
const totalUsed = ec.reduce((sum, e) => sum + e.usedExports, 0);
|
|
3063
|
+
const unusedExports = totalExports - totalUsed;
|
|
3064
|
+
const filesWithUnused = ec.filter((e) => e.usedExports < e.totalExports).length;
|
|
3065
|
+
if (unusedExports > 0) {
|
|
3066
|
+
findings.push(`${unusedExports} unused export${unusedExports === 1 ? "" : "s"} in ${filesWithUnused} file${filesWithUnused === 1 ? "" : "s"}`);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
console.log("");
|
|
3070
|
+
if (findings.length > 0) {
|
|
3071
|
+
console.log(pc.yellow(` \u26A0 ${findings.length} finding${findings.length === 1 ? "" : "s"}`));
|
|
3072
|
+
for (const f of findings) {
|
|
3073
|
+
console.log(pc.dim(` \u25CF ${f}`));
|
|
3074
|
+
}
|
|
3075
|
+
} else {
|
|
3076
|
+
console.log(pc.green(` \u2713 No structural issues detected`));
|
|
3084
3077
|
}
|
|
3085
3078
|
}
|
|
3086
3079
|
console.log("");
|
|
@@ -3504,35 +3497,111 @@ async function main() {
|
|
|
3504
3497
|
const fileCount = graph.centrality.size;
|
|
3505
3498
|
spinner3.start(`Running PageRank on ${fileCount} files...`);
|
|
3506
3499
|
const hubFiles = getHubFiles(graph);
|
|
3507
|
-
|
|
3508
|
-
spinner3.
|
|
3500
|
+
const topHubName = hubFiles[0]?.path ?? "";
|
|
3501
|
+
spinner3.stop(
|
|
3502
|
+
hubFiles.length > 0 ? `${pc3.green("PageRank")} found ${pc3.bold(String(hubFiles.length))} hub files` + (topHubName ? pc3.dim(` (top: ${topHubName})`) : "") : `${pc3.green("PageRank")} ${pc3.dim("no hub files detected")}`
|
|
3503
|
+
);
|
|
3504
|
+
if (verbose && hubFiles.length > 0) {
|
|
3505
|
+
for (const h of hubFiles.slice(0, 5)) {
|
|
3506
|
+
p4.log.info(pc3.dim(` ${h.path} (centrality: ${h.centrality.toFixed(3)}, imported by ${h.importedBy})`));
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
spinner3.start("Finding circular dependencies...");
|
|
3509
3510
|
const circularDeps = findCircularDeps(graph);
|
|
3510
|
-
|
|
3511
|
-
|
|
3511
|
+
spinner3.stop(
|
|
3512
|
+
circularDeps.length === 0 ? `${pc3.green("Tarjan SCC")} no cycles found ${pc3.green("\u2713")}` : `${pc3.yellow("Tarjan SCC")} ${pc3.bold(String(circularDeps.length))} cycle${circularDeps.length === 1 ? "" : "s"} found`
|
|
3513
|
+
);
|
|
3514
|
+
if (verbose && circularDeps.length > 0) {
|
|
3515
|
+
for (const c of circularDeps.slice(0, 3)) {
|
|
3516
|
+
p4.log.info(pc3.dim(` ${c.chain.join(" \u2192 ")}`));
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
spinner3.start("Detecting architecture layers...");
|
|
3512
3520
|
const { layers, layerEdges } = detectArchitecturalLayers(graph);
|
|
3513
|
-
|
|
3514
|
-
|
|
3521
|
+
spinner3.stop(
|
|
3522
|
+
layers.length > 0 ? `${pc3.green("Layers")} ${layers.map((l) => l.name).join(" \u2192 ")}` : `${pc3.green("Layers")} ${pc3.dim("no clear layers detected")}`
|
|
3523
|
+
);
|
|
3524
|
+
if (verbose && layers.length > 0) {
|
|
3525
|
+
for (const l of layers) {
|
|
3526
|
+
p4.log.info(pc3.dim(` ${l.name}: ${l.files.length} files, depends on: ${l.dependsOn.join(", ") || "none"}`));
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
spinner3.start("Computing instability metrics...");
|
|
3515
3530
|
const instabilities = computeInstability(graph);
|
|
3516
|
-
|
|
3517
|
-
spinner3.
|
|
3531
|
+
const highInstability = instabilities.filter((f) => f.instability > 0.8);
|
|
3532
|
+
spinner3.stop(
|
|
3533
|
+
highInstability.length > 0 ? `${pc3.yellow("Instability")} ${pc3.bold(String(highInstability.length))} high-risk file${highInstability.length === 1 ? "" : "s"}` : `${pc3.green("Instability")} ${pc3.dim("all files within healthy range")} ${pc3.green("\u2713")}`
|
|
3534
|
+
);
|
|
3535
|
+
if (verbose && highInstability.length > 0) {
|
|
3536
|
+
for (const f of highInstability.slice(0, 5)) {
|
|
3537
|
+
p4.log.info(pc3.dim(` ${f.path} (I=${f.instability.toFixed(2)}, fan-in=${f.fanIn}, fan-out=${f.fanOut})`));
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
spinner3.start("Detecting module communities...");
|
|
3518
3541
|
const communities = detectCommunities(graph);
|
|
3519
|
-
|
|
3520
|
-
|
|
3542
|
+
spinner3.stop(
|
|
3543
|
+
communities.length > 0 ? `${pc3.green("Communities")} ${pc3.bold(String(communities.length))} module cluster${communities.length === 1 ? "" : "s"}` : `${pc3.green("Communities")} ${pc3.dim("single cohesive module")}`
|
|
3544
|
+
);
|
|
3545
|
+
if (verbose && communities.length > 0) {
|
|
3546
|
+
for (const c of communities.slice(0, 5)) {
|
|
3547
|
+
p4.log.info(pc3.dim(` ${c.label} (${c.files.length} files)`));
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
spinner3.start("Computing export coverage...");
|
|
3521
3551
|
const exportCoverage = computeExportCoverage(graph);
|
|
3522
|
-
|
|
3523
|
-
|
|
3552
|
+
{
|
|
3553
|
+
const totalExp = exportCoverage.reduce((s, e) => s + e.totalExports, 0);
|
|
3554
|
+
const totalUsed = exportCoverage.reduce((s, e) => s + e.usedExports, 0);
|
|
3555
|
+
const unusedCount = totalExp - totalUsed;
|
|
3556
|
+
const filesWithUnused = exportCoverage.filter((e) => e.usedExports < e.totalExports).length;
|
|
3557
|
+
spinner3.stop(
|
|
3558
|
+
unusedCount > 0 ? `${pc3.yellow("Exports")} ${pc3.bold(String(unusedCount))} unused export${unusedCount === 1 ? "" : "s"} in ${filesWithUnused} file${filesWithUnused === 1 ? "" : "s"}` : `${pc3.green("Exports")} ${pc3.dim("all exports used")} ${pc3.green("\u2713")}`
|
|
3559
|
+
);
|
|
3560
|
+
if (verbose && unusedCount > 0) {
|
|
3561
|
+
for (const e of exportCoverage.filter((e2) => e2.usedExports < e2.totalExports).slice(0, 5)) {
|
|
3562
|
+
p4.log.info(pc3.dim(` ${e.file}: ${e.totalExports - e.usedExports} unused of ${e.totalExports}`));
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
spinner3.start("Analyzing git history...");
|
|
3524
3567
|
const gitActivity = detected.isGitRepo ? analyzeGitActivity(rootDir, verbose ? verboseLog : spinnerProgress) : null;
|
|
3525
|
-
if (gitActivity)
|
|
3568
|
+
if (gitActivity) {
|
|
3569
|
+
const coupledPairs = gitActivity.changeCoupling.length;
|
|
3570
|
+
spinner3.stop(
|
|
3571
|
+
`${pc3.green("Git (90d)")} ${pc3.bold(String(gitActivity.hotFiles.length))} active file${gitActivity.hotFiles.length === 1 ? "" : "s"}, ${pc3.bold(String(coupledPairs))} coupled pair${coupledPairs === 1 ? "" : "s"}`
|
|
3572
|
+
);
|
|
3573
|
+
if (verbose) {
|
|
3574
|
+
for (const h of gitActivity.hotFiles.slice(0, 5)) {
|
|
3575
|
+
p4.log.info(pc3.dim(` ${h.path} (${h.commits} commits, last: ${h.lastChanged})`));
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
} else {
|
|
3579
|
+
spinner3.stop(`${pc3.green("Git")} ${pc3.dim("not a git repo \u2014 skipped")}`);
|
|
3580
|
+
}
|
|
3526
3581
|
const analysis = { hubFiles, circularDeps, layers, layerEdges, gitActivity, instabilities, communities, exportCoverage };
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3582
|
+
{
|
|
3583
|
+
const reportLines = [];
|
|
3584
|
+
reportLines.push(` Files analyzed: ${fileCount}`);
|
|
3585
|
+
reportLines.push(` Import edges: ${graph.edges.length}`);
|
|
3586
|
+
reportLines.push(` External pkgs: ${graph.externalImportCounts.size}`);
|
|
3587
|
+
if (hubFiles.length > 0) {
|
|
3588
|
+
reportLines.push(` Hub files: ${hubFiles.length}` + (hubFiles[0] ? ` (most connected: ${hubFiles[0].path})` : ""));
|
|
3589
|
+
}
|
|
3590
|
+
if (layers.length > 0) {
|
|
3591
|
+
reportLines.push(` Architecture: ${layers.map((l) => l.name).join(" \u2192 ")}`);
|
|
3592
|
+
}
|
|
3593
|
+
reportLines.push(` Circular deps: ${circularDeps.length === 0 ? "none" : `${circularDeps.length} chain${circularDeps.length === 1 ? "" : "s"}`}`);
|
|
3594
|
+
if (gitActivity) {
|
|
3595
|
+
reportLines.push(` Hot files (90d): ${gitActivity.hotFiles.length}`);
|
|
3596
|
+
}
|
|
3597
|
+
p4.note(reportLines.join("\n"), "Analysis Report");
|
|
3598
|
+
if (circularDeps.length > 0) {
|
|
3599
|
+
for (const c of circularDeps.slice(0, 2)) {
|
|
3600
|
+
const shortChain = c.chain.map((f) => f.split("/").pop() ?? f);
|
|
3601
|
+
p4.log.warn(pc3.yellow(`Cycle: ${shortChain.join(" \u2192 ")}`));
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3536
3605
|
const savedConfig = await loadConfig(rootDir);
|
|
3537
3606
|
if (savedConfig?.snapshotHash) {
|
|
3538
3607
|
const currentHash = await computeSnapshotHash(rootDir, detected.language);
|