@aiready/context-analyzer 0.22.6 → 0.22.8

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.
@@ -1,31 +1,32 @@
1
-
2
- > @aiready/context-analyzer@0.22.6 build /Users/pengcao/projects/aiready/packages/context-analyzer
3
- > tsup src/index.ts src/cli.ts --format cjs,esm --dts
4
-
5
- CLI Building entry: src/cli.ts, src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Target: es2020
9
- CJS Build start
10
- ESM Build start
11
- CJS dist/index.js 83.32 KB
12
- CJS dist/cli.js 69.59 KB
13
- CJS ⚡️ Build success in 148ms
14
- ESM dist/cli-action-VCXBZGZP.mjs 2.86 KB
15
- ESM dist/cli.mjs 1.81 KB
16
- ESM dist/python-context-BWDC4E5Z.mjs 4.48 KB
17
- ESM dist/chunk-M2EGQ36M.mjs 41.00 KB
18
- ESM dist/index.mjs 12.64 KB
19
- ESM dist/chunk-J3SZQZNU.mjs 8.11 KB
20
- ESM dist/chunk-EMYD7NS6.mjs 4.91 KB
21
- ESM dist/chunk-64U3PNO3.mjs 2.65 KB
22
- ESM dist/summary-7PZVW72O.mjs 119.00 B
23
- ESM dist/orchestrator-KMAKMHTD.mjs 190.00 B
24
- ESM dist/chunk-BQCISA2F.mjs 3.35 KB
25
- ESM ⚡️ Build success in 151ms
26
- DTS Build start
27
- DTS ⚡️ Build success in 12467ms
28
- DTS dist/cli.d.ts 20.00 B
29
- DTS dist/index.d.ts 25.64 KB
30
- DTS dist/cli.d.mts 20.00 B
31
- DTS dist/index.d.mts 25.64 KB
1
+
2
+ 
3
+ > @aiready/context-analyzer@0.22.8 build /Users/pengcao/projects/aiready/packages/context-analyzer
4
+ > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
+
6
+ CLI Building entry: src/cli.ts, src/index.ts
7
+ CLI Using tsconfig: tsconfig.json
8
+ CLI tsup v8.5.1
9
+ CLI Target: es2020
10
+ CJS Build start
11
+ ESM Build start
12
+ ESM dist/index.mjs 12.69 KB
13
+ ESM dist/cli.mjs 1.81 KB
14
+ ESM dist/python-context-BWDC4E5Z.mjs 4.48 KB
15
+ ESM dist/orchestrator-62YVSQFV.mjs 190.00 B
16
+ ESM dist/cli-action-HREY7TK5.mjs 2.86 KB
17
+ ESM dist/chunk-BTDF2ZA4.mjs 3.35 KB
18
+ ESM dist/chunk-J3SZQZNU.mjs 8.11 KB
19
+ ESM dist/chunk-64U3PNO3.mjs 2.65 KB
20
+ ESM dist/summary-RSPRRY6S.mjs 119.00 B
21
+ ESM dist/chunk-JSM7Q5CY.mjs 4.98 KB
22
+ ESM dist/chunk-ZA7HRDAH.mjs 41.00 KB
23
+ ESM ⚡️ Build success in 180ms
24
+ CJS dist/index.js 83.44 KB
25
+ CJS dist/cli.js 69.65 KB
26
+ CJS ⚡️ Build success in 180ms
27
+ DTS Build start
28
+ DTS ⚡️ Build success in 2332ms
29
+ DTS dist/cli.d.ts 20.00 B
30
+ DTS dist/index.d.ts 25.64 KB
31
+ DTS dist/cli.d.mts 20.00 B
32
+ DTS dist/index.d.mts 25.64 KB
@@ -0,0 +1,7 @@
1
+
2
+ 
3
+ > @aiready/context-analyzer@0.22.8 format-check /Users/pengcao/projects/aiready/packages/context-analyzer
4
+ > prettier --check . --ignore-path ../../.prettierignore
5
+
6
+ Checking formatting...
7
+ .aireadyignoreCONTRIBUTING.mdLICENSEpackage.jsonREADME.mdsrc/__tests__/analyzer.test.tssrc/__tests__/auto-detection.test.tssrc/__tests__/boilerplate-barrel.test.tssrc/__tests__/cluster-detector.test.tssrc/__tests__/consolidation.test.tssrc/__tests__/contract.test.tssrc/__tests__/defaults.test.tssrc/__tests__/domain-inference.test.tssrc/__tests__/enhanced-cohesion.test.tssrc/__tests__/file-classification.test.tssrc/__tests__/fragmentation-advanced.test.tssrc/__tests__/fragmentation-coupling.test.tssrc/__tests__/fragmentation-log.test.tssrc/__tests__/issue-analyzer.test.tssrc/__tests__/orchestrator.test.tssrc/__tests__/provider.test.tssrc/__tests__/python-context.test.tssrc/__tests__/remediation.test.tssrc/__tests__/report/console-report.test.tssrc/__tests__/report/html-report.test.tssrc/__tests__/scoring.test.tssrc/__tests__/structural-cohesion.test.tssrc/analyzers/python-context.tssrc/ast-utils.tssrc/classifier.tssrc/classify/classification-patterns.tssrc/classify/file-classifiers.tssrc/cli-action.tssrc/cli-definition.tssrc/cli.tssrc/cluster-detector.tssrc/defaults.tssrc/graph-builder.tssrc/index.tssrc/issue-analyzer.tssrc/metrics.tssrc/orchestrator.tssrc/provider.tssrc/remediation.tssrc/report/console-report.tssrc/report/html-report.tssrc/report/interactive-setup.tssrc/scoring.tssrc/semantic/co-usage.tssrc/semantic/consolidation.tssrc/semantic/domain-inference.tssrc/semantic/type-graph.tssrc/summary.tssrc/types.tssrc/utils/dependency-graph-utils.tssrc/utils/string-utils.tstsconfig.jsontsconfig.tsbuildinfoAll matched files use Prettier code style!
@@ -1,4 +1,5 @@
1
-
2
- > @aiready/context-analyzer@0.21.24 lint /Users/pengcao/projects/aiready/packages/context-analyzer
3
- > eslint src
4
-
1
+
2
+ 
3
+ > @aiready/context-analyzer@0.22.8 lint /Users/pengcao/projects/aiready/packages/context-analyzer
4
+ > eslint src
5
+
@@ -1,49 +1,51 @@
1
-
2
- > @aiready/context-analyzer@0.22.5 test /Users/pengcao/projects/aiready/packages/context-analyzer
3
- > vitest run --exclude "**/dist/**"
4
-
5
-
6
-  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/context-analyzer
7
-
8
- ✓ src/__tests__/domain-inference.test.ts (26 tests) 29ms
9
- ✓ src/__tests__/report/console-report.test.ts (8 tests) 79ms
10
- ✓ src/__tests__/report/html-report.test.ts (11 tests) 71ms
11
- ✓ src/__tests__/remediation.test.ts (6 tests) 5ms
12
- stderr | src/__tests__/python-context.test.ts > python-context > analyzePythonContext > should filter for Python files only
13
- Failed to analyze src/file2.py: TypeError: Cannot read properties of undefined (reading 'split')
14
- at Module.analyzePythonContext (/Users/pengcao/projects/aiready/packages/context-analyzer/src/analyzers/python-context.ts:91:32)
15
-  at processTicksAndRejections (node:internal/process/task_queues:104:5)
16
- at /Users/pengcao/projects/aiready/packages/context-analyzer/src/__tests__/python-context.test.ts:63:23
17
- at file:///Users/pengcao/projects/aiready/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:915:20
18
-
19
- stderr | src/__tests__/python-context.test.ts > python-context > analyzePythonContext > should analyze Python files and return metrics
20
- Failed to analyze src/test.py: TypeError: Cannot read properties of undefined (reading 'split')
21
- at Module.analyzePythonContext (/Users/pengcao/projects/aiready/packages/context-analyzer/src/analyzers/python-context.ts:91:32)
22
-  at processTicksAndRejections (node:internal/process/task_queues:104:5)
23
- at /Users/pengcao/projects/aiready/packages/context-analyzer/src/__tests__/python-context.test.ts:84:23
24
- at file:///Users/pengcao/projects/aiready/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:915:20
25
-
26
- ✓ src/__tests__/python-context.test.ts (4 tests) 14ms
27
- ✓ src/__tests__/issue-analyzer.test.ts (17 tests) 104ms
28
- ✓ src/__tests__/provider.test.ts (2 tests) 4ms
29
- ✓ src/__tests__/orchestrator.test.ts (8 tests) 130ms
30
- ✓ src/__tests__/fragmentation-coupling.test.ts (2 tests) 14ms
31
- ✓ src/__tests__/analyzer.test.ts (14 tests) 30ms
32
- ✓ src/__tests__/file-classification.test.ts (63 tests) 58ms
33
- ✓ src/__tests__/auto-detection.test.ts (8 tests) 115ms
34
- ✓ src/__tests__/contract.test.ts (1 test) 120ms
35
- ✓ src/__tests__/scoring.test.ts (15 tests) 4ms
36
- ✓ src/__tests__/consolidation.test.ts (6 tests) 4ms
37
- ✓ src/__tests__/cluster-detector.test.ts (3 tests) 6ms
38
- ✓ src/__tests__/fragmentation-log.test.ts (3 tests) 1ms
39
- ✓ src/__tests__/fragmentation-advanced.test.ts (3 tests) 2ms
40
- ✓ src/__tests__/defaults.test.ts (7 tests) 8ms
41
- ✓ src/__tests__/enhanced-cohesion.test.ts (6 tests) 3ms
42
- ✓ src/__tests__/boilerplate-barrel.test.ts (7 tests) 4ms
43
- ✓ src/__tests__/structural-cohesion.test.ts (4 tests) 2ms
44
-
45
-  Test Files  22 passed (22)
46
-  Tests  224 passed (224)
47
-  Start at  20:27:24
48
-  Duration  6.51s (transform 11.24s, setup 0ms, import 42.58s, tests 806ms, environment 30ms)
49
-
1
+
2
+ 
3
+ > @aiready/context-analyzer@0.22.7 test /Users/pengcao/projects/aiready/packages/context-analyzer
4
+ > vitest run --exclude "**/dist/**"
5
+
6
+ [?25l
7
+  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/context-analyzer
8
+
9
+ ✓ src/__tests__/consolidation.test.ts (6 tests) 3ms
10
+ ✓ src/__tests__/report/html-report.test.ts (11 tests) 32ms
11
+ ✓ src/__tests__/report/console-report.test.ts (8 tests) 124ms
12
+ ✓ src/__tests__/domain-inference.test.ts (26 tests) 6ms
13
+ stderr | src/__tests__/python-context.test.ts > python-context > analyzePythonContext > should filter for Python files only
14
+ Failed to analyze src/file2.py: TypeError: Cannot read properties of undefined (reading 'split')
15
+ at Module.analyzePythonContext (/Users/pengcao/projects/aiready/packages/context-analyzer/src/analyzers/python-context.ts:91:32)
16
+  at processTicksAndRejections (node:internal/process/task_queues:104:5)
17
+ at /Users/pengcao/projects/aiready/packages/context-analyzer/src/__tests__/python-context.test.ts:63:23
18
+ at file:///Users/pengcao/projects/aiready/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:915:20
19
+
20
+ stderr | src/__tests__/python-context.test.ts > python-context > analyzePythonContext > should analyze Python files and return metrics
21
+ Failed to analyze src/test.py: TypeError: Cannot read properties of undefined (reading 'split')
22
+ at Module.analyzePythonContext (/Users/pengcao/projects/aiready/packages/context-analyzer/src/analyzers/python-context.ts:91:32)
23
+  at processTicksAndRejections (node:internal/process/task_queues:104:5)
24
+ at /Users/pengcao/projects/aiready/packages/context-analyzer/src/__tests__/python-context.test.ts:84:23
25
+ at file:///Users/pengcao/projects/aiready/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:915:20
26
+
27
+ ✓ src/__tests__/python-context.test.ts (4 tests) 14ms
28
+ ✓ src/__tests__/fragmentation-log.test.ts (3 tests) 1ms
29
+ ✓ src/__tests__/fragmentation-coupling.test.ts (2 tests) 42ms
30
+ ✓ src/__tests__/analyzer.test.ts (14 tests) 46ms
31
+ ✓ src/__tests__/issue-analyzer.test.ts (17 tests) 14ms
32
+ ✓ src/__tests__/auto-detection.test.ts (8 tests) 177ms
33
+ ✓ src/__tests__/orchestrator.test.ts (8 tests) 226ms
34
+ ✓ src/__tests__/provider.test.ts (2 tests) 10ms
35
+ ✓ src/__tests__/defaults.test.ts (7 tests) 3ms
36
+ ✓ src/__tests__/cluster-detector.test.ts (3 tests) 35ms
37
+ ✓ src/__tests__/file-classification.test.ts (63 tests) 12ms
38
+ ✓ src/__tests__/remediation.test.ts (6 tests) 2ms
39
+ ✓ src/__tests__/contract.test.ts (1 test) 19ms
40
+ ✓ src/__tests__/scoring.test.ts (15 tests) 3ms
41
+ ✓ src/__tests__/enhanced-cohesion.test.ts (6 tests) 2ms
42
+ ✓ src/__tests__/structural-cohesion.test.ts (4 tests) 39ms
43
+ ✓ src/__tests__/fragmentation-advanced.test.ts (3 tests) 1ms
44
+ ✓ src/__tests__/boilerplate-barrel.test.ts (7 tests) 3ms
45
+
46
+  Test Files  22 passed (22)
47
+  Tests  224 passed (224)
48
+  Start at  00:08:32
49
+  Duration  5.11s (transform 8.77s, setup 0ms, import 35.15s, tests 815ms, environment 9ms)
50
+
51
+ [?25h
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @aiready/context-analyzer@0.22.8 type-check /Users/pengcao/projects/aiready/packages/context-analyzer
4
+ > tsc --noEmit
5
+
package/CONTRIBUTING.md CHANGED
@@ -13,7 +13,7 @@ The Context Analyzer measures and optimizes **context window usage** - how much
13
13
 
14
14
  ## 🐛 Reporting Issues
15
15
 
16
- Found a bug or have a feature request? [Open an issue](https://github.com/caopengau/aiready-context-analyzer/issues) with:
16
+ Found a bug or have a feature request? [Open an issue](https://github.com/getaiready/aiready-context-analyzer/issues) with:
17
17
 
18
18
  - Clear description of the problem or feature
19
19
  - Sample code that demonstrates the issue
@@ -0,0 +1,91 @@
1
+ import {
2
+ calculatePathEntropy
3
+ } from "./chunk-JSM7Q5CY.mjs";
4
+
5
+ // src/summary.ts
6
+ import { GLOBAL_SCAN_OPTIONS } from "@aiready/core";
7
+ function generateSummary(results, options = {}) {
8
+ const config = options ? Object.fromEntries(
9
+ Object.entries(options).filter(
10
+ ([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
11
+ )
12
+ ) : {};
13
+ const totalFiles = results.length;
14
+ const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
15
+ const avgContextBudget = totalFiles > 0 ? results.reduce((sum, r) => sum + r.contextBudget, 0) / totalFiles : 0;
16
+ const deepFiles = results.filter((r) => r.importDepth > 5).map((r) => ({ file: r.file, depth: r.importDepth }));
17
+ const maxImportDepth = Math.max(0, ...results.map((r) => r.importDepth));
18
+ const moduleMap = /* @__PURE__ */ new Map();
19
+ results.forEach((r) => {
20
+ const parts = r.file.split("/");
21
+ let domain = "root";
22
+ if (parts.length > 2) {
23
+ domain = parts.slice(0, 2).join("/");
24
+ }
25
+ if (!moduleMap.has(domain)) moduleMap.set(domain, []);
26
+ moduleMap.get(domain).push(r);
27
+ });
28
+ const fragmentedModules = [];
29
+ moduleMap.forEach((files, domain) => {
30
+ const clusterTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
31
+ const filePaths = files.map((f) => f.file);
32
+ const avgEntropy = calculatePathEntropy(filePaths);
33
+ const fragmentationScore = Math.min(1, avgEntropy * (files.length / 10));
34
+ if (fragmentationScore > 0.4) {
35
+ fragmentedModules.push({
36
+ domain,
37
+ files: filePaths,
38
+ fragmentationScore,
39
+ totalTokens: clusterTokens,
40
+ avgCohesion: files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length,
41
+ suggestedStructure: {
42
+ targetFiles: Math.ceil(files.length / 2),
43
+ consolidationPlan: [
44
+ `Consolidate ${files.length} files in ${domain} into fewer modules`
45
+ ]
46
+ }
47
+ });
48
+ }
49
+ });
50
+ fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
51
+ const avgFragmentation = fragmentedModules.length > 0 ? fragmentedModules.reduce((sum, m) => sum + m.fragmentationScore, 0) / fragmentedModules.length : 0;
52
+ const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / (totalFiles || 1);
53
+ const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore }));
54
+ const criticalIssues = results.filter(
55
+ (r) => r.severity === "critical"
56
+ ).length;
57
+ const majorIssues = results.filter((r) => r.severity === "major").length;
58
+ const minorIssues = results.filter((r) => r.severity === "minor").length;
59
+ const totalPotentialSavings = results.reduce(
60
+ (sum, r) => sum + (r.potentialSavings || 0),
61
+ 0
62
+ );
63
+ const topExpensiveFiles = [...results].sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
64
+ file: r.file,
65
+ contextBudget: r.contextBudget,
66
+ severity: r.severity
67
+ }));
68
+ return {
69
+ totalFiles,
70
+ totalTokens,
71
+ avgContextBudget,
72
+ maxContextBudget: Math.max(0, ...results.map((r) => r.contextBudget)),
73
+ avgImportDepth: results.reduce((sum, r) => sum + r.importDepth, 0) / (totalFiles || 1),
74
+ maxImportDepth,
75
+ deepFiles,
76
+ avgFragmentation,
77
+ fragmentedModules,
78
+ avgCohesion,
79
+ lowCohesionFiles,
80
+ criticalIssues,
81
+ majorIssues,
82
+ minorIssues,
83
+ totalPotentialSavings,
84
+ topExpensiveFiles,
85
+ config
86
+ };
87
+ }
88
+
89
+ export {
90
+ generateSummary
91
+ };
@@ -0,0 +1,143 @@
1
+ // src/metrics.ts
2
+ import { calculateImportSimilarity, isTestFile } from "@aiready/core";
3
+ function calculateEnhancedCohesion(exports, filePath, options) {
4
+ if (exports.length <= 1) return 1;
5
+ if (filePath && isTestFile(filePath)) return 1;
6
+ const domains = exports.map((e) => e.inferredDomain || "unknown");
7
+ const domainCounts = /* @__PURE__ */ new Map();
8
+ for (const domain of domains)
9
+ domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1);
10
+ if (domainCounts.size === 1 && domains[0] !== "unknown") {
11
+ if (!options?.weights) return 1;
12
+ }
13
+ const probs = Array.from(domainCounts.values()).map(
14
+ (count) => count / exports.length
15
+ );
16
+ let domainEntropy = 0;
17
+ for (const prob of probs) {
18
+ if (prob > 0) domainEntropy -= prob * Math.log2(prob);
19
+ }
20
+ const maxEntropy = Math.log2(Math.max(2, domainCounts.size));
21
+ const domainScore = 1 - domainEntropy / maxEntropy;
22
+ let importScoreTotal = 0;
23
+ let pairsWithData = 0;
24
+ let anyImportData = false;
25
+ for (let i = 0; i < exports.length; i++) {
26
+ for (let j = i + 1; j < exports.length; j++) {
27
+ const exp1Imports = exports[i].imports;
28
+ const exp2Imports = exports[j].imports;
29
+ if (exp1Imports || exp2Imports) {
30
+ anyImportData = true;
31
+ const sim = calculateImportSimilarity(
32
+ {
33
+ ...exports[i],
34
+ imports: exp1Imports || []
35
+ },
36
+ {
37
+ ...exports[j],
38
+ imports: exp2Imports || []
39
+ }
40
+ );
41
+ importScoreTotal += sim;
42
+ pairsWithData++;
43
+ }
44
+ }
45
+ }
46
+ const avgImportScore = pairsWithData > 0 ? importScoreTotal / pairsWithData : 0;
47
+ let score = anyImportData ? domainScore * 0.4 + avgImportScore * 0.6 : domainScore;
48
+ if (anyImportData && score === 0 && domainScore === 0) {
49
+ score = 0.1;
50
+ }
51
+ let structuralScore = 0;
52
+ for (const exp of exports) {
53
+ if (exp.dependencies && exp.dependencies.length > 0) {
54
+ structuralScore += 1;
55
+ }
56
+ }
57
+ if (structuralScore > 0) {
58
+ score = Math.min(1, score + 0.1);
59
+ }
60
+ if (!options?.weights && !anyImportData && domainCounts.size === 1) return 1;
61
+ return score;
62
+ }
63
+ function calculateStructuralCohesionFromCoUsage(file, coUsageMatrix) {
64
+ if (!coUsageMatrix) return 1;
65
+ const coUsages = coUsageMatrix.get(file);
66
+ if (!coUsages || coUsages.size === 0) return 1;
67
+ let total = 0;
68
+ for (const count of coUsages.values()) total += count;
69
+ if (total === 0) return 1;
70
+ const probs = [];
71
+ for (const count of coUsages.values()) {
72
+ if (count > 0) probs.push(count / total);
73
+ }
74
+ if (probs.length <= 1) return 1;
75
+ let entropy = 0;
76
+ for (const prob of probs) {
77
+ entropy -= prob * Math.log2(prob);
78
+ }
79
+ const maxEntropy = Math.log2(probs.length);
80
+ return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
81
+ }
82
+ function calculateFragmentation(files, domain, options) {
83
+ if (files.length <= 1) return 0;
84
+ const directories = new Set(
85
+ files.map((file) => file.split("/").slice(0, -1).join("/"))
86
+ );
87
+ const uniqueDirs = directories.size;
88
+ let score = options?.useLogScale ? uniqueDirs <= 1 ? 0 : Math.log(uniqueDirs) / Math.log(options.logBase || Math.E) / (Math.log(files.length) / Math.log(options.logBase || Math.E)) : (uniqueDirs - 1) / (files.length - 1);
89
+ if (options?.sharedImportRatio && options.sharedImportRatio > 0.5) {
90
+ const discount = (options.sharedImportRatio - 0.5) * 0.4;
91
+ score = score * (1 - discount);
92
+ }
93
+ return score;
94
+ }
95
+ function calculatePathEntropy(files) {
96
+ if (!files || files.length === 0) return 0;
97
+ const dirCounts = /* @__PURE__ */ new Map();
98
+ for (const file of files) {
99
+ const dir = file.split("/").slice(0, -1).join("/") || ".";
100
+ dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
101
+ }
102
+ const counts = Array.from(dirCounts.values());
103
+ if (counts.length <= 1) return 0;
104
+ const total = counts.reduce((sum, value) => sum + value, 0);
105
+ let entropy = 0;
106
+ for (const count of counts) {
107
+ const prob = count / total;
108
+ entropy -= prob * Math.log2(prob);
109
+ }
110
+ const maxEntropy = Math.log2(counts.length);
111
+ return maxEntropy > 0 ? entropy / maxEntropy : 0;
112
+ }
113
+ function calculateDirectoryDistance(files) {
114
+ if (!files || files.length <= 1) return 0;
115
+ const pathSegments = (pathStr) => pathStr.split("/").filter(Boolean);
116
+ const commonAncestorDepth = (pathA, pathB) => {
117
+ const minLen = Math.min(pathA.length, pathB.length);
118
+ let i = 0;
119
+ while (i < minLen && pathA[i] === pathB[i]) i++;
120
+ return i;
121
+ };
122
+ let totalNormalized = 0;
123
+ let comparisons = 0;
124
+ for (let i = 0; i < files.length; i++) {
125
+ for (let j = i + 1; j < files.length; j++) {
126
+ const segA = pathSegments(files[i]);
127
+ const segB = pathSegments(files[j]);
128
+ const shared = commonAncestorDepth(segA, segB);
129
+ const maxDepth = Math.max(segA.length, segB.length);
130
+ totalNormalized += 1 - (maxDepth > 0 ? shared / maxDepth : 0);
131
+ comparisons++;
132
+ }
133
+ }
134
+ return comparisons > 0 ? totalNormalized / comparisons : 0;
135
+ }
136
+
137
+ export {
138
+ calculateEnhancedCohesion,
139
+ calculateStructuralCohesionFromCoUsage,
140
+ calculateFragmentation,
141
+ calculatePathEntropy,
142
+ calculateDirectoryDistance
143
+ };