@aiready/change-amplification 0.13.18 → 0.13.20

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,23 +1,24 @@
1
-
2
- > @aiready/change-amplification@0.13.18 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.72 KB
12
- ESM dist/chunk-OBBL7HKE.mjs 4.82 KB
13
- ESM dist/cli.mjs 2.78 KB
14
- ESM ⚡️ Build success in 305ms
15
- CJS dist/index.js 7.93 KB
16
- CJS dist/cli.js 9.02 KB
17
- CJS ⚡️ Build success in 305ms
18
- DTS Build start
19
- DTS ⚡️ Build success in 12268ms
20
- DTS dist/cli.d.ts 152.00 B
21
- DTS dist/index.d.ts 1.30 KB
22
- DTS dist/cli.d.mts 152.00 B
23
- DTS dist/index.d.mts 1.30 KB
1
+
2
+ 
3
+ > @aiready/change-amplification@0.13.20 build /Users/pengcao/projects/aiready/packages/change-amplification
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
+ CJS dist/cli.js 8.51 KB
13
+ CJS dist/index.js 7.74 KB
14
+ CJS ⚡️ Build success in 331ms
15
+ ESM dist/index.mjs 1.53 KB
16
+ ESM dist/cli.mjs 2.41 KB
17
+ ESM dist/chunk-SPXGOPNW.mjs 4.83 KB
18
+ ESM ⚡️ Build success in 418ms
19
+ DTS Build start
20
+ DTS ⚡️ Build success in 12130ms
21
+ DTS dist/cli.d.ts 152.00 B
22
+ DTS dist/index.d.ts 1.12 KB
23
+ DTS dist/cli.d.mts 152.00 B
24
+ DTS dist/index.d.mts 1.12 KB
@@ -1,10 +1,5 @@
1
-
2
- > @aiready/change-amplification@0.13.2 lint /Users/pengcao/projects/aiready/packages/change-amplification
3
- > eslint src --ext .ts
4
-
5
-
6
- /Users/pengcao/projects/aiready/packages/change-amplification/src/__tests__/cli.test.ts
7
- 3:13 warning 'analyzer' is defined but never used @typescript-eslint/no-unused-vars
8
-
9
- ✖ 1 problem (0 errors, 1 warning)
10
-
1
+
2
+ 
3
+ > @aiready/change-amplification@0.13.18 lint /Users/pengcao/projects/aiready/packages/change-amplification
4
+ > eslint src --ext .ts
5
+
@@ -1,22 +1,24 @@
1
-
2
- > @aiready/change-amplification@0.13.14 test /Users/pengcao/projects/aiready/packages/change-amplification
3
- > vitest run
4
-
5
-
6
-  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/change-amplification
7
-
8
- ✓ src/__tests__/dummy.test.ts (1 test) 4ms
9
- ✓ src/__tests__/cli.test.ts (2 tests) 14ms
10
- ✓ src/__tests__/scoring.test.ts (2 tests) 2ms
11
- ✓ src/__tests__/provider.test.ts (2 tests) 2ms
12
- stdout | src/__tests__/analyzer.test.ts > analyzeChangeAmplification reproduction > should see how it gets to 0
13
- Resulting score for highly coupled: 27
14
- Rating: explosive
15
-
16
- ✓ src/__tests__/analyzer.test.ts (2 tests) 4ms
17
-
18
-  Test Files  5 passed (5)
19
-  Tests  9 passed (9)
20
-  Start at  12:24:39
21
-  Duration  4.16s (transform 3.05s, setup 0ms, import 8.87s, tests 25ms, environment 0ms)
22
-
1
+
2
+ 
3
+ > @aiready/change-amplification@0.13.19 test /Users/pengcao/projects/aiready/packages/change-amplification
4
+ > vitest run
5
+
6
+ [?25l
7
+  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/change-amplification
8
+
9
+ ✓ src/__tests__/dummy.test.ts (1 test) 9ms
10
+ ✓ src/__tests__/provider.test.ts (2 tests) 23ms
11
+ ✓ src/__tests__/cli.test.ts (2 tests) 2ms
12
+ ✓ src/__tests__/scoring.test.ts (2 tests) 241ms
13
+ stdout | src/__tests__/analyzer.test.ts > analyzeChangeAmplification reproduction > should see how it gets to 0
14
+ Resulting score for highly coupled: 27
15
+ Rating: explosive
16
+
17
+ ✓ src/__tests__/analyzer.test.ts (2 tests) 184ms
18
+
19
+  Test Files  5 passed (5)
20
+  Tests  9 passed (9)
21
+  Start at  23:31:08
22
+  Duration  11.56s (transform 5.08s, setup 0ms, import 39.45s, tests 459ms, environment 15ms)
23
+
24
+ [?25h
@@ -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
- console.log(import_chalk.default.bold("\n\u{1F310} Change Amplification Analysis\n"));
187
- console.log(`Rating: ${import_chalk.default.bold(report.summary.rating)}`);
188
- console.log(`Score: ${Math.round(report.summary.score ?? 0)}/100`);
189
- console.log(`Critical Issues: ${report.summary.criticalIssues}`);
190
- console.log(`Major Issues: ${report.summary.majorIssues}`);
191
- if (report.summary.recommendations.length > 0) {
192
- console.log(import_chalk.default.bold("\nRecommendations:"));
193
- for (const rec of report.summary.recommendations) {
194
- console.log(import_chalk.default.cyan(`\u2022 ${rec}`));
195
- }
196
- }
197
- if (report.results.length > 0) {
198
- console.log(import_chalk.default.bold("\nHotspots:"));
199
- for (const result of report.results) {
200
- console.log(`
201
- \u{1F4C4} ${import_chalk.default.cyan(result.fileName)}`);
202
- for (const issue of result.issues) {
203
- const color = issue.severity === "critical" ? import_chalk.default.red : import_chalk.default.yellow;
204
- console.log(` ${color("\u25A0")} ${issue.message}`);
205
- console.log(` ${import_chalk.default.dim("Suggestion: " + issue.suggestion)}`);
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-OBBL7HKE.mjs";
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
- console.log(chalk.bold("\n\u{1F310} Change Amplification Analysis\n"));
27
- console.log(`Rating: ${chalk.bold(report.summary.rating)}`);
28
- console.log(`Score: ${Math.round(report.summary.score ?? 0)}/100`);
29
- console.log(`Critical Issues: ${report.summary.criticalIssues}`);
30
- console.log(`Major Issues: ${report.summary.majorIssues}`);
31
- if (report.summary.recommendations.length > 0) {
32
- console.log(chalk.bold("\nRecommendations:"));
33
- for (const rec of report.summary.recommendations) {
34
- console.log(chalk.cyan(`\u2022 ${rec}`));
35
- }
36
- }
37
- if (report.results.length > 0) {
38
- console.log(chalk.bold("\nHotspots:"));
39
- for (const result of report.results) {
40
- console.log(`
41
- \u{1F4C4} ${chalk.cyan(result.fileName)}`);
42
- for (const issue of result.issues) {
43
- const color = issue.severity === "critical" ? chalk.red : chalk.yellow;
44
- console.log(` ${color("\u25A0")} ${issue.message}`);
45
- console.log(` ${chalk.dim("Suggestion: " + issue.suggestion)}`);
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, ToolScoringOutput } from '@aiready/core';
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): ToolScoringOutput;
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, ToolScoringOutput } from '@aiready/core';
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): ToolScoringOutput;
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
- const factors = [
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
- rawMetrics: {
202
+ rawData: {
215
203
  totalFiles: summary.totalFiles,
216
- totalIssues: summary.totalIssues,
217
- rating: summary.rating
204
+ totalIssues: summary.totalIssues
218
205
  },
219
- factors,
220
- recommendations
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-OBBL7HKE.mjs";
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
- const factors = [
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
- rawMetrics: {
43
+ rawData: {
56
44
  totalFiles: summary.totalFiles,
57
- totalIssues: summary.totalIssues,
58
- rating: summary.rating
45
+ totalIssues: summary.totalIssues
59
46
  },
60
- factors,
61
- recommendations
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.18",
3
+ "version": "0.13.20",
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.19"
13
+ "@aiready/core": "0.23.21"
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).mockReturnValue({
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).mockReturnValue({
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('High coupling');
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
@@ -46,7 +46,7 @@ export async function analyzeChangeAmplification(
46
46
  }
47
47
 
48
48
  try {
49
- const parser = getParser(file);
49
+ const parser = await getParser(file);
50
50
  if (!parser) continue;
51
51
 
52
52
  const content = fs.readFileSync(file, 'utf8');
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
- console.log(chalk.bold('\n🌐 Change Amplification Analysis\n'));
31
- console.log(`Rating: ${chalk.bold(report.summary.rating)}`);
32
- console.log(`Score: ${Math.round(report.summary.score ?? 0)}/100`);
33
- console.log(`Critical Issues: ${report.summary.criticalIssues}`);
34
- console.log(`Major Issues: ${report.summary.majorIssues}`);
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
- if (report.summary.recommendations.length > 0) {
37
- console.log(chalk.bold('\nRecommendations:'));
38
- for (const rec of report.summary.recommendations) {
39
- console.log(chalk.cyan(`• ${rec}`));
40
- }
41
- }
42
-
43
- if (report.results.length > 0) {
44
- console.log(chalk.bold('\nHotspots:'));
45
- for (const result of report.results) {
46
- console.log(`\n📄 ${chalk.cyan(result.fileName)}`);
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
- ): ToolScoringOutput {
9
+ ): any {
15
10
  const { summary } = report;
16
11
 
17
- const factors: ToolScoringOutput['factors'] = [
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
- rawMetrics: {
15
+ rawData: {
39
16
  totalFiles: summary.totalFiles,
40
17
  totalIssues: summary.totalIssues,
41
- rating: summary.rating,
42
18
  },
43
- factors,
44
- recommendations,
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
  }