@aiready/change-amplification 0.14.14 → 0.14.16
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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-format-check.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +45 -7
- package/.turbo/turbo-type-check.log +1 -1
- package/dist/chunk-BAPK2YBZ.mjs +148 -0
- package/dist/chunk-KUIEB4UN.mjs +148 -0
- package/dist/cli.js +4 -2
- package/dist/cli.mjs +1 -1
- package/dist/index.js +4 -2
- package/dist/index.mjs +1 -1
- package/package.json +5 -3
- package/src/analyzer.ts +21 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/change-amplification@0.14.
|
|
3
|
+
> @aiready/change-amplification@0.14.15 build /Users/pengcao/projects/aiready/packages/change-amplification
|
|
4
4
|
> tsup src/index.ts src/cli.ts --format cjs,esm --dts
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[
|
|
13
|
-
[
|
|
12
|
+
[32mCJS[39m [1mdist/index.js [22m[32m7.53 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m8.99 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 43ms
|
|
15
|
+
[32mESM[39m [1mdist/chunk-KUIEB4UN.mjs [22m[32m5.32 KB[39m
|
|
14
16
|
[32mESM[39m [1mdist/cli.mjs [22m[32m2.41 KB[39m
|
|
15
|
-
[32mESM[39m
|
|
16
|
-
[
|
|
17
|
-
[32mCJS[39m [1mdist/index.js [22m[32m7.04 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 107ms
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m916.00 B[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 48ms
|
|
19
19
|
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in
|
|
20
|
+
DTS ⚡️ Build success in 3155ms
|
|
21
21
|
DTS dist/cli.d.ts 266.00 B
|
|
22
22
|
DTS dist/index.d.ts 937.00 B
|
|
23
23
|
DTS dist/cli.d.mts 266.00 B
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/change-amplification@0.14.
|
|
3
|
+
> @aiready/change-amplification@0.14.15 format-check /Users/pengcao/projects/aiready/packages/change-amplification
|
|
4
4
|
> prettier --check . --ignore-path ../../.prettierignore
|
|
5
5
|
|
|
6
6
|
Checking formatting...
|
package/.turbo/turbo-lint.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,24 +1,62 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/change-amplification@0.14.
|
|
3
|
+
> @aiready/change-amplification@0.14.15 test /Users/pengcao/projects/aiready/packages/change-amplification
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
[?25l
|
|
7
7
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/change-amplification[39m
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
[
|
|
9
|
+
[?2026h
|
|
10
|
+
[1m[33m ❯ [39m[22msrc/__tests__/scoring.test.ts[2m [queued][22m
|
|
11
|
+
|
|
12
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (5)[39m
|
|
13
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
14
|
+
[2m Start at [22m23:35:29
|
|
15
|
+
[2m Duration [22m370ms
|
|
16
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
17
|
+
[1m[33m ❯ [39m[22msrc/__tests__/analyzer.test.ts[2m [queued][22m
|
|
18
|
+
[1m[33m ❯ [39m[22msrc/__tests__/cli.test.ts[2m [queued][22m
|
|
19
|
+
[1m[33m ❯ [39m[22msrc/__tests__/dummy.test.ts[2m 0/1[22m
|
|
20
|
+
[1m[33m ❯ [39m[22msrc/__tests__/provider.test.ts[2m [queued][22m
|
|
21
|
+
[1m[33m ❯ [39m[22msrc/__tests__/scoring.test.ts[2m [queued][22m
|
|
22
|
+
|
|
23
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (5)[39m
|
|
24
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
25
|
+
[2m Start at [22m23:35:29
|
|
26
|
+
[2m Duration [22m474ms
|
|
27
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m src/__tests__/dummy.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 1[2mms[22m[39m
|
|
28
|
+
|
|
29
|
+
[1m[33m ❯ [39m[22msrc/__tests__/analyzer.test.ts[2m [queued][22m
|
|
30
|
+
[1m[33m ❯ [39m[22msrc/__tests__/cli.test.ts[2m [queued][22m
|
|
31
|
+
[1m[33m ❯ [39m[22msrc/__tests__/provider.test.ts[2m [queued][22m
|
|
32
|
+
[1m[33m ❯ [39m[22msrc/__tests__/scoring.test.ts[2m [queued][22m
|
|
33
|
+
|
|
34
|
+
[2m Test Files [22m[1m[32m1 passed[39m[22m[90m (5)[39m
|
|
35
|
+
[2m Tests [22m[1m[32m1 passed[39m[22m[90m (1)[39m
|
|
36
|
+
[2m Start at [22m23:35:29
|
|
37
|
+
[2m Duration [22m908ms
|
|
38
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
39
|
+
[1m[33m ❯ [39m[22msrc/__tests__/analyzer.test.ts[2m [queued][22m
|
|
40
|
+
[1m[33m ❯ [39m[22msrc/__tests__/cli.test.ts[2m 0/2[22m
|
|
41
|
+
[1m[33m ❯ [39m[22msrc/__tests__/provider.test.ts[2m [queued][22m
|
|
42
|
+
[1m[33m ❯ [39m[22msrc/__tests__/scoring.test.ts[2m [queued][22m
|
|
43
|
+
|
|
44
|
+
[2m Test Files [22m[1m[32m1 passed[39m[22m[90m (5)[39m
|
|
45
|
+
[2m Tests [22m[1m[32m1 passed[39m[22m[90m (3)[39m
|
|
46
|
+
[2m Start at [22m23:35:29
|
|
47
|
+
[2m Duration [22m1.54s
|
|
48
|
+
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m src/__tests__/cli.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
11
49
|
[90mstdout[2m | src/__tests__/analyzer.test.ts[2m > [22m[2manalyzeChangeAmplification reproduction[2m > [22m[2mshould see how it gets to 0
|
|
12
50
|
[22m[39mResulting score for highly coupled: [33m27[39m
|
|
13
51
|
Rating: explosive
|
|
14
52
|
|
|
15
|
-
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
16
|
-
[32m✓[39m src/__tests__/provider.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
53
|
+
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
54
|
+
[32m✓[39m src/__tests__/provider.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
17
55
|
[32m✓[39m src/__tests__/scoring.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
18
56
|
|
|
19
57
|
[2m Test Files [22m [1m[32m5 passed[39m[22m[90m (5)[39m
|
|
20
58
|
[2m Tests [22m [1m[32m9 passed[39m[22m[90m (9)[39m
|
|
21
|
-
[2m Start at [22m
|
|
22
|
-
[2m Duration [22m
|
|
59
|
+
[2m Start at [22m 23:35:29
|
|
60
|
+
[2m Duration [22m 1.65s[2m (transform 1.47s, setup 0ms, import 4.83s, tests 16ms, environment 0ms)[22m
|
|
23
61
|
|
|
24
62
|
[?25h
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/analyzer.ts
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import {
|
|
12
|
+
scanFiles,
|
|
13
|
+
calculateChangeAmplification,
|
|
14
|
+
getParser,
|
|
15
|
+
Severity,
|
|
16
|
+
IssueType
|
|
17
|
+
} from "@aiready/core";
|
|
18
|
+
async function analyzeChangeAmplification(options) {
|
|
19
|
+
const files = await scanFiles({
|
|
20
|
+
...options,
|
|
21
|
+
include: options.include || ["**/*.{ts,tsx,js,jsx,py,go}"]
|
|
22
|
+
});
|
|
23
|
+
const dependencyGraph = /* @__PURE__ */ new Map();
|
|
24
|
+
const reverseGraph = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
dependencyGraph.set(file, []);
|
|
27
|
+
reverseGraph.set(file, []);
|
|
28
|
+
}
|
|
29
|
+
let processed = 0;
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
processed++;
|
|
32
|
+
if (processed % 50 === 0 || processed === files.length) {
|
|
33
|
+
options.onProgress?.(
|
|
34
|
+
processed,
|
|
35
|
+
files.length,
|
|
36
|
+
`analyzing dependencies (${processed}/${files.length})`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parser = await getParser(file);
|
|
41
|
+
if (!parser) continue;
|
|
42
|
+
const content = fs.readFileSync(file, "utf8");
|
|
43
|
+
const parseResult = parser.parse(content, file);
|
|
44
|
+
const dependencies = parseResult.imports.map((i) => i.source);
|
|
45
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
46
|
+
for (const dep of dependencies) {
|
|
47
|
+
const depDir = path.dirname(file);
|
|
48
|
+
let resolvedPath;
|
|
49
|
+
if (dep.startsWith(".")) {
|
|
50
|
+
const absoluteDepBase = path.resolve(depDir, dep);
|
|
51
|
+
for (const ext of extensions) {
|
|
52
|
+
const withExt = absoluteDepBase + ext;
|
|
53
|
+
if (files.includes(withExt)) {
|
|
54
|
+
resolvedPath = withExt;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!resolvedPath) {
|
|
59
|
+
for (const ext of extensions) {
|
|
60
|
+
const withIndex = path.join(absoluteDepBase, `index${ext}`);
|
|
61
|
+
if (files.includes(withIndex)) {
|
|
62
|
+
resolvedPath = withIndex;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const depWithoutExt = dep.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
69
|
+
resolvedPath = files.find((f) => {
|
|
70
|
+
const fWithoutExt = f.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
71
|
+
return fWithoutExt === depWithoutExt || fWithoutExt.endsWith(`/${depWithoutExt}`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (resolvedPath && resolvedPath !== file) {
|
|
75
|
+
dependencyGraph.get(file)?.push(resolvedPath);
|
|
76
|
+
reverseGraph.get(resolvedPath)?.push(file);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
void err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const fileMetrics = files.map((file) => {
|
|
84
|
+
const fanOut = dependencyGraph.get(file)?.length || 0;
|
|
85
|
+
const fanIn = reverseGraph.get(file)?.length || 0;
|
|
86
|
+
return { file, fanOut, fanIn };
|
|
87
|
+
});
|
|
88
|
+
const riskResult = calculateChangeAmplification({ files: fileMetrics });
|
|
89
|
+
let finalScore = riskResult.score;
|
|
90
|
+
if (finalScore === 0 && files.length > 0 && riskResult.rating !== "explosive") {
|
|
91
|
+
finalScore = 10;
|
|
92
|
+
}
|
|
93
|
+
const results = [];
|
|
94
|
+
const getLevel = (s) => {
|
|
95
|
+
if (s === Severity.Critical || s === "critical") return 4;
|
|
96
|
+
if (s === Severity.Major || s === "major") return 3;
|
|
97
|
+
if (s === Severity.Minor || s === "minor") return 2;
|
|
98
|
+
if (s === Severity.Info || s === "info") return 1;
|
|
99
|
+
return 0;
|
|
100
|
+
};
|
|
101
|
+
for (const hotspot of riskResult.hotspots) {
|
|
102
|
+
const issues = [];
|
|
103
|
+
const fileName = path.basename(hotspot.file);
|
|
104
|
+
const isBarrelFile = fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx" || fileName.startsWith("all-") || fileName.endsWith(".meta.ts") || fileName.endsWith(".meta.js");
|
|
105
|
+
if (hotspot.amplificationFactor > 20 && !isBarrelFile) {
|
|
106
|
+
issues.push({
|
|
107
|
+
type: IssueType.ChangeAmplification,
|
|
108
|
+
severity: hotspot.amplificationFactor > 40 ? Severity.Critical : Severity.Major,
|
|
109
|
+
message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
|
|
110
|
+
location: { file: hotspot.file, line: 1 },
|
|
111
|
+
suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (hotspot.amplificationFactor > 5 && !isBarrelFile) {
|
|
115
|
+
results.push({
|
|
116
|
+
fileName: hotspot.file,
|
|
117
|
+
issues,
|
|
118
|
+
metrics: {
|
|
119
|
+
aiSignalClarityScore: 100 - hotspot.amplificationFactor
|
|
120
|
+
// Just a rough score
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
summary: {
|
|
127
|
+
totalFiles: files.length,
|
|
128
|
+
totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
|
|
129
|
+
criticalIssues: results.reduce(
|
|
130
|
+
(sum, r) => sum + r.issues.filter((i) => getLevel(i.severity) === 4).length,
|
|
131
|
+
0
|
|
132
|
+
),
|
|
133
|
+
majorIssues: results.reduce(
|
|
134
|
+
(sum, r) => sum + r.issues.filter((i) => getLevel(i.severity) === 3).length,
|
|
135
|
+
0
|
|
136
|
+
),
|
|
137
|
+
score: finalScore,
|
|
138
|
+
rating: riskResult.rating,
|
|
139
|
+
recommendations: riskResult.recommendations
|
|
140
|
+
},
|
|
141
|
+
results
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
__require,
|
|
147
|
+
analyzeChangeAmplification
|
|
148
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/analyzer.ts
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import {
|
|
12
|
+
scanFiles,
|
|
13
|
+
calculateChangeAmplification,
|
|
14
|
+
getParser,
|
|
15
|
+
Severity,
|
|
16
|
+
IssueType
|
|
17
|
+
} from "@aiready/core";
|
|
18
|
+
async function analyzeChangeAmplification(options) {
|
|
19
|
+
const files = await scanFiles({
|
|
20
|
+
...options,
|
|
21
|
+
include: options.include || ["**/*.{ts,tsx,js,jsx,py,go}"]
|
|
22
|
+
});
|
|
23
|
+
const dependencyGraph = /* @__PURE__ */ new Map();
|
|
24
|
+
const reverseGraph = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
dependencyGraph.set(file, []);
|
|
27
|
+
reverseGraph.set(file, []);
|
|
28
|
+
}
|
|
29
|
+
let processed = 0;
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
processed++;
|
|
32
|
+
if (processed % 50 === 0 || processed === files.length) {
|
|
33
|
+
options.onProgress?.(
|
|
34
|
+
processed,
|
|
35
|
+
files.length,
|
|
36
|
+
`analyzing dependencies (${processed}/${files.length})`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parser = await getParser(file);
|
|
41
|
+
if (!parser) continue;
|
|
42
|
+
const content = fs.readFileSync(file, "utf8");
|
|
43
|
+
const parseResult = parser.parse(content, file);
|
|
44
|
+
const dependencies = parseResult.imports.map((i) => i.source);
|
|
45
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
46
|
+
for (const dep of dependencies) {
|
|
47
|
+
const depDir = path.dirname(file);
|
|
48
|
+
let resolvedPath;
|
|
49
|
+
if (dep.startsWith(".")) {
|
|
50
|
+
const absoluteDepBase = path.resolve(depDir, dep);
|
|
51
|
+
for (const ext of extensions) {
|
|
52
|
+
const withExt = absoluteDepBase + ext;
|
|
53
|
+
if (files.includes(withExt)) {
|
|
54
|
+
resolvedPath = withExt;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!resolvedPath) {
|
|
59
|
+
for (const ext of extensions) {
|
|
60
|
+
const withIndex = path.join(absoluteDepBase, `index${ext}`);
|
|
61
|
+
if (files.includes(withIndex)) {
|
|
62
|
+
resolvedPath = withIndex;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const depWithoutExt = dep.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
69
|
+
resolvedPath = files.find((f) => {
|
|
70
|
+
const fWithoutExt = f.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
71
|
+
return fWithoutExt === depWithoutExt || fWithoutExt.endsWith(`/${depWithoutExt}`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (resolvedPath && resolvedPath !== file) {
|
|
75
|
+
dependencyGraph.get(file)?.push(resolvedPath);
|
|
76
|
+
reverseGraph.get(resolvedPath)?.push(file);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
void err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const fileMetrics = files.map((file) => {
|
|
84
|
+
const fanOut = dependencyGraph.get(file)?.length || 0;
|
|
85
|
+
const fanIn = reverseGraph.get(file)?.length || 0;
|
|
86
|
+
return { file, fanOut, fanIn };
|
|
87
|
+
});
|
|
88
|
+
const riskResult = calculateChangeAmplification({ files: fileMetrics });
|
|
89
|
+
let finalScore = riskResult.score;
|
|
90
|
+
if (finalScore === 0 && files.length > 0 && riskResult.rating !== "explosive") {
|
|
91
|
+
finalScore = 10;
|
|
92
|
+
}
|
|
93
|
+
const results = [];
|
|
94
|
+
const getLevel = (s) => {
|
|
95
|
+
if (s === Severity.Critical || s === "critical") return 4;
|
|
96
|
+
if (s === Severity.Major || s === "major") return 3;
|
|
97
|
+
if (s === Severity.Minor || s === "minor") return 2;
|
|
98
|
+
if (s === Severity.Info || s === "info") return 1;
|
|
99
|
+
return 0;
|
|
100
|
+
};
|
|
101
|
+
for (const hotspot of riskResult.hotspots) {
|
|
102
|
+
const issues = [];
|
|
103
|
+
const fileName = path.basename(hotspot.file).toLowerCase();
|
|
104
|
+
const isSharedUtility = fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx" || fileName.includes("logger") || fileName.includes("log.ts") || fileName.includes("constants") || fileName.includes("types.ts") || fileName.includes("enums.ts") || fileName.startsWith("all-") || fileName.endsWith(".meta.ts") || fileName.endsWith(".meta.js");
|
|
105
|
+
if (hotspot.amplificationFactor > 20 && !isSharedUtility) {
|
|
106
|
+
issues.push({
|
|
107
|
+
type: IssueType.ChangeAmplification,
|
|
108
|
+
severity: hotspot.amplificationFactor > 40 ? Severity.Critical : Severity.Major,
|
|
109
|
+
message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
|
|
110
|
+
location: { file: hotspot.file, line: 1 },
|
|
111
|
+
suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (hotspot.amplificationFactor > 5 && !isSharedUtility) {
|
|
115
|
+
results.push({
|
|
116
|
+
fileName: hotspot.file,
|
|
117
|
+
issues,
|
|
118
|
+
metrics: {
|
|
119
|
+
aiSignalClarityScore: 100 - hotspot.amplificationFactor
|
|
120
|
+
// Just a rough score
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
summary: {
|
|
127
|
+
totalFiles: files.length,
|
|
128
|
+
totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
|
|
129
|
+
criticalIssues: results.reduce(
|
|
130
|
+
(sum, r) => sum + r.issues.filter((i) => getLevel(i.severity) === 4).length,
|
|
131
|
+
0
|
|
132
|
+
),
|
|
133
|
+
majorIssues: results.reduce(
|
|
134
|
+
(sum, r) => sum + r.issues.filter((i) => getLevel(i.severity) === 3).length,
|
|
135
|
+
0
|
|
136
|
+
),
|
|
137
|
+
score: finalScore,
|
|
138
|
+
rating: riskResult.rating,
|
|
139
|
+
recommendations: riskResult.recommendations
|
|
140
|
+
},
|
|
141
|
+
results
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
__require,
|
|
147
|
+
analyzeChangeAmplification
|
|
148
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -128,7 +128,9 @@ async function analyzeChangeAmplification(options) {
|
|
|
128
128
|
};
|
|
129
129
|
for (const hotspot of riskResult.hotspots) {
|
|
130
130
|
const issues = [];
|
|
131
|
-
|
|
131
|
+
const fileName = path.basename(hotspot.file).toLowerCase();
|
|
132
|
+
const isSharedUtility = fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx" || fileName.includes("logger") || fileName.includes("log.ts") || fileName.includes("constants") || fileName.includes("types.ts") || fileName.includes("enums.ts") || fileName.startsWith("all-") || fileName.endsWith(".meta.ts") || fileName.endsWith(".meta.js");
|
|
133
|
+
if (hotspot.amplificationFactor > 20 && !isSharedUtility) {
|
|
132
134
|
issues.push({
|
|
133
135
|
type: import_core.IssueType.ChangeAmplification,
|
|
134
136
|
severity: hotspot.amplificationFactor > 40 ? import_core.Severity.Critical : import_core.Severity.Major,
|
|
@@ -137,7 +139,7 @@ async function analyzeChangeAmplification(options) {
|
|
|
137
139
|
suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
|
|
138
140
|
});
|
|
139
141
|
}
|
|
140
|
-
if (hotspot.amplificationFactor > 5) {
|
|
142
|
+
if (hotspot.amplificationFactor > 5 && !isSharedUtility) {
|
|
141
143
|
results.push({
|
|
142
144
|
fileName: hotspot.file,
|
|
143
145
|
issues,
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -128,7 +128,9 @@ async function analyzeChangeAmplification(options) {
|
|
|
128
128
|
};
|
|
129
129
|
for (const hotspot of riskResult.hotspots) {
|
|
130
130
|
const issues = [];
|
|
131
|
-
|
|
131
|
+
const fileName = path.basename(hotspot.file).toLowerCase();
|
|
132
|
+
const isSharedUtility = fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx" || fileName.includes("logger") || fileName.includes("log.ts") || fileName.includes("constants") || fileName.includes("types.ts") || fileName.includes("enums.ts") || fileName.startsWith("all-") || fileName.endsWith(".meta.ts") || fileName.endsWith(".meta.js");
|
|
133
|
+
if (hotspot.amplificationFactor > 20 && !isSharedUtility) {
|
|
132
134
|
issues.push({
|
|
133
135
|
type: import_core.IssueType.ChangeAmplification,
|
|
134
136
|
severity: hotspot.amplificationFactor > 40 ? import_core.Severity.Critical : import_core.Severity.Major,
|
|
@@ -137,7 +139,7 @@ async function analyzeChangeAmplification(options) {
|
|
|
137
139
|
suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
|
|
138
140
|
});
|
|
139
141
|
}
|
|
140
|
-
if (hotspot.amplificationFactor > 5) {
|
|
142
|
+
if (hotspot.amplificationFactor > 5 && !isSharedUtility) {
|
|
141
143
|
results.push({
|
|
142
144
|
fileName: hotspot.file,
|
|
143
145
|
issues,
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/change-amplification",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.16",
|
|
4
4
|
"description": "AI-Readiness: Change Amplification Detection",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"commander": "^14.0.0",
|
|
11
11
|
"glob": "^13.0.0",
|
|
12
12
|
"chalk": "^5.3.0",
|
|
13
|
-
"@aiready/core": "0.24.
|
|
13
|
+
"@aiready/core": "0.24.19"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/node": "^24.0.0",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"test:watch": "vitest",
|
|
43
43
|
"lint": "eslint src --ext .ts",
|
|
44
44
|
"type-check": "tsc --noEmit",
|
|
45
|
-
"format-check": "prettier --check . --ignore-path ../../.prettierignore"
|
|
45
|
+
"format-check": "prettier --check . --ignore-path ../../.prettierignore",
|
|
46
|
+
"format": "prettier --write . --ignore-path ../../.prettierignore",
|
|
47
|
+
"lint:fix": "eslint . --fix"
|
|
46
48
|
}
|
|
47
49
|
}
|
package/src/analyzer.ts
CHANGED
|
@@ -135,7 +135,25 @@ export async function analyzeChangeAmplification(
|
|
|
135
135
|
|
|
136
136
|
for (const hotspot of riskResult.hotspots) {
|
|
137
137
|
const issues: ChangeAmplificationIssue[] = [];
|
|
138
|
-
|
|
138
|
+
|
|
139
|
+
// Check if this is a barrel/index file or a common utility pattern (intentional re-export or shared use)
|
|
140
|
+
const fileName = path.basename(hotspot.file).toLowerCase();
|
|
141
|
+
const isSharedUtility =
|
|
142
|
+
fileName === 'index.ts' ||
|
|
143
|
+
fileName === 'index.js' ||
|
|
144
|
+
fileName === 'index.tsx' ||
|
|
145
|
+
fileName === 'index.jsx' ||
|
|
146
|
+
fileName.includes('logger') ||
|
|
147
|
+
fileName.includes('log.ts') ||
|
|
148
|
+
fileName.includes('constants') ||
|
|
149
|
+
fileName.includes('types.ts') ||
|
|
150
|
+
fileName.includes('enums.ts') ||
|
|
151
|
+
fileName.startsWith('all-') ||
|
|
152
|
+
fileName.endsWith('.meta.ts') ||
|
|
153
|
+
fileName.endsWith('.meta.js');
|
|
154
|
+
|
|
155
|
+
// Only flag high amplification if it's NOT a barrel file or shared utility
|
|
156
|
+
if (hotspot.amplificationFactor > 20 && !isSharedUtility) {
|
|
139
157
|
issues.push({
|
|
140
158
|
type: IssueType.ChangeAmplification,
|
|
141
159
|
severity:
|
|
@@ -147,7 +165,8 @@ export async function analyzeChangeAmplification(
|
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
// We only push results for files that have either high fan-in or fan-out
|
|
150
|
-
|
|
168
|
+
// Also exclude barrel files and shared utilities from the results
|
|
169
|
+
if (hotspot.amplificationFactor > 5 && !isSharedUtility) {
|
|
151
170
|
results.push({
|
|
152
171
|
fileName: hotspot.file,
|
|
153
172
|
issues,
|