@aiready/change-amplification 0.1.4

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.
@@ -0,0 +1,108 @@
1
+
2
+ 
3
+ > @aiready/change-amplification@0.1.4 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
+
13
+ [1:03:47 PM]  WARN  ā–² [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
14
+
15
+ package.json:33:6:
16
+  33 │ "types": "./dist/index.d.ts"
17
+ ╵ ~~~~~~~
18
+
19
+ The "import" condition comes earlier and will be used for all "import" statements:
20
+
21
+ package.json:31:6:
22
+  31 │ "import": "./dist/index.mjs",
23
+ ╵ ~~~~~~~~
24
+
25
+ The "require" condition comes earlier and will be used for all "require" calls:
26
+
27
+ package.json:32:6:
28
+  32 │ "require": "./dist/index.js",
29
+ ╵ ~~~~~~~~~
30
+
31
+
32
+
33
+
34
+ [1:03:47 PM]  WARN  ā–² [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
35
+
36
+ package.json:38:6:
37
+  38 │ "types": "./dist/cli.d.ts"
38
+ ╵ ~~~~~~~
39
+
40
+ The "import" condition comes earlier and will be used for all "import" statements:
41
+
42
+ package.json:36:6:
43
+  36 │ "import": "./dist/cli.mjs",
44
+ ╵ ~~~~~~~~
45
+
46
+ The "require" condition comes earlier and will be used for all "require" calls:
47
+
48
+ package.json:37:6:
49
+  37 │ "require": "./dist/cli.js",
50
+ ╵ ~~~~~~~~~
51
+
52
+
53
+
54
+
55
+ [1:03:47 PM]  WARN  ā–² [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
56
+
57
+ package.json:33:6:
58
+  33 │ "types": "./dist/index.d.ts"
59
+ ╵ ~~~~~~~
60
+
61
+ The "import" condition comes earlier and will be used for all "import" statements:
62
+
63
+ package.json:31:6:
64
+  31 │ "import": "./dist/index.mjs",
65
+ ╵ ~~~~~~~~
66
+
67
+ The "require" condition comes earlier and will be used for all "require" calls:
68
+
69
+ package.json:32:6:
70
+  32 │ "require": "./dist/index.js",
71
+ ╵ ~~~~~~~~~
72
+
73
+
74
+
75
+
76
+ [1:03:47 PM]  WARN  ā–² [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
77
+
78
+ package.json:38:6:
79
+  38 │ "types": "./dist/cli.d.ts"
80
+ ╵ ~~~~~~~
81
+
82
+ The "import" condition comes earlier and will be used for all "import" statements:
83
+
84
+ package.json:36:6:
85
+  36 │ "import": "./dist/cli.mjs",
86
+ ╵ ~~~~~~~~
87
+
88
+ The "require" condition comes earlier and will be used for all "require" calls:
89
+
90
+ package.json:37:6:
91
+  37 │ "require": "./dist/cli.js",
92
+ ╵ ~~~~~~~~~
93
+
94
+
95
+
96
+ ESM dist/index.mjs 110.00 B
97
+ ESM dist/cli.mjs 2.72 KB
98
+ ESM dist/chunk-VW57ZQRN.mjs 3.83 KB
99
+ ESM āš”ļø Build success in 66ms
100
+ CJS dist/index.js 5.13 KB
101
+ CJS dist/cli.js 7.94 KB
102
+ CJS āš”ļø Build success in 67ms
103
+ DTS Build start
104
+ DTS āš”ļø Build success in 7604ms
105
+ DTS dist/cli.d.ts 152.00 B
106
+ DTS dist/index.d.ts 1013.00 B
107
+ DTS dist/cli.d.mts 152.00 B
108
+ DTS dist/index.d.mts 1013.00 B
@@ -0,0 +1,16 @@
1
+
2
+ 
3
+ > @aiready/change-amplification@0.1.4 test /Users/pengcao/projects/aiready/packages/change-amplification
4
+ > vitest run
5
+
6
+
7
+  RUN  v1.6.1 /Users/pengcao/projects/aiready/packages/change-amplification
8
+
9
+ āœ“ src/__tests__/dummy.test.ts  (1 test) 9ms
10
+
11
+  Test Files  1 passed (1)
12
+  Tests  1 passed (1)
13
+  Start at  13:04:26
14
+  Duration  2.33s (transform 367ms, setup 0ms, collect 466ms, tests 9ms, environment 0ms, prepare 541ms)
15
+
16
+ [?25h
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @aiready/change-amplification
2
+
3
+ > AIReady Spoke: Analyzes architectural coupling and graph metrics to predict how code changes "explode" across the codebase.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@aiready/change-amplification.svg)](https://npmjs.com/package/@aiready/change-amplification)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Overview
9
+
10
+ High architectural coupling is one of the leading causes of AI agent failure. When an agent modifies a "bottleneck" file with dozens of dependents, the resulting cascade of breakages often exceeds the agent's context window or reasoning capacity.
11
+
12
+ The **Change Amplification** analyzer computes graph metrics (fan-in, fan-out, and centrality) to identify these high-risk areas before they cause an "edit explosion."
13
+
14
+ ## šŸ›ļø Architecture
15
+
16
+ ```
17
+ šŸŽÆ USER
18
+ │
19
+ ā–¼
20
+ šŸŽ›ļø @aiready/cli (orchestrator)
21
+ │ │ │ │ │ │ │ │ │
22
+ ā–¼ ā–¼ ā–¼ ā–¼ ā–¼ ā–¼ ā–¼ ā–¼ ā–¼
23
+ [PAT] [CTX] [CON] [AMP] [DEP] [DOC] [SIG] [AGT] [TST]
24
+ │ │ │ │ │ │ │ │ │
25
+ ā””ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”˜
26
+ │
27
+ ā–¼
28
+ šŸ¢ @aiready/core
29
+
30
+ Legend:
31
+ PAT = pattern-detect CTX = context-analyzer
32
+ CON = consistency AMP = change-amplification ā˜…
33
+ DEP = deps-health DOC = doc-drift
34
+ SIG = ai-signal-clarity AGT = agent-grounding
35
+ TST = testability ā˜… = YOU ARE HERE
36
+ ```
37
+
38
+ ## Features
39
+
40
+ - **Fan-Out Analysis**: Measures how many dependencies a file has (impact of external changes on this file).
41
+ - **Fan-In Analysis**: Measures how many files depend on this one (impact of changes in this file on the system).
42
+ - **Amplification Factor**: A weighted metric predicting the "blast radius" of a single line change.
43
+ - **Hotspot Detection**: Automatically flags files that should be refactored to reduce system-wide fragility.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pnpm add @aiready/change-amplification
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ This tool is designed to be run through the unified AIReady CLI.
54
+
55
+ ```bash
56
+ # Scan for change amplification hotspots
57
+ aiready scan . --tools change-amplification
58
+
59
+ # Get specific results for a directory
60
+ aiready change-amplification ./src
61
+ ```
62
+
63
+ ## License
64
+
65
+ MIT
@@ -0,0 +1,100 @@
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 { globSync } from "glob";
12
+ import { calculateChangeAmplification } from "@aiready/core";
13
+ import { parseDependencies } from "@aiready/core";
14
+ function collectFiles(dir, options) {
15
+ const includePatterns = options.include && options.include.length > 0 ? options.include : ["**/*.{ts,tsx,js,jsx,py,go}"];
16
+ const excludePatterns = options.exclude && options.exclude.length > 0 ? options.exclude : ["**/node_modules/**", "**/dist/**", "**/.git/**"];
17
+ let matchedFiles = [];
18
+ for (const pattern of includePatterns) {
19
+ const files = globSync(pattern, { cwd: dir, ignore: excludePatterns, absolute: true });
20
+ matchedFiles = matchedFiles.concat(files);
21
+ }
22
+ return [...new Set(matchedFiles)];
23
+ }
24
+ async function analyzeChangeAmplification(options) {
25
+ const rootDir = path.resolve(options.rootDir || ".");
26
+ const files = collectFiles(rootDir, options);
27
+ const dependencyGraph = /* @__PURE__ */ new Map();
28
+ const reverseGraph = /* @__PURE__ */ new Map();
29
+ for (const file of files) {
30
+ dependencyGraph.set(file, []);
31
+ reverseGraph.set(file, []);
32
+ }
33
+ for (const file of files) {
34
+ try {
35
+ const content = fs.readFileSync(file, "utf8");
36
+ const dependencies = parseDependencies(content);
37
+ for (const dep of dependencies) {
38
+ const depDir = path.dirname(file);
39
+ const resolvedPath = files.find((f) => {
40
+ if (dep.startsWith(".")) {
41
+ return f.startsWith(path.resolve(depDir, dep));
42
+ } else {
43
+ return f.includes(dep);
44
+ }
45
+ });
46
+ if (resolvedPath) {
47
+ dependencyGraph.get(file)?.push(resolvedPath);
48
+ reverseGraph.get(resolvedPath)?.push(file);
49
+ }
50
+ }
51
+ } catch (err) {
52
+ }
53
+ }
54
+ const fileMetrics = files.map((file) => {
55
+ const fanOut = dependencyGraph.get(file)?.length || 0;
56
+ const fanIn = reverseGraph.get(file)?.length || 0;
57
+ return { file, fanOut, fanIn };
58
+ });
59
+ const riskResult = calculateChangeAmplification({ files: fileMetrics });
60
+ const results = [];
61
+ for (const hotspot of riskResult.hotspots) {
62
+ const issues = [];
63
+ if (hotspot.amplificationFactor > 20) {
64
+ issues.push({
65
+ type: "change-amplification",
66
+ severity: hotspot.amplificationFactor > 40 ? "critical" : "major",
67
+ message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
68
+ location: { file: hotspot.file, line: 1 },
69
+ suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
70
+ });
71
+ }
72
+ if (hotspot.amplificationFactor > 5) {
73
+ results.push({
74
+ fileName: hotspot.file,
75
+ issues,
76
+ metrics: {
77
+ aiSignalClarityScore: 100 - hotspot.amplificationFactor
78
+ // Just a rough score
79
+ }
80
+ });
81
+ }
82
+ }
83
+ return {
84
+ summary: {
85
+ totalFiles: files.length,
86
+ totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
87
+ criticalIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "critical").length, 0),
88
+ majorIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "major").length, 0),
89
+ score: riskResult.score,
90
+ rating: riskResult.rating,
91
+ recommendations: riskResult.recommendations
92
+ },
93
+ results
94
+ };
95
+ }
96
+
97
+ export {
98
+ __require,
99
+ analyzeChangeAmplification
100
+ };
@@ -0,0 +1,103 @@
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 { globSync } from "glob";
12
+ import { calculateChangeAmplification } from "@aiready/core";
13
+ import { getParser } from "@aiready/core";
14
+ function collectFiles(dir, options) {
15
+ const includePatterns = options.include && options.include.length > 0 ? options.include : ["**/*.{ts,tsx,js,jsx,py,go}"];
16
+ const excludePatterns = options.exclude && options.exclude.length > 0 ? options.exclude : ["**/node_modules/**", "**/dist/**", "**/.git/**"];
17
+ let matchedFiles = [];
18
+ for (const pattern of includePatterns) {
19
+ const files = globSync(pattern, { cwd: dir, ignore: excludePatterns, absolute: true });
20
+ matchedFiles = matchedFiles.concat(files);
21
+ }
22
+ return [...new Set(matchedFiles)];
23
+ }
24
+ async function analyzeChangeAmplification(options) {
25
+ const rootDir = path.resolve(options.rootDir || ".");
26
+ const files = collectFiles(rootDir, options);
27
+ const dependencyGraph = /* @__PURE__ */ new Map();
28
+ const reverseGraph = /* @__PURE__ */ new Map();
29
+ for (const file of files) {
30
+ dependencyGraph.set(file, []);
31
+ reverseGraph.set(file, []);
32
+ }
33
+ for (const file of files) {
34
+ try {
35
+ const parser = getParser(file);
36
+ if (!parser) continue;
37
+ const content = fs.readFileSync(file, "utf8");
38
+ const parseResult = parser.parse(content, file);
39
+ const dependencies = parseResult.imports.map((i) => i.source);
40
+ for (const dep of dependencies) {
41
+ const depDir = path.dirname(file);
42
+ const resolvedPath = files.find((f) => {
43
+ if (dep.startsWith(".")) {
44
+ return f.startsWith(path.resolve(depDir, dep));
45
+ } else {
46
+ return f.includes(dep);
47
+ }
48
+ });
49
+ if (resolvedPath) {
50
+ dependencyGraph.get(file)?.push(resolvedPath);
51
+ reverseGraph.get(resolvedPath)?.push(file);
52
+ }
53
+ }
54
+ } catch (err) {
55
+ }
56
+ }
57
+ const fileMetrics = files.map((file) => {
58
+ const fanOut = dependencyGraph.get(file)?.length || 0;
59
+ const fanIn = reverseGraph.get(file)?.length || 0;
60
+ return { file, fanOut, fanIn };
61
+ });
62
+ const riskResult = calculateChangeAmplification({ files: fileMetrics });
63
+ const results = [];
64
+ for (const hotspot of riskResult.hotspots) {
65
+ const issues = [];
66
+ if (hotspot.amplificationFactor > 20) {
67
+ issues.push({
68
+ type: "change-amplification",
69
+ severity: hotspot.amplificationFactor > 40 ? "critical" : "major",
70
+ message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
71
+ location: { file: hotspot.file, line: 1 },
72
+ suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
73
+ });
74
+ }
75
+ if (hotspot.amplificationFactor > 5) {
76
+ results.push({
77
+ fileName: hotspot.file,
78
+ issues,
79
+ metrics: {
80
+ aiSignalClarityScore: 100 - hotspot.amplificationFactor
81
+ // Just a rough score
82
+ }
83
+ });
84
+ }
85
+ }
86
+ return {
87
+ summary: {
88
+ totalFiles: files.length,
89
+ totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
90
+ criticalIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "critical").length, 0),
91
+ majorIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "major").length, 0),
92
+ score: riskResult.score,
93
+ rating: riskResult.rating,
94
+ recommendations: riskResult.recommendations
95
+ },
96
+ results
97
+ };
98
+ }
99
+
100
+ export {
101
+ __require,
102
+ analyzeChangeAmplification
103
+ };
package/dist/cli.d.mts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ declare const changeAmplificationAction: (directory: string, options: any) => Promise<void>;
3
+
4
+ export { changeAmplificationAction };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ declare const changeAmplificationAction: (directory: string, options: any) => Promise<void>;
3
+
4
+ export { changeAmplificationAction };
package/dist/cli.js ADDED
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ changeAmplificationAction: () => changeAmplificationAction
35
+ });
36
+ module.exports = __toCommonJS(cli_exports);
37
+ var import_commander = require("commander");
38
+ var import_chalk = __toESM(require("chalk"));
39
+ var path2 = __toESM(require("path"));
40
+ var fs2 = __toESM(require("fs"));
41
+
42
+ // src/analyzer.ts
43
+ var fs = __toESM(require("fs"));
44
+ var path = __toESM(require("path"));
45
+ var import_glob = require("glob");
46
+ var import_core = require("@aiready/core");
47
+ var import_core2 = require("@aiready/core");
48
+ function collectFiles(dir, options) {
49
+ const includePatterns = options.include && options.include.length > 0 ? options.include : ["**/*.{ts,tsx,js,jsx,py,go}"];
50
+ const excludePatterns = options.exclude && options.exclude.length > 0 ? options.exclude : ["**/node_modules/**", "**/dist/**", "**/.git/**"];
51
+ let matchedFiles = [];
52
+ for (const pattern of includePatterns) {
53
+ const files = (0, import_glob.globSync)(pattern, { cwd: dir, ignore: excludePatterns, absolute: true });
54
+ matchedFiles = matchedFiles.concat(files);
55
+ }
56
+ return [...new Set(matchedFiles)];
57
+ }
58
+ async function analyzeChangeAmplification(options) {
59
+ const rootDir = path.resolve(options.rootDir || ".");
60
+ const files = collectFiles(rootDir, options);
61
+ const dependencyGraph = /* @__PURE__ */ new Map();
62
+ const reverseGraph = /* @__PURE__ */ new Map();
63
+ for (const file of files) {
64
+ dependencyGraph.set(file, []);
65
+ reverseGraph.set(file, []);
66
+ }
67
+ for (const file of files) {
68
+ try {
69
+ const parser = (0, import_core2.getParser)(file);
70
+ if (!parser) continue;
71
+ const content = fs.readFileSync(file, "utf8");
72
+ const parseResult = parser.parse(content, file);
73
+ const dependencies = parseResult.imports.map((i) => i.source);
74
+ for (const dep of dependencies) {
75
+ const depDir = path.dirname(file);
76
+ const resolvedPath = files.find((f) => {
77
+ if (dep.startsWith(".")) {
78
+ return f.startsWith(path.resolve(depDir, dep));
79
+ } else {
80
+ return f.includes(dep);
81
+ }
82
+ });
83
+ if (resolvedPath) {
84
+ dependencyGraph.get(file)?.push(resolvedPath);
85
+ reverseGraph.get(resolvedPath)?.push(file);
86
+ }
87
+ }
88
+ } catch (err) {
89
+ }
90
+ }
91
+ const fileMetrics = files.map((file) => {
92
+ const fanOut = dependencyGraph.get(file)?.length || 0;
93
+ const fanIn = reverseGraph.get(file)?.length || 0;
94
+ return { file, fanOut, fanIn };
95
+ });
96
+ const riskResult = (0, import_core.calculateChangeAmplification)({ files: fileMetrics });
97
+ const results = [];
98
+ for (const hotspot of riskResult.hotspots) {
99
+ const issues = [];
100
+ if (hotspot.amplificationFactor > 20) {
101
+ issues.push({
102
+ type: "change-amplification",
103
+ severity: hotspot.amplificationFactor > 40 ? "critical" : "major",
104
+ message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
105
+ location: { file: hotspot.file, line: 1 },
106
+ suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
107
+ });
108
+ }
109
+ if (hotspot.amplificationFactor > 5) {
110
+ results.push({
111
+ fileName: hotspot.file,
112
+ issues,
113
+ metrics: {
114
+ aiSignalClarityScore: 100 - hotspot.amplificationFactor
115
+ // Just a rough score
116
+ }
117
+ });
118
+ }
119
+ }
120
+ return {
121
+ summary: {
122
+ totalFiles: files.length,
123
+ totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
124
+ criticalIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "critical").length, 0),
125
+ majorIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "major").length, 0),
126
+ score: riskResult.score,
127
+ rating: riskResult.rating,
128
+ recommendations: riskResult.recommendations
129
+ },
130
+ results
131
+ };
132
+ }
133
+
134
+ // src/cli.ts
135
+ var changeAmplificationAction = async (directory, options) => {
136
+ try {
137
+ const resolvedDir = path2.resolve(process.cwd(), directory);
138
+ const finalOptions = {
139
+ rootDir: resolvedDir,
140
+ include: options.include ? options.include.split(",") : void 0,
141
+ exclude: options.exclude ? options.exclude.split(",") : void 0
142
+ };
143
+ const report = await analyzeChangeAmplification(finalOptions);
144
+ if (options.output === "json") {
145
+ const outputPath = options.outputFile || `change-amplification-report-${Date.now()}.json`;
146
+ fs2.writeFileSync(outputPath, JSON.stringify(report, null, 2));
147
+ return;
148
+ }
149
+ console.log(import_chalk.default.bold("\n\u{1F310} Change Amplification Analysis\n"));
150
+ console.log(`Rating: ${import_chalk.default.bold(report.summary.rating)}`);
151
+ console.log(`Score: ${Math.round(report.summary.score)}/100`);
152
+ console.log(`Critical Issues: ${report.summary.criticalIssues}`);
153
+ console.log(`Major Issues: ${report.summary.majorIssues}`);
154
+ if (report.summary.recommendations.length > 0) {
155
+ console.log(import_chalk.default.bold("\nRecommendations:"));
156
+ for (const rec of report.summary.recommendations) {
157
+ console.log(import_chalk.default.cyan(`\u2022 ${rec}`));
158
+ }
159
+ }
160
+ if (report.results.length > 0) {
161
+ console.log(import_chalk.default.bold("\nHotspots:"));
162
+ for (const result of report.results) {
163
+ console.log(`
164
+ \u{1F4C4} ${import_chalk.default.cyan(result.fileName)}`);
165
+ for (const issue of result.issues) {
166
+ const color = issue.severity === "critical" ? import_chalk.default.red : import_chalk.default.yellow;
167
+ console.log(` ${color("\u25A0")} ${issue.message}`);
168
+ console.log(` ${import_chalk.default.dim("Suggestion: " + issue.suggestion)}`);
169
+ }
170
+ }
171
+ } else {
172
+ console.log(import_chalk.default.green("\n\u2728 No change amplification issues found. Architecture is well contained."));
173
+ }
174
+ } catch (error) {
175
+ console.error(import_chalk.default.red("Error during change amplification analysis:"), error);
176
+ process.exit(1);
177
+ }
178
+ };
179
+ var program = new import_commander.Command();
180
+ program.name("aiready-change-amplification").description("Analyze graph metrics for change amplification").argument("[directory]", "Directory to analyze", ".").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(changeAmplificationAction);
181
+ if (require.main === module) {
182
+ program.parse();
183
+ }
184
+ // Annotate the CommonJS export names for ESM import in node:
185
+ 0 && (module.exports = {
186
+ changeAmplificationAction
187
+ });
package/dist/cli.mjs ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __require,
4
+ analyzeChangeAmplification
5
+ } from "./chunk-VW57ZQRN.mjs";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+ import chalk from "chalk";
10
+ import * as path from "path";
11
+ import * as fs from "fs";
12
+ var changeAmplificationAction = async (directory, options) => {
13
+ try {
14
+ const resolvedDir = path.resolve(process.cwd(), directory);
15
+ const finalOptions = {
16
+ rootDir: resolvedDir,
17
+ include: options.include ? options.include.split(",") : void 0,
18
+ exclude: options.exclude ? options.exclude.split(",") : void 0
19
+ };
20
+ const report = await analyzeChangeAmplification(finalOptions);
21
+ if (options.output === "json") {
22
+ const outputPath = options.outputFile || `change-amplification-report-${Date.now()}.json`;
23
+ fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
24
+ return;
25
+ }
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)}/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(chalk.green("\n\u2728 No change amplification issues found. Architecture is well contained."));
50
+ }
51
+ } catch (error) {
52
+ console.error(chalk.red("Error during change amplification analysis:"), error);
53
+ process.exit(1);
54
+ }
55
+ };
56
+ var program = new Command();
57
+ program.name("aiready-change-amplification").description("Analyze graph metrics for change amplification").argument("[directory]", "Directory to analyze", ".").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(changeAmplificationAction);
58
+ if (__require.main === module) {
59
+ program.parse();
60
+ }
61
+ export {
62
+ changeAmplificationAction
63
+ };
@@ -0,0 +1,30 @@
1
+ import { Issue, ScanOptions } from '@aiready/core';
2
+
3
+ interface ChangeAmplificationOptions extends ScanOptions {
4
+ }
5
+ interface ChangeAmplificationIssue extends Issue {
6
+ type: 'change-amplification';
7
+ }
8
+ interface FileChangeAmplificationResult {
9
+ fileName: string;
10
+ issues: ChangeAmplificationIssue[];
11
+ metrics: {
12
+ aiSignalClarityScore?: number;
13
+ };
14
+ }
15
+ interface ChangeAmplificationReport {
16
+ summary: {
17
+ totalFiles: number;
18
+ totalIssues: number;
19
+ criticalIssues: number;
20
+ majorIssues: number;
21
+ score: number;
22
+ rating: 'isolated' | 'contained' | 'amplified' | 'explosive';
23
+ recommendations: string[];
24
+ };
25
+ results: FileChangeAmplificationResult[];
26
+ }
27
+
28
+ declare function analyzeChangeAmplification(options: ChangeAmplificationOptions): Promise<ChangeAmplificationReport>;
29
+
30
+ export { type ChangeAmplificationIssue, type ChangeAmplificationOptions, type ChangeAmplificationReport, type FileChangeAmplificationResult, analyzeChangeAmplification };
@@ -0,0 +1,30 @@
1
+ import { Issue, ScanOptions } from '@aiready/core';
2
+
3
+ interface ChangeAmplificationOptions extends ScanOptions {
4
+ }
5
+ interface ChangeAmplificationIssue extends Issue {
6
+ type: 'change-amplification';
7
+ }
8
+ interface FileChangeAmplificationResult {
9
+ fileName: string;
10
+ issues: ChangeAmplificationIssue[];
11
+ metrics: {
12
+ aiSignalClarityScore?: number;
13
+ };
14
+ }
15
+ interface ChangeAmplificationReport {
16
+ summary: {
17
+ totalFiles: number;
18
+ totalIssues: number;
19
+ criticalIssues: number;
20
+ majorIssues: number;
21
+ score: number;
22
+ rating: 'isolated' | 'contained' | 'amplified' | 'explosive';
23
+ recommendations: string[];
24
+ };
25
+ results: FileChangeAmplificationResult[];
26
+ }
27
+
28
+ declare function analyzeChangeAmplification(options: ChangeAmplificationOptions): Promise<ChangeAmplificationReport>;
29
+
30
+ export { type ChangeAmplificationIssue, type ChangeAmplificationOptions, type ChangeAmplificationReport, type FileChangeAmplificationResult, analyzeChangeAmplification };
package/dist/index.js ADDED
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ analyzeChangeAmplification: () => analyzeChangeAmplification
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/analyzer.ts
38
+ var fs = __toESM(require("fs"));
39
+ var path = __toESM(require("path"));
40
+ var import_glob = require("glob");
41
+ var import_core = require("@aiready/core");
42
+ var import_core2 = require("@aiready/core");
43
+ function collectFiles(dir, options) {
44
+ const includePatterns = options.include && options.include.length > 0 ? options.include : ["**/*.{ts,tsx,js,jsx,py,go}"];
45
+ const excludePatterns = options.exclude && options.exclude.length > 0 ? options.exclude : ["**/node_modules/**", "**/dist/**", "**/.git/**"];
46
+ let matchedFiles = [];
47
+ for (const pattern of includePatterns) {
48
+ const files = (0, import_glob.globSync)(pattern, { cwd: dir, ignore: excludePatterns, absolute: true });
49
+ matchedFiles = matchedFiles.concat(files);
50
+ }
51
+ return [...new Set(matchedFiles)];
52
+ }
53
+ async function analyzeChangeAmplification(options) {
54
+ const rootDir = path.resolve(options.rootDir || ".");
55
+ const files = collectFiles(rootDir, options);
56
+ const dependencyGraph = /* @__PURE__ */ new Map();
57
+ const reverseGraph = /* @__PURE__ */ new Map();
58
+ for (const file of files) {
59
+ dependencyGraph.set(file, []);
60
+ reverseGraph.set(file, []);
61
+ }
62
+ for (const file of files) {
63
+ try {
64
+ const parser = (0, import_core2.getParser)(file);
65
+ if (!parser) continue;
66
+ const content = fs.readFileSync(file, "utf8");
67
+ const parseResult = parser.parse(content, file);
68
+ const dependencies = parseResult.imports.map((i) => i.source);
69
+ for (const dep of dependencies) {
70
+ const depDir = path.dirname(file);
71
+ const resolvedPath = files.find((f) => {
72
+ if (dep.startsWith(".")) {
73
+ return f.startsWith(path.resolve(depDir, dep));
74
+ } else {
75
+ return f.includes(dep);
76
+ }
77
+ });
78
+ if (resolvedPath) {
79
+ dependencyGraph.get(file)?.push(resolvedPath);
80
+ reverseGraph.get(resolvedPath)?.push(file);
81
+ }
82
+ }
83
+ } catch (err) {
84
+ }
85
+ }
86
+ const fileMetrics = files.map((file) => {
87
+ const fanOut = dependencyGraph.get(file)?.length || 0;
88
+ const fanIn = reverseGraph.get(file)?.length || 0;
89
+ return { file, fanOut, fanIn };
90
+ });
91
+ const riskResult = (0, import_core.calculateChangeAmplification)({ files: fileMetrics });
92
+ const results = [];
93
+ for (const hotspot of riskResult.hotspots) {
94
+ const issues = [];
95
+ if (hotspot.amplificationFactor > 20) {
96
+ issues.push({
97
+ type: "change-amplification",
98
+ severity: hotspot.amplificationFactor > 40 ? "critical" : "major",
99
+ message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
100
+ location: { file: hotspot.file, line: 1 },
101
+ suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
102
+ });
103
+ }
104
+ if (hotspot.amplificationFactor > 5) {
105
+ results.push({
106
+ fileName: hotspot.file,
107
+ issues,
108
+ metrics: {
109
+ aiSignalClarityScore: 100 - hotspot.amplificationFactor
110
+ // Just a rough score
111
+ }
112
+ });
113
+ }
114
+ }
115
+ return {
116
+ summary: {
117
+ totalFiles: files.length,
118
+ totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
119
+ criticalIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "critical").length, 0),
120
+ majorIssues: results.reduce((sum, r) => sum + r.issues.filter((i) => i.severity === "major").length, 0),
121
+ score: riskResult.score,
122
+ rating: riskResult.rating,
123
+ recommendations: riskResult.recommendations
124
+ },
125
+ results
126
+ };
127
+ }
128
+ // Annotate the CommonJS export names for ESM import in node:
129
+ 0 && (module.exports = {
130
+ analyzeChangeAmplification
131
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ analyzeChangeAmplification
3
+ } from "./chunk-VW57ZQRN.mjs";
4
+ export {
5
+ analyzeChangeAmplification
6
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@aiready/change-amplification",
3
+ "version": "0.1.4",
4
+ "description": "AI-Readiness: Change Amplification Detection",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "dependencies": {
9
+ "@typescript-eslint/typescript-estree": "^7.8.0",
10
+ "commander": "^12.0.0",
11
+ "glob": "^10.3.12",
12
+ "chalk": "^5.3.0",
13
+ "@aiready/core": "0.9.31"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^20.12.7",
17
+ "@typescript-eslint/types": "^7.8.0",
18
+ "tsup": "^8.0.2",
19
+ "typescript": "^5.4.5",
20
+ "vitest": "^1.6.0"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.mjs",
25
+ "require": "./dist/index.js",
26
+ "types": "./dist/index.d.ts"
27
+ },
28
+ "./cli": {
29
+ "import": "./dist/cli.mjs",
30
+ "require": "./dist/cli.js",
31
+ "types": "./dist/cli.d.ts"
32
+ },
33
+ "./dist/cli.js": {
34
+ "require": "./dist/cli.js",
35
+ "import": "./dist/cli.mjs"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
40
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --watch",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "lint": "eslint src --ext .ts"
44
+ }
45
+ }
@@ -0,0 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('Change Amplification Analyzer', () => {
4
+ it('should initialize successfully', () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -0,0 +1,116 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { globSync } from 'glob';
4
+ import { calculateChangeAmplification } from '@aiready/core';
5
+ import type { ChangeAmplificationOptions, ChangeAmplificationReport, FileChangeAmplificationResult, ChangeAmplificationIssue } from './types';
6
+ import { getParser } from '@aiready/core';
7
+
8
+ function collectFiles(dir: string, options: ChangeAmplificationOptions): string[] {
9
+ const includePatterns = options.include && options.include.length > 0 ? options.include : ['**/*.{ts,tsx,js,jsx,py,go}'];
10
+ const excludePatterns = options.exclude && options.exclude.length > 0 ? options.exclude : ['**/node_modules/**', '**/dist/**', '**/.git/**'];
11
+
12
+ let matchedFiles: string[] = [];
13
+ for (const pattern of includePatterns) {
14
+ const files = globSync(pattern, { cwd: dir, ignore: excludePatterns, absolute: true });
15
+ matchedFiles = matchedFiles.concat(files);
16
+ }
17
+ return [...new Set(matchedFiles)];
18
+ }
19
+
20
+ export async function analyzeChangeAmplification(
21
+ options: ChangeAmplificationOptions,
22
+ ): Promise<ChangeAmplificationReport> {
23
+ const rootDir = path.resolve(options.rootDir || '.');
24
+ const files = collectFiles(rootDir, options);
25
+
26
+ // Compute graph metrics: fanIn and fanOut
27
+ const dependencyGraph = new Map<string, string[]>(); // key: file, value: imported files
28
+ const reverseGraph = new Map<string, string[]>(); // key: file, value: files that import it
29
+
30
+ // Initialize graph
31
+ for (const file of files) {
32
+ dependencyGraph.set(file, []);
33
+ reverseGraph.set(file, []);
34
+ }
35
+
36
+ // Parse files to build dependency graph
37
+ for (const file of files) {
38
+ try {
39
+ const parser = getParser(file);
40
+ if (!parser) continue;
41
+
42
+ const content = fs.readFileSync(file, 'utf8');
43
+ const parseResult = parser.parse(content, file);
44
+ const dependencies = parseResult.imports.map(i => i.source);
45
+
46
+ for (const dep of dependencies) {
47
+ // Resolve simple relative or absolute imports for the graph
48
+ // This is a simplified resolution for demonstration purposes
49
+ const depDir = path.dirname(file);
50
+
51
+ // Find if this dependency resolves to one of our mapped files
52
+ const resolvedPath = files.find(f => {
53
+ if (dep.startsWith('.')) {
54
+ return f.startsWith(path.resolve(depDir, dep));
55
+ } else {
56
+ return f.includes(dep);
57
+ }
58
+ });
59
+
60
+ if (resolvedPath) {
61
+ dependencyGraph.get(file)?.push(resolvedPath);
62
+ reverseGraph.get(resolvedPath)?.push(file);
63
+ }
64
+ }
65
+ } catch (err) {
66
+ // Ignore parse errors silently
67
+ }
68
+ }
69
+
70
+ const fileMetrics = files.map(file => {
71
+ const fanOut = dependencyGraph.get(file)?.length || 0;
72
+ const fanIn = reverseGraph.get(file)?.length || 0;
73
+ return { file, fanOut, fanIn };
74
+ });
75
+
76
+ const riskResult = calculateChangeAmplification({ files: fileMetrics });
77
+
78
+ const results: FileChangeAmplificationResult[] = [];
79
+
80
+ for (const hotspot of riskResult.hotspots) {
81
+ const issues: ChangeAmplificationIssue[] = [];
82
+ if (hotspot.amplificationFactor > 20) {
83
+ issues.push({
84
+ type: 'change-amplification',
85
+ severity: hotspot.amplificationFactor > 40 ? 'critical' : 'major',
86
+ message: `High change amplification detected (Factor: ${hotspot.amplificationFactor}). Changes here cascade heavily.`,
87
+ location: { file: hotspot.file, line: 1 },
88
+ suggestion: `Reduce coupling. Fan-out is ${hotspot.fanOut}, Fan-in is ${hotspot.fanIn}.`
89
+ });
90
+ }
91
+
92
+ // We only push results for files that have either high fan-in or fan-out
93
+ if (hotspot.amplificationFactor > 5) {
94
+ results.push({
95
+ fileName: hotspot.file,
96
+ issues,
97
+ metrics: {
98
+ aiSignalClarityScore: 100 - hotspot.amplificationFactor, // Just a rough score
99
+ }
100
+ });
101
+ }
102
+ }
103
+
104
+ return {
105
+ summary: {
106
+ totalFiles: files.length,
107
+ totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
108
+ criticalIssues: results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'critical').length, 0),
109
+ majorIssues: results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'major').length, 0),
110
+ score: riskResult.score,
111
+ rating: riskResult.rating,
112
+ recommendations: riskResult.recommendations,
113
+ },
114
+ results,
115
+ };
116
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs';
6
+ import { analyzeChangeAmplification } from './analyzer';
7
+ import type { ChangeAmplificationOptions } from './types';
8
+
9
+ export const changeAmplificationAction = async (directory: string, options: any) => {
10
+ try {
11
+ const resolvedDir = path.resolve(process.cwd(), directory);
12
+ const finalOptions: ChangeAmplificationOptions = {
13
+ rootDir: resolvedDir,
14
+ include: options.include ? options.include.split(',') : undefined,
15
+ exclude: options.exclude ? options.exclude.split(',') : undefined,
16
+ };
17
+
18
+ const report = await analyzeChangeAmplification(finalOptions);
19
+
20
+ if (options.output === 'json') {
21
+ const outputPath = options.outputFile || `change-amplification-report-${Date.now()}.json`;
22
+ fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
23
+ return;
24
+ }
25
+
26
+ console.log(chalk.bold('\n🌐 Change Amplification Analysis\n'));
27
+ console.log(`Rating: ${chalk.bold(report.summary.rating)}`);
28
+ console.log(`Score: ${Math.round(report.summary.score)}/100`);
29
+ console.log(`Critical Issues: ${report.summary.criticalIssues}`);
30
+ console.log(`Major Issues: ${report.summary.majorIssues}`);
31
+
32
+ if (report.summary.recommendations.length > 0) {
33
+ console.log(chalk.bold('\nRecommendations:'));
34
+ for (const rec of report.summary.recommendations) {
35
+ console.log(chalk.cyan(`• ${rec}`));
36
+ }
37
+ }
38
+
39
+ if (report.results.length > 0) {
40
+ console.log(chalk.bold('\nHotspots:'));
41
+ for (const result of report.results) {
42
+ console.log(`\nšŸ“„ ${chalk.cyan(result.fileName)}`);
43
+ for (const issue of result.issues) {
44
+ const color = issue.severity === 'critical' ? chalk.red : chalk.yellow;
45
+ console.log(` ${color('ā– ')} ${issue.message}`);
46
+ console.log(` ${chalk.dim('Suggestion: ' + issue.suggestion)}`);
47
+ }
48
+ }
49
+ } else {
50
+ console.log(chalk.green('\n✨ No change amplification issues found. Architecture is well contained.'));
51
+ }
52
+ } catch (error) {
53
+ console.error(chalk.red('Error during change amplification analysis:'), error);
54
+ process.exit(1);
55
+ }
56
+ };
57
+
58
+ const program = new Command();
59
+ program
60
+ .name('aiready-change-amplification')
61
+ .description('Analyze graph metrics for change amplification')
62
+ .argument('[directory]', 'Directory to analyze', '.')
63
+ .option('--include <patterns>', 'File patterns to include (comma-separated)')
64
+ .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
65
+ .option('-o, --output <format>', 'Output format: console, json', 'console')
66
+ .option('--output-file <path>', 'Output file path (for json)')
67
+ .action(changeAmplificationAction);
68
+
69
+ if (require.main === module) {
70
+ program.parse();
71
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { analyzeChangeAmplification } from './analyzer';
2
+ export type {
3
+ ChangeAmplificationOptions,
4
+ ChangeAmplificationReport,
5
+ ChangeAmplificationIssue,
6
+ FileChangeAmplificationResult,
7
+ } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { ScanOptions, Issue } from '@aiready/core';
2
+
3
+ export interface ChangeAmplificationOptions extends ScanOptions {
4
+ // Add any specific configurations needed for change amplification here
5
+ }
6
+
7
+ export interface ChangeAmplificationIssue extends Issue {
8
+ type: 'change-amplification';
9
+ }
10
+
11
+ export interface FileChangeAmplificationResult {
12
+ fileName: string;
13
+ issues: ChangeAmplificationIssue[];
14
+ metrics: {
15
+ aiSignalClarityScore?: number;
16
+ };
17
+ }
18
+
19
+ export interface ChangeAmplificationReport {
20
+ summary: {
21
+ totalFiles: number;
22
+ totalIssues: number;
23
+ criticalIssues: number;
24
+ majorIssues: number;
25
+ score: number;
26
+ rating: 'isolated' | 'contained' | 'amplified' | 'explosive';
27
+ recommendations: string[];
28
+ };
29
+ results: FileChangeAmplificationResult[];
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }