@girardelli/architect 1.2.1 → 2.1.0

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.
Files changed (63) hide show
  1. package/README.md +111 -112
  2. package/dist/agent-generator.d.ts +95 -0
  3. package/dist/agent-generator.d.ts.map +1 -0
  4. package/dist/agent-generator.js +1295 -0
  5. package/dist/agent-generator.js.map +1 -0
  6. package/dist/cli.js +76 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/html-reporter.d.ts +26 -4
  9. package/dist/html-reporter.d.ts.map +1 -1
  10. package/dist/html-reporter.js +832 -33
  11. package/dist/html-reporter.js.map +1 -1
  12. package/dist/index.d.ts +26 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +85 -8
  15. package/dist/index.js.map +1 -1
  16. package/dist/refactor-engine.d.ts +18 -0
  17. package/dist/refactor-engine.d.ts.map +1 -0
  18. package/dist/refactor-engine.js +86 -0
  19. package/dist/refactor-engine.js.map +1 -0
  20. package/dist/refactor-reporter.d.ts +20 -0
  21. package/dist/refactor-reporter.d.ts.map +1 -0
  22. package/dist/refactor-reporter.js +389 -0
  23. package/dist/refactor-reporter.js.map +1 -0
  24. package/dist/rules/barrel-optimizer.d.ts +13 -0
  25. package/dist/rules/barrel-optimizer.d.ts.map +1 -0
  26. package/dist/rules/barrel-optimizer.js +77 -0
  27. package/dist/rules/barrel-optimizer.js.map +1 -0
  28. package/dist/rules/dead-code-detector.d.ts +21 -0
  29. package/dist/rules/dead-code-detector.d.ts.map +1 -0
  30. package/dist/rules/dead-code-detector.js +117 -0
  31. package/dist/rules/dead-code-detector.js.map +1 -0
  32. package/dist/rules/hub-splitter.d.ts +13 -0
  33. package/dist/rules/hub-splitter.d.ts.map +1 -0
  34. package/dist/rules/hub-splitter.js +110 -0
  35. package/dist/rules/hub-splitter.js.map +1 -0
  36. package/dist/rules/import-organizer.d.ts +13 -0
  37. package/dist/rules/import-organizer.d.ts.map +1 -0
  38. package/dist/rules/import-organizer.js +85 -0
  39. package/dist/rules/import-organizer.js.map +1 -0
  40. package/dist/rules/module-grouper.d.ts +13 -0
  41. package/dist/rules/module-grouper.d.ts.map +1 -0
  42. package/dist/rules/module-grouper.js +110 -0
  43. package/dist/rules/module-grouper.js.map +1 -0
  44. package/dist/scorer.d.ts +12 -0
  45. package/dist/scorer.d.ts.map +1 -1
  46. package/dist/scorer.js +61 -17
  47. package/dist/scorer.js.map +1 -1
  48. package/dist/types.d.ts +51 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +1 -1
  51. package/src/agent-generator.ts +1401 -0
  52. package/src/cli.ts +83 -2
  53. package/src/html-reporter.ts +872 -35
  54. package/src/index.ts +108 -9
  55. package/src/refactor-engine.ts +117 -0
  56. package/src/refactor-reporter.ts +408 -0
  57. package/src/rules/barrel-optimizer.ts +97 -0
  58. package/src/rules/dead-code-detector.ts +132 -0
  59. package/src/rules/hub-splitter.ts +123 -0
  60. package/src/rules/import-organizer.ts +98 -0
  61. package/src/rules/module-grouper.ts +124 -0
  62. package/src/scorer.ts +63 -17
  63. package/src/types.ts +52 -0
