@aiready/change-amplification 0.13.19 → 0.13.21
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 +23 -24
- package/.turbo/turbo-lint.log +4 -5
- package/.turbo/turbo-test.log +22 -24
- package/dist/chunk-SPXGOPNW.mjs +146 -0
- package/dist/cli.js +24 -30
- package/dist/cli.mjs +24 -30
- package/dist/index.d.mts +2 -6
- package/dist/index.d.ts +2 -6
- package/dist/index.js +14 -20
- package/dist/index.mjs +15 -21
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +10 -5
- package/src/__tests__/scoring.test.ts +1 -1
- package/src/analyzer.ts +1 -1
- package/src/cli.ts +23 -30
- package/src/scoring.ts +14 -31
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
DTS Build
|
|
20
|
-
DTS
|
|
21
|
-
DTS dist/
|
|
22
|
-
DTS dist/
|
|
23
|
-
DTS dist/
|
|
24
|
-
DTS dist/index.d.mts 1.30 KB
|
|
1
|
+
|
|
2
|
+
> @aiready/change-amplification@0.13.21 build /Users/pengcao/projects/aiready/packages/change-amplification
|
|
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
|
+
ESM dist/index.mjs 1.53 KB
|
|
12
|
+
ESM dist/chunk-SPXGOPNW.mjs 4.83 KB
|
|
13
|
+
ESM dist/cli.mjs 2.41 KB
|
|
14
|
+
ESM ⚡️ Build success in 61ms
|
|
15
|
+
CJS dist/index.js 7.74 KB
|
|
16
|
+
CJS dist/cli.js 8.51 KB
|
|
17
|
+
CJS ⚡️ Build success in 61ms
|
|
18
|
+
DTS Build start
|
|
19
|
+
DTS ⚡️ Build success in 9509ms
|
|
20
|
+
DTS dist/cli.d.ts 152.00 B
|
|
21
|
+
DTS dist/index.d.ts 1.12 KB
|
|
22
|
+
DTS dist/cli.d.mts 152.00 B
|
|
23
|
+
DTS dist/index.d.mts 1.12 KB
|
package/.turbo/turbo-lint.log
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
|
|
2
|
+
> @aiready/change-amplification@0.13.20 lint /Users/pengcao/projects/aiready/packages/change-amplification
|
|
3
|
+
> eslint src --ext .ts
|
|
4
|
+
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[32m✓[39m src/__tests__/
|
|
10
|
-
|
|
11
|
-
[
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[2m
|
|
20
|
-
[2m
|
|
21
|
-
[2m
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[?25h
|
|
1
|
+
|
|
2
|
+
> @aiready/change-amplification@0.13.20 test /Users/pengcao/projects/aiready/packages/change-amplification
|
|
3
|
+
> vitest run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/change-amplification[39m
|
|
7
|
+
|
|
8
|
+
[32m✓[39m src/__tests__/dummy.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
9
|
+
[32m✓[39m src/__tests__/cli.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
10
|
+
[90mstdout[2m | src/__tests__/analyzer.test.ts[2m > [22m[2manalyzeChangeAmplification reproduction[2m > [22m[2mshould see how it gets to 0
|
|
11
|
+
[22m[39mResulting score for highly coupled: [33m27[39m
|
|
12
|
+
Rating: explosive
|
|
13
|
+
|
|
14
|
+
[32m✓[39m src/__tests__/scoring.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
15
|
+
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
|
16
|
+
[32m✓[39m src/__tests__/provider.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
17
|
+
|
|
18
|
+
[2m Test Files [22m [1m[32m5 passed[39m[22m[90m (5)[39m
|
|
19
|
+
[2m Tests [22m [1m[32m9 passed[39m[22m[90m (9)[39m
|
|
20
|
+
[2m Start at [22m 19:03:33
|
|
21
|
+
[2m Duration [22m 2.87s[2m (transform 2.93s, setup 0ms, import 8.52s, tests 29ms, environment 0ms)[22m
|
|
22
|
+
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
if (hotspot.amplificationFactor > 20) {
|
|
104
|
+
issues.push({
|
|
105
|
+
type: IssueType.ChangeAmplification,
|
|
106
|
+
severity: hotspot.amplificationFactor > 40 ? Severity.Critical : Severity.Major,
|
|
107
|
+
message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
|
|
108
|
+
location: { file: hotspot.file, line: 1 },
|
|
109
|
+
suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (hotspot.amplificationFactor > 5) {
|
|
113
|
+
results.push({
|
|
114
|
+
fileName: hotspot.file,
|
|
115
|
+
issues,
|
|
116
|
+
metrics: {
|
|
117
|
+
aiSignalClarityScore: 100 - hotspot.amplificationFactor
|
|
118
|
+
// Just a rough score
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
summary: {
|
|
125
|
+
totalFiles: files.length,
|
|
126
|
+
totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
|
|
127
|
+
criticalIssues: results.reduce(
|
|
128
|
+
(sum, r) => sum + r.issues.filter((i) => getLevel(i.severity) === 4).length,
|
|
129
|
+
0
|
|
130
|
+
),
|
|
131
|
+
majorIssues: results.reduce(
|
|
132
|
+
(sum, r) => sum + r.issues.filter((i) => getLevel(i.severity) === 3).length,
|
|
133
|
+
0
|
|
134
|
+
),
|
|
135
|
+
score: finalScore,
|
|
136
|
+
rating: riskResult.rating,
|
|
137
|
+
recommendations: riskResult.recommendations
|
|
138
|
+
},
|
|
139
|
+
results
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
__require,
|
|
145
|
+
analyzeChangeAmplification
|
|
146
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -65,7 +65,7 @@ async function analyzeChangeAmplification(options) {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
try {
|
|
68
|
-
const parser = (0, import_core.getParser)(file);
|
|
68
|
+
const parser = await (0, import_core.getParser)(file);
|
|
69
69
|
if (!parser) continue;
|
|
70
70
|
const content = fs.readFileSync(file, "utf8");
|
|
71
71
|
const parseResult = parser.parse(content, file);
|
|
@@ -169,6 +169,7 @@ async function analyzeChangeAmplification(options) {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
// src/cli.ts
|
|
172
|
+
var import_core2 = require("@aiready/core");
|
|
172
173
|
var changeAmplificationAction = async (directory, options) => {
|
|
173
174
|
try {
|
|
174
175
|
const resolvedDir = path2.resolve(process.cwd(), directory);
|
|
@@ -183,35 +184,28 @@ var changeAmplificationAction = async (directory, options) => {
|
|
|
183
184
|
fs2.writeFileSync(outputPath, JSON.stringify(report, null, 2));
|
|
184
185
|
return;
|
|
185
186
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} else {
|
|
209
|
-
console.log(
|
|
210
|
-
import_chalk.default.green(
|
|
211
|
-
"\n\u2728 No change amplification issues found. Architecture is well contained."
|
|
212
|
-
)
|
|
213
|
-
);
|
|
214
|
-
}
|
|
187
|
+
(0, import_core2.displayStandardConsoleReport)({
|
|
188
|
+
title: "\u{1F310} Change Amplification Analysis",
|
|
189
|
+
score: report.summary.score ?? 0,
|
|
190
|
+
rating: report.summary.rating,
|
|
191
|
+
dimensions: [
|
|
192
|
+
{
|
|
193
|
+
name: "Critical Issues",
|
|
194
|
+
value: (report.summary.criticalIssues ?? 0) * 10
|
|
195
|
+
},
|
|
196
|
+
{ name: "Major Issues", value: (report.summary.majorIssues ?? 0) * 5 }
|
|
197
|
+
],
|
|
198
|
+
issues: report.results.flatMap(
|
|
199
|
+
(r) => r.issues.map((i) => ({
|
|
200
|
+
...i,
|
|
201
|
+
message: `[${r.fileName}] ${i.message}`
|
|
202
|
+
}))
|
|
203
|
+
),
|
|
204
|
+
recommendations: report.summary.recommendations,
|
|
205
|
+
elapsedTime: "0",
|
|
206
|
+
// Not tracked in this action yet
|
|
207
|
+
noIssuesMessage: "\u2728 No change amplification issues found. Architecture is well contained."
|
|
208
|
+
});
|
|
215
209
|
} catch (error) {
|
|
216
210
|
console.error(
|
|
217
211
|
import_chalk.default.red("Error during change amplification analysis:"),
|
package/dist/cli.mjs
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
__require,
|
|
4
4
|
analyzeChangeAmplification
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SPXGOPNW.mjs";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import * as fs from "fs";
|
|
12
|
+
import { displayStandardConsoleReport } from "@aiready/core";
|
|
12
13
|
var changeAmplificationAction = async (directory, options) => {
|
|
13
14
|
try {
|
|
14
15
|
const resolvedDir = path.resolve(process.cwd(), directory);
|
|
@@ -23,35 +24,28 @@ var changeAmplificationAction = async (directory, options) => {
|
|
|
23
24
|
fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
console.log(
|
|
50
|
-
chalk.green(
|
|
51
|
-
"\n\u2728 No change amplification issues found. Architecture is well contained."
|
|
52
|
-
)
|
|
53
|
-
);
|
|
54
|
-
}
|
|
27
|
+
displayStandardConsoleReport({
|
|
28
|
+
title: "\u{1F310} Change Amplification Analysis",
|
|
29
|
+
score: report.summary.score ?? 0,
|
|
30
|
+
rating: report.summary.rating,
|
|
31
|
+
dimensions: [
|
|
32
|
+
{
|
|
33
|
+
name: "Critical Issues",
|
|
34
|
+
value: (report.summary.criticalIssues ?? 0) * 10
|
|
35
|
+
},
|
|
36
|
+
{ name: "Major Issues", value: (report.summary.majorIssues ?? 0) * 5 }
|
|
37
|
+
],
|
|
38
|
+
issues: report.results.flatMap(
|
|
39
|
+
(r) => r.issues.map((i) => ({
|
|
40
|
+
...i,
|
|
41
|
+
message: `[${r.fileName}] ${i.message}`
|
|
42
|
+
}))
|
|
43
|
+
),
|
|
44
|
+
recommendations: report.summary.recommendations,
|
|
45
|
+
elapsedTime: "0",
|
|
46
|
+
// Not tracked in this action yet
|
|
47
|
+
noIssuesMessage: "\u2728 No change amplification issues found. Architecture is well contained."
|
|
48
|
+
});
|
|
55
49
|
} catch (error) {
|
|
56
50
|
console.error(
|
|
57
51
|
chalk.red("Error during change amplification analysis:"),
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _aiready_core from '@aiready/core';
|
|
2
|
-
import { Issue, IssueType, ScanOptions, SpokeOutput, AnalysisResult
|
|
2
|
+
import { Issue, IssueType, ScanOptions, SpokeOutput, AnalysisResult } from '@aiready/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Change Amplification Tool Provider
|
|
@@ -21,11 +21,7 @@ declare function analyzeChangeAmplification(options: ChangeAmplificationOptions)
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Convert change amplification report into a standardized ToolScoringOutput.
|
|
24
|
-
*
|
|
25
|
-
* @param report - The detailed change amplification report.
|
|
26
|
-
* @returns Standardized scoring and risk factor breakdown.
|
|
27
|
-
* @lastUpdated 2026-03-18
|
|
28
24
|
*/
|
|
29
|
-
declare function calculateChangeAmplificationScore(report: ChangeAmplificationReport):
|
|
25
|
+
declare function calculateChangeAmplificationScore(report: ChangeAmplificationReport): any;
|
|
30
26
|
|
|
31
27
|
export { type ChangeAmplificationIssue, type ChangeAmplificationOptions, ChangeAmplificationProvider, type ChangeAmplificationReport, type FileChangeAmplificationResult, analyzeChangeAmplification, calculateChangeAmplificationScore };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _aiready_core from '@aiready/core';
|
|
2
|
-
import { Issue, IssueType, ScanOptions, SpokeOutput, AnalysisResult
|
|
2
|
+
import { Issue, IssueType, ScanOptions, SpokeOutput, AnalysisResult } from '@aiready/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Change Amplification Tool Provider
|
|
@@ -21,11 +21,7 @@ declare function analyzeChangeAmplification(options: ChangeAmplificationOptions)
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Convert change amplification report into a standardized ToolScoringOutput.
|
|
24
|
-
*
|
|
25
|
-
* @param report - The detailed change amplification report.
|
|
26
|
-
* @returns Standardized scoring and risk factor breakdown.
|
|
27
|
-
* @lastUpdated 2026-03-18
|
|
28
24
|
*/
|
|
29
|
-
declare function calculateChangeAmplificationScore(report: ChangeAmplificationReport):
|
|
25
|
+
declare function calculateChangeAmplificationScore(report: ChangeAmplificationReport): any;
|
|
30
26
|
|
|
31
27
|
export { type ChangeAmplificationIssue, type ChangeAmplificationOptions, ChangeAmplificationProvider, type ChangeAmplificationReport, type FileChangeAmplificationResult, analyzeChangeAmplification, calculateChangeAmplificationScore };
|
package/dist/index.js
CHANGED
|
@@ -66,7 +66,7 @@ async function analyzeChangeAmplification(options) {
|
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
try {
|
|
69
|
-
const parser = (0, import_core.getParser)(file);
|
|
69
|
+
const parser = await (0, import_core.getParser)(file);
|
|
70
70
|
if (!parser) continue;
|
|
71
71
|
const content = fs.readFileSync(file, "utf8");
|
|
72
72
|
const parseResult = parser.parse(content, file);
|
|
@@ -196,29 +196,23 @@ var ChangeAmplificationProvider = (0, import_core2.createProvider)({
|
|
|
196
196
|
var import_core3 = require("@aiready/core");
|
|
197
197
|
function calculateChangeAmplificationScore(report) {
|
|
198
198
|
const { summary } = report;
|
|
199
|
-
|
|
200
|
-
{
|
|
201
|
-
name: "Graph Stability",
|
|
202
|
-
impact: Math.round((summary.score ?? 0) - 50),
|
|
203
|
-
description: (summary.score ?? 0) < 30 ? "High coupling detected in core modules" : "Stable dependency structure"
|
|
204
|
-
}
|
|
205
|
-
];
|
|
206
|
-
const recommendations = summary.recommendations.map((rec) => ({
|
|
207
|
-
action: rec,
|
|
208
|
-
estimatedImpact: 10,
|
|
209
|
-
priority: (summary.score ?? 0) < 50 ? "high" : "medium"
|
|
210
|
-
}));
|
|
211
|
-
return {
|
|
199
|
+
return (0, import_core3.buildStandardToolScore)({
|
|
212
200
|
toolName: import_core3.ToolName.ChangeAmplification,
|
|
213
201
|
score: summary.score ?? 0,
|
|
214
|
-
|
|
202
|
+
rawData: {
|
|
215
203
|
totalFiles: summary.totalFiles,
|
|
216
|
-
totalIssues: summary.totalIssues
|
|
217
|
-
rating: summary.rating
|
|
204
|
+
totalIssues: summary.totalIssues
|
|
218
205
|
},
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
206
|
+
dimensions: {
|
|
207
|
+
graphStability: summary.score ?? 0
|
|
208
|
+
},
|
|
209
|
+
dimensionNames: {
|
|
210
|
+
graphStability: "Graph Stability"
|
|
211
|
+
},
|
|
212
|
+
recommendations: summary.recommendations,
|
|
213
|
+
recommendationImpact: 10,
|
|
214
|
+
rating: summary.rating
|
|
215
|
+
});
|
|
222
216
|
}
|
|
223
217
|
|
|
224
218
|
// src/index.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeChangeAmplification
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SPXGOPNW.mjs";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { ToolRegistry } from "@aiready/core";
|
|
@@ -34,32 +34,26 @@ var ChangeAmplificationProvider = createProvider({
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
// src/scoring.ts
|
|
37
|
-
import { ToolName as ToolName2 } from "@aiready/core";
|
|
37
|
+
import { ToolName as ToolName2, buildStandardToolScore } from "@aiready/core";
|
|
38
38
|
function calculateChangeAmplificationScore(report) {
|
|
39
39
|
const { summary } = report;
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
name: "Graph Stability",
|
|
43
|
-
impact: Math.round((summary.score ?? 0) - 50),
|
|
44
|
-
description: (summary.score ?? 0) < 30 ? "High coupling detected in core modules" : "Stable dependency structure"
|
|
45
|
-
}
|
|
46
|
-
];
|
|
47
|
-
const recommendations = summary.recommendations.map((rec) => ({
|
|
48
|
-
action: rec,
|
|
49
|
-
estimatedImpact: 10,
|
|
50
|
-
priority: (summary.score ?? 0) < 50 ? "high" : "medium"
|
|
51
|
-
}));
|
|
52
|
-
return {
|
|
40
|
+
return buildStandardToolScore({
|
|
53
41
|
toolName: ToolName2.ChangeAmplification,
|
|
54
42
|
score: summary.score ?? 0,
|
|
55
|
-
|
|
43
|
+
rawData: {
|
|
56
44
|
totalFiles: summary.totalFiles,
|
|
57
|
-
totalIssues: summary.totalIssues
|
|
58
|
-
rating: summary.rating
|
|
45
|
+
totalIssues: summary.totalIssues
|
|
59
46
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
dimensions: {
|
|
48
|
+
graphStability: summary.score ?? 0
|
|
49
|
+
},
|
|
50
|
+
dimensionNames: {
|
|
51
|
+
graphStability: "Graph Stability"
|
|
52
|
+
},
|
|
53
|
+
recommendations: summary.recommendations,
|
|
54
|
+
recommendationImpact: 10,
|
|
55
|
+
rating: summary.rating
|
|
56
|
+
});
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/change-amplification",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.21",
|
|
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.23.
|
|
13
|
+
"@aiready/core": "0.23.22"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/node": "^24.0.0",
|
|
@@ -17,14 +17,16 @@ describe('analyzeChangeAmplification reproduction', () => {
|
|
|
17
17
|
it('should not return 0 if there are some dependencies but not crazy', async () => {
|
|
18
18
|
const files = ['src/a.ts', 'src/b.ts', 'src/c.ts'];
|
|
19
19
|
(scanFiles as any).mockResolvedValue(files);
|
|
20
|
-
(getParser as any).
|
|
20
|
+
(getParser as any).mockResolvedValue({
|
|
21
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
21
22
|
parse: (content: string) => {
|
|
22
23
|
if (content.includes('import b'))
|
|
23
|
-
return { imports: [{ source: './b' }] };
|
|
24
|
+
return { imports: [{ source: './b' }], exports: [] };
|
|
24
25
|
if (content.includes('import c'))
|
|
25
|
-
return { imports: [{ source: './c' }] };
|
|
26
|
-
return { imports: [] };
|
|
26
|
+
return { imports: [{ source: './c' }], exports: [] };
|
|
27
|
+
return { imports: [], exports: [] };
|
|
27
28
|
},
|
|
29
|
+
language: 'typescript',
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
(fs.readFileSync as any).mockImplementation((file: string) => {
|
|
@@ -42,10 +44,13 @@ describe('analyzeChangeAmplification reproduction', () => {
|
|
|
42
44
|
// Creating a highly coupled scenario
|
|
43
45
|
const files = Array.from({ length: 20 }, (_, i) => `src/file${i}.ts`);
|
|
44
46
|
(scanFiles as any).mockResolvedValue(files);
|
|
45
|
-
(getParser as any).
|
|
47
|
+
(getParser as any).mockResolvedValue({
|
|
48
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
46
49
|
parse: () => ({
|
|
50
|
+
exports: [],
|
|
47
51
|
imports: files.map((f) => ({ source: f })), // Everyone imports everyone
|
|
48
52
|
}),
|
|
53
|
+
language: 'typescript',
|
|
49
54
|
});
|
|
50
55
|
(fs.readFileSync as any).mockReturnValue('import everything');
|
|
51
56
|
|
|
@@ -37,7 +37,7 @@ describe('Change Amplification Scoring', () => {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const scoring = calculateChangeAmplificationScore(lowScoreReport);
|
|
40
|
-
expect(scoring.factors[0].description).toContain('
|
|
40
|
+
expect(scoring.factors[0].description).toContain('20/100');
|
|
41
41
|
expect(scoring.recommendations[0].priority).toBe('high');
|
|
42
42
|
});
|
|
43
43
|
});
|
package/src/analyzer.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as path from 'path';
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import { analyzeChangeAmplification } from './analyzer';
|
|
7
7
|
import type { ChangeAmplificationOptions } from './types';
|
|
8
|
+
import { displayStandardConsoleReport } from '@aiready/core';
|
|
8
9
|
|
|
9
10
|
export const changeAmplificationAction = async (
|
|
10
11
|
directory: string,
|
|
@@ -27,37 +28,29 @@ export const changeAmplificationAction = async (
|
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
displayStandardConsoleReport({
|
|
32
|
+
title: '🌐 Change Amplification Analysis',
|
|
33
|
+
score: report.summary.score ?? 0,
|
|
34
|
+
rating: report.summary.rating,
|
|
35
|
+
dimensions: [
|
|
36
|
+
{
|
|
37
|
+
name: 'Critical Issues',
|
|
38
|
+
value: (report.summary.criticalIssues ?? 0) * 10,
|
|
39
|
+
},
|
|
40
|
+
{ name: 'Major Issues', value: (report.summary.majorIssues ?? 0) * 5 },
|
|
41
|
+
],
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const issue of result.issues) {
|
|
48
|
-
const color =
|
|
49
|
-
issue.severity === 'critical' ? chalk.red : chalk.yellow;
|
|
50
|
-
console.log(` ${color('■')} ${issue.message}`);
|
|
51
|
-
console.log(` ${chalk.dim('Suggestion: ' + issue.suggestion)}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
console.log(
|
|
56
|
-
chalk.green(
|
|
57
|
-
'\n✨ No change amplification issues found. Architecture is well contained.'
|
|
58
|
-
)
|
|
59
|
-
);
|
|
60
|
-
}
|
|
43
|
+
issues: report.results.flatMap((r: any) =>
|
|
44
|
+
r.issues.map((i: any) => ({
|
|
45
|
+
...i,
|
|
46
|
+
message: `[${r.fileName}] ${i.message}`,
|
|
47
|
+
}))
|
|
48
|
+
),
|
|
49
|
+
recommendations: report.summary.recommendations,
|
|
50
|
+
elapsedTime: '0', // Not tracked in this action yet
|
|
51
|
+
noIssuesMessage:
|
|
52
|
+
'✨ No change amplification issues found. Architecture is well contained.',
|
|
53
|
+
});
|
|
61
54
|
} catch (error) {
|
|
62
55
|
console.error(
|
|
63
56
|
chalk.red('Error during change amplification analysis:'),
|
package/src/scoring.ts
CHANGED
|
@@ -1,46 +1,29 @@
|
|
|
1
|
-
import { ToolName } from '@aiready/core';
|
|
2
|
-
import type { ToolScoringOutput } from '@aiready/core';
|
|
1
|
+
import { ToolName, buildStandardToolScore } from '@aiready/core';
|
|
3
2
|
import type { ChangeAmplificationReport } from './types';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Convert change amplification report into a standardized ToolScoringOutput.
|
|
7
|
-
*
|
|
8
|
-
* @param report - The detailed change amplification report.
|
|
9
|
-
* @returns Standardized scoring and risk factor breakdown.
|
|
10
|
-
* @lastUpdated 2026-03-18
|
|
11
6
|
*/
|
|
12
7
|
export function calculateChangeAmplificationScore(
|
|
13
8
|
report: ChangeAmplificationReport
|
|
14
|
-
):
|
|
9
|
+
): any {
|
|
15
10
|
const { summary } = report;
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
name: 'Graph Stability',
|
|
20
|
-
impact: Math.round((summary.score ?? 0) - 50),
|
|
21
|
-
description:
|
|
22
|
-
(summary.score ?? 0) < 30
|
|
23
|
-
? 'High coupling detected in core modules'
|
|
24
|
-
: 'Stable dependency structure',
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const recommendations: ToolScoringOutput['recommendations'] =
|
|
29
|
-
summary.recommendations.map((rec: string) => ({
|
|
30
|
-
action: rec,
|
|
31
|
-
estimatedImpact: 10,
|
|
32
|
-
priority: (summary.score ?? 0) < 50 ? 'high' : 'medium',
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
return {
|
|
12
|
+
return buildStandardToolScore({
|
|
36
13
|
toolName: ToolName.ChangeAmplification,
|
|
37
14
|
score: summary.score ?? 0,
|
|
38
|
-
|
|
15
|
+
rawData: {
|
|
39
16
|
totalFiles: summary.totalFiles,
|
|
40
17
|
totalIssues: summary.totalIssues,
|
|
41
|
-
rating: summary.rating,
|
|
42
18
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
19
|
+
dimensions: {
|
|
20
|
+
graphStability: summary.score ?? 0,
|
|
21
|
+
},
|
|
22
|
+
dimensionNames: {
|
|
23
|
+
graphStability: 'Graph Stability',
|
|
24
|
+
},
|
|
25
|
+
recommendations: summary.recommendations,
|
|
26
|
+
recommendationImpact: 10,
|
|
27
|
+
rating: summary.rating,
|
|
28
|
+
});
|
|
46
29
|
}
|