@@ -0,0 +1,123 @@
1
+ import { basename, dirname, join } from 'path';
2
+ import { AnalysisReport, RefactorRule, RefactorStep, FileOperation } from '../types.js';
3
+
4
+ /**
5
+ * Hub Splitter Rule (Tier 1)
6
+ * Detects files with many connections and generates split plans.
7
+ * A "hub" is a file that many other files depend on, creating tight coupling.
8
+ */
9
+ export class HubSplitterRule implements RefactorRule {
10
+ name = 'hub-splitter';
11
+ tier = 1 as const;
12
+
13
+ analyze(report: AnalysisReport, projectPath: string): RefactorStep[] {
14
+ const steps: RefactorStep[] = [];
15
+
16
+ // Count connections per node
17
+ const connectionCount: Record<string, { incoming: string[]; outgoing: string[] }> = {};
18
+
19
+ for (const edge of report.dependencyGraph.edges) {
20
+ if (!connectionCount[edge.from]) connectionCount[edge.from] = { incoming: [], outgoing: [] };
21
+ if (!connectionCount[edge.to]) connectionCount[edge.to] = { incoming: [], outgoing: [] };
22
+ connectionCount[edge.from].outgoing.push(edge.to);
23
+ connectionCount[edge.to].incoming.push(edge.from);
24
+ }
25
+
26
+ // Find hubs (5+ incoming connections, not barrel files)
27
+ const barrelFiles = new Set(['__init__.py', 'index.ts', 'index.js', 'index.tsx', 'mod.rs']);
28
+
29
+ for (const [file, connections] of Object.entries(connectionCount)) {
30
+ const fileName = basename(file);
31
+ if (barrelFiles.has(fileName)) continue;
32
+ if (connections.incoming.length < 5) continue;
33
+
34
+ const operations: FileOperation[] = [];
35
+
36
+ // Determine if this is a dot-notation module or a real file
37
+ const isDotNotation = !file.includes('/') && !file.includes('\\');
38
+ const moduleName = isDotNotation
39
+ ? file.split('.').pop() || file
40
+ : fileName.replace(/\.[^.]+$/, '');
41
+ const moduleDir = isDotNotation
42
+ ? file.split('.').slice(0, -1).join('/')
43
+ : dirname(file);
44
+ const ext = isDotNotation ? 'py' : (fileName.split('.').pop() || 'py');
45
+
46
+ // Analyze what dependents import to suggest groupings
47
+ const dependentGroups = this.groupDependents(connections.incoming);
48
+
49
+ // Suggest splitting into domain modules
50
+ if (dependentGroups.length >= 2) {
51
+ for (const group of dependentGroups) {
52
+ const newFileName = `${moduleName}_${group.name}.${ext}`;
53
+ const newPath = moduleDir ? `${moduleDir}/${newFileName}` : newFileName;
54
+
55
+ operations.push({
56
+ type: 'CREATE',
57
+ path: newPath,
58
+ description: `Create \`${newFileName}\` with functionality used by: ${group.dependents.join(', ')}`,
59
+ content: ext === 'py'
60
+ ? `"""${moduleName}_${group.name} — extracted from ${moduleName}."""\n# Used by: ${group.dependents.join(', ')}\n`
61
+ : `// ${moduleName}_${group.name} — extracted from ${moduleName}\n// Used by: ${group.dependents.join(', ')}\n`,
62
+ });
63
+ }
64
+
65
+ // Update imports in all dependents
66
+ for (const dependent of connections.incoming) {
67
+ operations.push({
68
+ type: 'MODIFY',
69
+ path: dependent,
70
+ description: `Update imports in \`${basename(dependent)}\` to use new split modules`,
71
+ });
72
+ }
73
+
74
+ // Mark original for refactoring
75
+ operations.push({
76
+ type: 'MODIFY',
77
+ path: isDotNotation ? `${moduleDir}/${moduleName}.${ext}` : file,
78
+ description: `Refactor \`${moduleName}.${ext}\` — extract grouped functionality to new modules`,
79
+ });
80
+ }
81
+
82
+ if (operations.length > 0) {
83
+ steps.push({
84
+ id: 0,
85
+ tier: 1,
86
+ rule: this.name,
87
+ priority: connections.incoming.length >= 8 ? 'CRITICAL' : 'HIGH',
88
+ title: `Split hub file: ${moduleName}.${ext}`,
89
+ description: `\`${file}\` has ${connections.incoming.length} incoming connections. ` +
90
+ `Split into ${dependentGroups.length} focused modules to reduce coupling.`,
91
+ rationale: `High fan-in (${connections.incoming.length} files depend on this) creates a bottleneck. ` +
92
+ `Changes to this file ripple to ${connections.incoming.length} other files. ` +
93
+ `Splitting by usage pattern reduces blast radius.`,
94
+ operations,
95
+ scoreImpact: [
96
+ { metric: 'coupling', before: report.score.breakdown.coupling, after: Math.min(95, report.score.breakdown.coupling + 15) },
97
+ ],
98
+ });
99
+ }
100
+ }
101
+
102
+ return steps;
103
+ }
104
+
105
+ private groupDependents(
106
+ dependents: string[]
107
+ ): Array<{ name: string; dependents: string[] }> {
108
+ // Group by top-level directory
109
+ const groups: Record<string, string[]> = {};
110
+
111
+ for (const dep of dependents) {
112
+ const parts = dep.includes('/') ? dep.split('/') : dep.split('.');
113
+ const groupName = parts.length >= 2 ? parts[parts.length - 2] : 'core';
114
+ if (!groups[groupName]) groups[groupName] = [];
115
+ groups[groupName].push(basename(dep));
116
+ }
117
+
118
+ return Object.entries(groups).map(([name, deps]) => ({
119
+ name,
120
+ dependents: deps,
121
+ }));
122
+ }
123
+ }
@@ -0,0 +1,98 @@
1
+ import { basename, dirname } from 'path';
2
+ import { AnalysisReport, RefactorRule, RefactorStep, FileOperation } from '../types.js';
3
+
4
+ /**
5
+ * Import Organizer Rule (Tier 1)
6
+ * Detects files that import from too many different modules (cross-boundary).
7
+ * Suggests dependency injection or facade patterns.
8
+ */
9
+ export class ImportOrganizerRule implements RefactorRule {
10
+ name = 'import-organizer';
11
+ tier = 1 as const;
12
+
13
+ analyze(report: AnalysisReport, projectPath: string): RefactorStep[] {
14
+ const steps: RefactorStep[] = [];
15
+
16
+ // Find files that import from many different directories
17
+ const crossBoundary: Record<string, { targets: Set<string>; dirs: Set<string> }> = {};
18
+
19
+ for (const edge of report.dependencyGraph.edges) {
20
+ const fromDir = dirname(edge.from);
21
+ const toDir = dirname(edge.to);
22
+
23
+ if (!crossBoundary[edge.from]) {
24
+ crossBoundary[edge.from] = { targets: new Set(), dirs: new Set() };
25
+ }
26
+ crossBoundary[edge.from].targets.add(edge.to);
27
+
28
+ if (fromDir !== toDir) {
29
+ crossBoundary[edge.from].dirs.add(toDir);
30
+ }
31
+ }
32
+
33
+ // Files importing from 3+ different directories
34
+ const violators = Object.entries(crossBoundary)
35
+ .filter(([_, data]) => data.dirs.size >= 3)
36
+ .sort((a, b) => b[1].dirs.size - a[1].dirs.size);
37
+
38
+ for (const [file, data] of violators) {
39
+ const operations: FileOperation[] = [];
40
+ const fileName = basename(file);
41
+ const fileDir = dirname(file);
42
+
43
+ // Suggest creating a facade/service layer
44
+ const ext = fileName.split('.').pop() || 'py';
45
+ const nameBase = fileName.replace(/\.[^.]+$/, '');
46
+ const facadePath = `${fileDir}/${nameBase}_deps.${ext}`;
47
+
48
+ operations.push({
49
+ type: 'CREATE',
50
+ path: facadePath,
51
+ description: `Create dependency facade \`${basename(facadePath)}\` — centralizes ${data.dirs.size} cross-module imports`,
52
+ content: this.generateFacadeContent(ext, Array.from(data.targets), Array.from(data.dirs)),
53
+ });
54
+
55
+ operations.push({
56
+ type: 'MODIFY',
57
+ path: file,
58
+ description: `Refactor \`${fileName}\` to import from local facade instead of ${data.dirs.size} different modules`,
59
+ });
60
+
61
+ steps.push({
62
+ id: 0,
63
+ tier: 1,
64
+ rule: this.name,
65
+ priority: data.dirs.size >= 5 ? 'HIGH' : 'MEDIUM',
66
+ title: `Reduce cross-boundary imports: ${fileName}`,
67
+ description: `\`${file}\` imports from ${data.dirs.size} different modules: ` +
68
+ `${Array.from(data.dirs).map((d) => `\`${d}\``).join(', ')}. ` +
69
+ `Consider using a facade or dependency injection.`,
70
+ rationale: `Files with imports scattered across many modules have high afferent coupling. ` +
71
+ `A facade centralizes these dependencies, making the file easier to test (mock one facade) ` +
72
+ `and reducing the impact of changes in dependent modules.`,
73
+ operations,
74
+ scoreImpact: [
75
+ { metric: 'cohesion', before: report.score.breakdown.cohesion, after: Math.min(95, report.score.breakdown.cohesion + 5) },
76
+ { metric: 'coupling', before: report.score.breakdown.coupling, after: Math.min(95, report.score.breakdown.coupling + 5) },
77
+ ],
78
+ });
79
+ }
80
+
81
+ return steps;
82
+ }
83
+
84
+ private generateFacadeContent(ext: string, targets: string[], dirs: string[]): string {
85
+ if (ext === 'py') {
86
+ const imports = targets
87
+ .map((t) => `# from ${t.replace(/\//g, '.')} import ...`)
88
+ .join('\n');
89
+ return `"""Dependency facade — centralizes cross-module imports."""\n\n${imports}\n\n# Re-export what ${dirs.length} modules need\n`;
90
+ }
91
+
92
+ // JS/TS
93
+ const imports = targets
94
+ .map((t) => `// export { ... } from '${t}';`)
95
+ .join('\n');
96
+ return `/**\n * Dependency facade — centralizes cross-module imports.\n */\n\n${imports}\n`;
97
+ }
98
+ }
@@ -0,0 +1,124 @@
1
+ import { basename, dirname, join } from 'path';
2
+ import { AnalysisReport, RefactorRule, RefactorStep, FileOperation } from '../types.js';
3
+
4
+ /**
5
+ * Module Grouper Rule (Tier 1)
6
+ * Analyzes which files are frequently imported together and suggests
7
+ * grouping them into cohesive modules/packages.
8
+ */
9
+ export class ModuleGrouperRule implements RefactorRule {
10
+ name = 'module-grouper';
11
+ tier = 1 as const;
12
+
13
+ analyze(report: AnalysisReport, projectPath: string): RefactorStep[] {
14
+ const steps: RefactorStep[] = [];
15
+
16
+ // Build co-import matrix: which files are imported together?
17
+ const coImportCount: Record<string, Record<string, number>> = {};
18
+
19
+ // For each source file, see what it imports
20
+ const importsBySource: Record<string, string[]> = {};
21
+ for (const edge of report.dependencyGraph.edges) {
22
+ if (!importsBySource[edge.from]) importsBySource[edge.from] = [];
23
+ importsBySource[edge.from].push(edge.to);
24
+ }
25
+
26
+ // Count co-imports
27
+ for (const [source, targets] of Object.entries(importsBySource)) {
28
+ for (let i = 0; i < targets.length; i++) {
29
+ for (let j = i + 1; j < targets.length; j++) {
30
+ const a = targets[i];
31
+ const b = targets[j];
32
+ if (!coImportCount[a]) coImportCount[a] = {};
33
+ if (!coImportCount[b]) coImportCount[b] = {};
34
+ coImportCount[a][b] = (coImportCount[a][b] || 0) + 1;
35
+ coImportCount[b][a] = (coImportCount[b][a] || 0) + 1;
36
+ }
37
+ }
38
+ }
39
+
40
+ // Find clusters: files that are always imported together
41
+ const clusters: Array<{ files: string[]; coImportScore: number }> = [];
42
+ const visited = new Set<string>();
43
+
44
+ for (const [fileA, partners] of Object.entries(coImportCount)) {
45
+ if (visited.has(fileA)) continue;
46
+
47
+ const strongPartners = Object.entries(partners)
48
+ .filter(([_, count]) => count >= 2)
49
+ .sort((a, b) => b[1] - a[1]);
50
+
51
+ if (strongPartners.length >= 2) {
52
+ const cluster = [fileA, ...strongPartners.map(([f]) => f)];
53
+ const inSameDir = cluster.every(
54
+ (f) => dirname(f) === dirname(cluster[0])
55
+ );
56
+
57
+ // Only suggest if NOT already in the same directory
58
+ if (!inSameDir) {
59
+ const score = strongPartners.reduce((sum, [_, c]) => sum + c, 0);
60
+ clusters.push({ files: cluster, coImportScore: score });
61
+ cluster.forEach((f) => visited.add(f));
62
+ }
63
+ }
64
+ }
65
+
66
+ // Generate steps for each cluster
67
+ for (const cluster of clusters.slice(0, 3)) {
68
+ const operations: FileOperation[] = [];
69
+ const clusterName = this.suggestModuleName(cluster.files);
70
+ const targetDir = `${dirname(cluster.files[0])}/${clusterName}`;
71
+
72
+ // Create new module directory
73
+ operations.push({
74
+ type: 'CREATE',
75
+ path: `${targetDir}/__init__.py`,
76
+ description: `Create new module \`${clusterName}/\` to group ${cluster.files.length} co-dependent files`,
77
+ content: `"""Module ${clusterName} — grouped by co-import pattern."""\n`,
78
+ });
79
+
80
+ // Move files
81
+ for (const file of cluster.files) {
82
+ const newPath = join(targetDir, basename(file));
83
+ operations.push({
84
+ type: 'MOVE',
85
+ path: file,
86
+ newPath,
87
+ description: `Move \`${basename(file)}\` → \`${clusterName}/${basename(file)}\``,
88
+ });
89
+ }
90
+
91
+ steps.push({
92
+ id: 0,
93
+ tier: 1,
94
+ rule: this.name,
95
+ priority: 'MEDIUM',
96
+ title: `Group co-dependent files into \`${clusterName}/\``,
97
+ description: `Files ${cluster.files.map((f) => `\`${basename(f)}\``).join(', ')} ` +
98
+ `are frequently imported together (co-import score: ${cluster.coImportScore}). ` +
99
+ `Grouping them improves cohesion.`,
100
+ rationale: `Files that are frequently imported together belong in the same module. ` +
101
+ `This improves discoverability and reduces the cognitive load of understanding ` +
102
+ `which files work together.`,
103
+ operations,
104
+ scoreImpact: [
105
+ { metric: 'cohesion', before: report.score.breakdown.cohesion, after: Math.min(95, report.score.breakdown.cohesion + 10) },
106
+ { metric: 'modularity', before: report.score.breakdown.modularity, after: Math.min(95, report.score.breakdown.modularity + 5) },
107
+ ],
108
+ });
109
+ }
110
+
111
+ return steps;
112
+ }
113
+
114
+ private suggestModuleName(files: string[]): string {
115
+ // Try to infer a common theme from filenames
116
+ const names = files.map((f) => basename(f).replace(/\.[^.]+$/, '').toLowerCase());
117
+ const commonParts = names[0].split(/[_-]/).filter((part) =>
118
+ names.every((n) => n.includes(part))
119
+ );
120
+
121
+ if (commonParts.length > 0) return commonParts[0];
122
+ return 'shared';
123
+ }
124
+ }
package/src/scorer.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ArchitectureScore, DependencyEdge, AntiPattern, ScoreComponent } from './types.js';
2
+ import { basename } from 'path';
2
3
 
3
4
  export class ArchitectureScorer {
4
5
  private modularity: number = 0;
@@ -6,6 +7,15 @@ export class ArchitectureScorer {
6
7
  private cohesion: number = 0;
7
8
  private layering: number = 0;
8
9
 
10
+ /**
11
+ * Barrel/index files that naturally have many connections and should be
12
+ * excluded from coupling max-edge penalty calculations.
13
+ */
14
+ private static readonly BARREL_FILES = new Set([
15
+ '__init__.py', 'index.ts', 'index.js', 'index.tsx', 'index.jsx',
16
+ 'mod.rs', '__init__.pyi',
17
+ ]);
18
+
9
19
  score(
10
20
  edges: DependencyEdge[],
11
21
  antiPatterns: AntiPattern[],
@@ -91,26 +101,42 @@ export class ArchitectureScorer {
91
101
  }
92
102
 
93
103
  private calculateCoupling(edges: DependencyEdge[], totalFiles: number): void {
94
- if (totalFiles === 0) {
104
+ if (totalFiles === 0 || totalFiles === 1) {
95
105
  this.coupling = 50;
96
106
  return;
97
107
  }
98
108
 
99
- const nodeWithMaxEdges = this.findNodeWithMaxEdges(edges);
109
+ // Exclude barrel/index files from max-edge calculation —
110
+ // they naturally have many connections by design.
111
+ const nonBarrelEdges = edges.filter((e) => {
112
+ const fromFile = basename(e.from);
113
+ const toFile = basename(e.to);
114
+ return !ArchitectureScorer.BARREL_FILES.has(fromFile) &&
115
+ !ArchitectureScorer.BARREL_FILES.has(toFile);
116
+ });
117
+
118
+ const nodeWithMaxEdges = this.findNodeWithMaxEdges(nonBarrelEdges);
100
119
  const maxEdgeCount = nodeWithMaxEdges ? nodeWithMaxEdges.count : 0;
101
120
 
102
- const couplingRatio = maxEdgeCount / (totalFiles - 1);
121
+ // Use non-barrel file count for ratio calculation
122
+ const effectiveFiles = Math.max(totalFiles - 1, 1);
123
+ const couplingRatio = maxEdgeCount / effectiveFiles;
103
124
 
104
- if (couplingRatio < 0.2) {
125
+ // More granular thresholds
126
+ if (couplingRatio < 0.15) {
105
127
  this.coupling = 95;
106
- } else if (couplingRatio < 0.4) {
128
+ } else if (couplingRatio < 0.25) {
107
129
  this.coupling = 85;
108
- } else if (couplingRatio < 0.6) {
109
- this.coupling = 70;
110
- } else if (couplingRatio < 0.8) {
130
+ } else if (couplingRatio < 0.35) {
131
+ this.coupling = 75;
132
+ } else if (couplingRatio < 0.5) {
133
+ this.coupling = 65;
134
+ } else if (couplingRatio < 0.7) {
111
135
  this.coupling = 50;
136
+ } else if (couplingRatio < 0.85) {
137
+ this.coupling = 35;
112
138
  } else {
113
- this.coupling = 30;
139
+ this.coupling = 20;
114
140
  }
115
141
  }
116
142
 
@@ -149,23 +175,42 @@ export class ArchitectureScorer {
149
175
 
150
176
  const cohesionRatio = internalEdges / edges.length;
151
177
 
152
- if (cohesionRatio > 0.7) {
178
+ // More granular thresholds
179
+ if (cohesionRatio > 0.8) {
153
180
  this.cohesion = 95;
154
- } else if (cohesionRatio > 0.5) {
155
- this.cohesion = 80;
181
+ } else if (cohesionRatio > 0.6) {
182
+ this.cohesion = 85;
183
+ } else if (cohesionRatio > 0.45) {
184
+ this.cohesion = 75;
156
185
  } else if (cohesionRatio > 0.3) {
157
186
  this.cohesion = 65;
158
- } else if (cohesionRatio > 0.1) {
159
- this.cohesion = 45;
187
+ } else if (cohesionRatio > 0.15) {
188
+ this.cohesion = 50;
160
189
  } else {
161
190
  this.cohesion = 30;
162
191
  }
163
192
  }
164
193
 
194
+ /**
195
+ * Determines if a dependency is "internal" (cohesive).
196
+ * Two files are considered cohesive if they share the same top-level
197
+ * package/directory (e.g., deepguard/cli.py → deepguard/analyzer.py).
198
+ * This is crucial for Python flat packages where all files live in
199
+ * one directory but ARE cohesive.
200
+ */
165
201
  private isInternalDependency(from: string, to: string): boolean {
166
- const fromModule = from.split('/').slice(0, -1).join('/');
167
- const toModule = to.split('/').slice(0, -1).join('/');
168
- return fromModule === toModule;
202
+ const fromParts = from.split('/');
203
+ const toParts = to.split('/');
204
+
205
+ // If both are in root (no directory), they're cohesive
206
+ if (fromParts.length <= 1 && toParts.length <= 1) return true;
207
+
208
+ // Compare top-level directory (package name)
209
+ // e.g., "deepguard/cli.py" and "deepguard/analyzer.py" → same package
210
+ const fromTopLevel = fromParts.length > 1 ? fromParts[0] : '';
211
+ const toTopLevel = toParts.length > 1 ? toParts[0] : '';
212
+
213
+ return fromTopLevel === toTopLevel;
169
214
  }
170
215
 
171
216
  private calculateLayering(antiPatterns: AntiPattern[]): void {
@@ -191,3 +236,4 @@ export class ArchitectureScorer {
191
236
  }
192
237
  }
193
238
  }
239
+
package/src/types.ts CHANGED
@@ -112,3 +112,55 @@ export interface ParsedImport {
112
112
  isDefault: boolean;
113
113
  isNamespace: boolean;
114
114
  }
115
+
116
+ // ── v2.0 Refactoring Types ──
117
+
118
+ export interface RefactoringPlan {
119
+ timestamp: string;
120
+ projectPath: string;
121
+ currentScore: ArchitectureScore;
122
+ estimatedScoreAfter: { overall: number; breakdown: Record<string, number> };
123
+ steps: RefactorStep[];
124
+ totalOperations: number;
125
+ tier1Steps: number;
126
+ tier2Steps: number;
127
+ }
128
+
129
+ export interface RefactorStep {
130
+ id: number;
131
+ tier: 1 | 2;
132
+ rule: string;
133
+ priority: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
134
+ title: string;
135
+ description: string;
136
+ rationale: string;
137
+ operations: FileOperation[];
138
+ scoreImpact: { metric: string; before: number; after: number }[];
139
+ codePreview?: string;
140
+ }
141
+
142
+ export interface FileOperation {
143
+ type: 'CREATE' | 'MOVE' | 'MODIFY' | 'DELETE';
144
+ path: string;
145
+ newPath?: string;
146
+ content?: string;
147
+ diff?: string;
148
+ description: string;
149
+ }
150
+
151
+ export interface CodeSymbol {
152
+ name: string;
153
+ type: 'function' | 'class' | 'variable' | 'import' | 'export';
154
+ startLine: number;
155
+ endLine: number;
156
+ lines: number;
157
+ dependencies: string[];
158
+ usedBy: string[];
159
+ }
160
+
161
+ export interface RefactorRule {
162
+ name: string;
163
+ tier: 1 | 2;
164
+ analyze(report: AnalysisReport, projectPath: string): RefactorStep[];
165
+ }
166
+