@girardelli/architect-core 8.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 (217) hide show
  1. package/dist/src/core/analyzer.d.ts +42 -0
  2. package/dist/src/core/analyzer.js +431 -0
  3. package/dist/src/core/analyzer.js.map +1 -0
  4. package/dist/src/core/analyzers/forecast.d.ts +84 -0
  5. package/dist/src/core/analyzers/forecast.js +338 -0
  6. package/dist/src/core/analyzers/forecast.js.map +1 -0
  7. package/dist/src/core/analyzers/index.d.ts +9 -0
  8. package/dist/src/core/analyzers/index.js +7 -0
  9. package/dist/src/core/analyzers/index.js.map +1 -0
  10. package/dist/src/core/analyzers/temporal-scorer.d.ts +71 -0
  11. package/dist/src/core/analyzers/temporal-scorer.js +141 -0
  12. package/dist/src/core/analyzers/temporal-scorer.js.map +1 -0
  13. package/dist/src/core/anti-patterns.d.ts +28 -0
  14. package/dist/src/core/anti-patterns.js +264 -0
  15. package/dist/src/core/anti-patterns.js.map +1 -0
  16. package/dist/src/core/ast/ast-parser.interface.d.ts +20 -0
  17. package/dist/src/core/ast/ast-parser.interface.js +2 -0
  18. package/dist/src/core/ast/ast-parser.interface.js.map +1 -0
  19. package/dist/src/core/ast/path-resolver.d.ts +13 -0
  20. package/dist/src/core/ast/path-resolver.js +54 -0
  21. package/dist/src/core/ast/path-resolver.js.map +1 -0
  22. package/dist/src/core/ast/tree-sitter-parser.d.ts +10 -0
  23. package/dist/src/core/ast/tree-sitter-parser.js +142 -0
  24. package/dist/src/core/ast/tree-sitter-parser.js.map +1 -0
  25. package/dist/src/core/config.d.ts +11 -0
  26. package/dist/src/core/config.js +112 -0
  27. package/dist/src/core/config.js.map +1 -0
  28. package/dist/src/core/diagram.d.ts +9 -0
  29. package/dist/src/core/diagram.js +101 -0
  30. package/dist/src/core/diagram.js.map +1 -0
  31. package/dist/src/core/i18n.d.ts +14 -0
  32. package/dist/src/core/i18n.js +54 -0
  33. package/dist/src/core/i18n.js.map +1 -0
  34. package/dist/src/core/locales/en.d.ts +2 -0
  35. package/dist/src/core/locales/en.js +337 -0
  36. package/dist/src/core/locales/en.js.map +1 -0
  37. package/dist/src/core/locales/pt-BR.d.ts +172 -0
  38. package/dist/src/core/locales/pt-BR.js +337 -0
  39. package/dist/src/core/locales/pt-BR.js.map +1 -0
  40. package/dist/src/core/locales/types.d.ts +86 -0
  41. package/dist/src/core/locales/types.js +2 -0
  42. package/dist/src/core/locales/types.js.map +1 -0
  43. package/dist/src/core/plugin-loader.d.ts +11 -0
  44. package/dist/src/core/plugin-loader.js +67 -0
  45. package/dist/src/core/plugin-loader.js.map +1 -0
  46. package/dist/src/core/project-summarizer.d.ts +16 -0
  47. package/dist/src/core/project-summarizer.js +37 -0
  48. package/dist/src/core/project-summarizer.js.map +1 -0
  49. package/dist/src/core/refactor-engine.d.ts +18 -0
  50. package/dist/src/core/refactor-engine.js +87 -0
  51. package/dist/src/core/refactor-engine.js.map +1 -0
  52. package/dist/src/core/rules/barrel-optimizer.d.ts +13 -0
  53. package/dist/src/core/rules/barrel-optimizer.js +76 -0
  54. package/dist/src/core/rules/barrel-optimizer.js.map +1 -0
  55. package/dist/src/core/rules/dead-code-detector.d.ts +21 -0
  56. package/dist/src/core/rules/dead-code-detector.js +116 -0
  57. package/dist/src/core/rules/dead-code-detector.js.map +1 -0
  58. package/dist/src/core/rules/hub-splitter.d.ts +13 -0
  59. package/dist/src/core/rules/hub-splitter.js +117 -0
  60. package/dist/src/core/rules/hub-splitter.js.map +1 -0
  61. package/dist/src/core/rules/import-organizer.d.ts +13 -0
  62. package/dist/src/core/rules/import-organizer.js +84 -0
  63. package/dist/src/core/rules/import-organizer.js.map +1 -0
  64. package/dist/src/core/rules/module-grouper.d.ts +13 -0
  65. package/dist/src/core/rules/module-grouper.js +116 -0
  66. package/dist/src/core/rules/module-grouper.js.map +1 -0
  67. package/dist/src/core/rules-engine.d.ts +7 -0
  68. package/dist/src/core/rules-engine.js +89 -0
  69. package/dist/src/core/rules-engine.js.map +1 -0
  70. package/dist/src/core/scorer.d.ts +15 -0
  71. package/dist/src/core/scorer.js +165 -0
  72. package/dist/src/core/scorer.js.map +1 -0
  73. package/dist/src/core/summarizer/keyword-extractor.d.ts +6 -0
  74. package/dist/src/core/summarizer/keyword-extractor.js +38 -0
  75. package/dist/src/core/summarizer/keyword-extractor.js.map +1 -0
  76. package/dist/src/core/summarizer/module-inferrer.d.ts +11 -0
  77. package/dist/src/core/summarizer/module-inferrer.js +171 -0
  78. package/dist/src/core/summarizer/module-inferrer.js.map +1 -0
  79. package/dist/src/core/summarizer/package-reader.d.ts +3 -0
  80. package/dist/src/core/summarizer/package-reader.js +33 -0
  81. package/dist/src/core/summarizer/package-reader.js.map +1 -0
  82. package/dist/src/core/summarizer/purpose-inferrer.d.ts +8 -0
  83. package/dist/src/core/summarizer/purpose-inferrer.js +179 -0
  84. package/dist/src/core/summarizer/purpose-inferrer.js.map +1 -0
  85. package/dist/src/core/summarizer/readme-reader.d.ts +3 -0
  86. package/dist/src/core/summarizer/readme-reader.js +24 -0
  87. package/dist/src/core/summarizer/readme-reader.js.map +1 -0
  88. package/dist/src/core/types/architect-rules.d.ts +27 -0
  89. package/dist/src/core/types/architect-rules.js +2 -0
  90. package/dist/src/core/types/architect-rules.js.map +1 -0
  91. package/dist/src/core/types/core.d.ts +87 -0
  92. package/dist/src/core/types/core.js +2 -0
  93. package/dist/src/core/types/core.js.map +1 -0
  94. package/dist/src/core/types/infrastructure.d.ts +38 -0
  95. package/dist/src/core/types/infrastructure.js +2 -0
  96. package/dist/src/core/types/infrastructure.js.map +1 -0
  97. package/dist/src/core/types/plugin.d.ts +12 -0
  98. package/dist/src/core/types/plugin.js +2 -0
  99. package/dist/src/core/types/plugin.js.map +1 -0
  100. package/dist/src/core/types/rules.d.ts +53 -0
  101. package/dist/src/core/types/rules.js +2 -0
  102. package/dist/src/core/types/rules.js.map +1 -0
  103. package/dist/src/core/types/summarizer.d.ts +12 -0
  104. package/dist/src/core/types/summarizer.js +2 -0
  105. package/dist/src/core/types/summarizer.js.map +1 -0
  106. package/dist/src/infrastructure/git-cache.d.ts +6 -0
  107. package/dist/src/infrastructure/git-cache.js +41 -0
  108. package/dist/src/infrastructure/git-cache.js.map +1 -0
  109. package/dist/src/infrastructure/git-history.d.ts +112 -0
  110. package/dist/src/infrastructure/git-history.js +340 -0
  111. package/dist/src/infrastructure/git-history.js.map +1 -0
  112. package/dist/src/infrastructure/logger.d.ts +20 -0
  113. package/dist/src/infrastructure/logger.js +57 -0
  114. package/dist/src/infrastructure/logger.js.map +1 -0
  115. package/dist/src/infrastructure/scanner.d.ts +31 -0
  116. package/dist/src/infrastructure/scanner.js +334 -0
  117. package/dist/src/infrastructure/scanner.js.map +1 -0
  118. package/dist/tests/analyzers-integration.test.d.ts +7 -0
  119. package/dist/tests/analyzers-integration.test.js +140 -0
  120. package/dist/tests/analyzers-integration.test.js.map +1 -0
  121. package/dist/tests/anti-patterns.test.d.ts +1 -0
  122. package/dist/tests/anti-patterns.test.js +81 -0
  123. package/dist/tests/anti-patterns.test.js.map +1 -0
  124. package/dist/tests/ast-parser.test.d.ts +1 -0
  125. package/dist/tests/ast-parser.test.js +94 -0
  126. package/dist/tests/ast-parser.test.js.map +1 -0
  127. package/dist/tests/fixtures/monorepo/packages/app/src/index.d.ts +1 -0
  128. package/dist/tests/fixtures/monorepo/packages/app/src/index.js +9 -0
  129. package/dist/tests/fixtures/monorepo/packages/app/src/index.js.map +1 -0
  130. package/dist/tests/fixtures/monorepo/packages/core/src/index.d.ts +2 -0
  131. package/dist/tests/fixtures/monorepo/packages/core/src/index.js +11 -0
  132. package/dist/tests/fixtures/monorepo/packages/core/src/index.js.map +1 -0
  133. package/dist/tests/forecast.test.d.ts +7 -0
  134. package/dist/tests/forecast.test.js +380 -0
  135. package/dist/tests/forecast.test.js.map +1 -0
  136. package/dist/tests/git-history.test.d.ts +7 -0
  137. package/dist/tests/git-history.test.js +193 -0
  138. package/dist/tests/git-history.test.js.map +1 -0
  139. package/dist/tests/i18n.test.d.ts +1 -0
  140. package/dist/tests/i18n.test.js +39 -0
  141. package/dist/tests/i18n.test.js.map +1 -0
  142. package/dist/tests/monorepo-scan.test.d.ts +11 -0
  143. package/dist/tests/monorepo-scan.test.js +143 -0
  144. package/dist/tests/monorepo-scan.test.js.map +1 -0
  145. package/dist/tests/plugin-loader.test.d.ts +1 -0
  146. package/dist/tests/plugin-loader.test.js +31 -0
  147. package/dist/tests/plugin-loader.test.js.map +1 -0
  148. package/dist/tests/rules-engine.test.d.ts +1 -0
  149. package/dist/tests/rules-engine.test.js +112 -0
  150. package/dist/tests/rules-engine.test.js.map +1 -0
  151. package/dist/tests/scanner.test.d.ts +1 -0
  152. package/dist/tests/scanner.test.js +44 -0
  153. package/dist/tests/scanner.test.js.map +1 -0
  154. package/dist/tests/scorer.test.d.ts +1 -0
  155. package/dist/tests/scorer.test.js +610 -0
  156. package/dist/tests/scorer.test.js.map +1 -0
  157. package/dist/tests/temporal-scorer.test.d.ts +7 -0
  158. package/dist/tests/temporal-scorer.test.js +239 -0
  159. package/dist/tests/temporal-scorer.test.js.map +1 -0
  160. package/package.json +29 -0
  161. package/src/core/analyzer.ts +499 -0
  162. package/src/core/analyzers/forecast.ts +497 -0
  163. package/src/core/analyzers/index.ts +33 -0
  164. package/src/core/analyzers/temporal-scorer.ts +227 -0
  165. package/src/core/anti-patterns.ts +324 -0
  166. package/src/core/ast/ast-parser.interface.ts +21 -0
  167. package/src/core/ast/path-resolver.ts +61 -0
  168. package/src/core/ast/tree-sitter-parser.ts +158 -0
  169. package/src/core/config.ts +125 -0
  170. package/src/core/diagram.ts +129 -0
  171. package/src/core/i18n.ts +64 -0
  172. package/src/core/locales/en.ts +340 -0
  173. package/src/core/locales/pt-BR.ts +341 -0
  174. package/src/core/locales/types.ts +95 -0
  175. package/src/core/plugin-loader.ts +80 -0
  176. package/src/core/project-summarizer.ts +42 -0
  177. package/src/core/refactor-engine.ts +112 -0
  178. package/src/core/rules/barrel-optimizer.ts +99 -0
  179. package/src/core/rules/dead-code-detector.ts +134 -0
  180. package/src/core/rules/hub-splitter.ts +135 -0
  181. package/src/core/rules/import-organizer.ts +100 -0
  182. package/src/core/rules/module-grouper.ts +133 -0
  183. package/src/core/rules-engine.ts +100 -0
  184. package/src/core/scorer.ts +181 -0
  185. package/src/core/summarizer/keyword-extractor.ts +53 -0
  186. package/src/core/summarizer/module-inferrer.ts +194 -0
  187. package/src/core/summarizer/package-reader.ts +34 -0
  188. package/src/core/summarizer/purpose-inferrer.ts +197 -0
  189. package/src/core/summarizer/readme-reader.ts +24 -0
  190. package/src/core/types/architect-rules.ts +29 -0
  191. package/src/core/types/core.ts +94 -0
  192. package/src/core/types/infrastructure.ts +41 -0
  193. package/src/core/types/plugin.ts +19 -0
  194. package/src/core/types/rules.ts +51 -0
  195. package/src/core/types/summarizer.ts +8 -0
  196. package/src/infrastructure/git-cache.ts +52 -0
  197. package/src/infrastructure/git-history.ts +496 -0
  198. package/src/infrastructure/logger.ts +68 -0
  199. package/src/infrastructure/scanner.ts +349 -0
  200. package/tests/analyzers-integration.test.ts +174 -0
  201. package/tests/anti-patterns.test.ts +95 -0
  202. package/tests/ast-parser.test.ts +102 -0
  203. package/tests/fixtures/monorepo/package.json +6 -0
  204. package/tests/fixtures/monorepo/packages/app/package.json +12 -0
  205. package/tests/fixtures/monorepo/packages/app/src/index.ts +6 -0
  206. package/tests/fixtures/monorepo/packages/core/package.json +7 -0
  207. package/tests/fixtures/monorepo/packages/core/src/index.ts +7 -0
  208. package/tests/forecast.test.ts +504 -0
  209. package/tests/git-history.test.ts +254 -0
  210. package/tests/i18n.test.ts +47 -0
  211. package/tests/monorepo-scan.test.ts +170 -0
  212. package/tests/plugin-loader.test.ts +40 -0
  213. package/tests/rules-engine.test.ts +131 -0
  214. package/tests/scanner.test.ts +54 -0
  215. package/tests/scorer.test.ts +675 -0
  216. package/tests/temporal-scorer.test.ts +306 -0
  217. package/tsconfig.json +9 -0
@@ -0,0 +1,95 @@
1
+ export interface TranslationDictionary {
2
+ // Common
3
+ common: {
4
+ generatedBy: string;
5
+ mandatory: string;
6
+ yes: string;
7
+ no: string;
8
+ action: string;
9
+ };
10
+
11
+ // Scanners / Progress
12
+ progress: {
13
+ scan: string;
14
+ dependencies: string;
15
+ layers: string;
16
+ antipatterns: string;
17
+ scoring: string;
18
+ normalize: string;
19
+ summarize: string;
20
+
21
+ // Verbs
22
+ scanningSystem: string;
23
+ mappingGraph: string;
24
+ classifyingArch: string;
25
+ detectingAntiPatterns: string;
26
+ computingMetrics: string;
27
+ normalizingPaths: string;
28
+ generatingSummary: string;
29
+ };
30
+
31
+ // Agents
32
+ agents: {
33
+ backend: {
34
+ description: string;
35
+ title: string;
36
+ specialistIn: string;
37
+ stack: string;
38
+ principles: string;
39
+ projectStructure: string;
40
+ implementationRules: string;
41
+ rulesBody: string;
42
+ afterImplementation: string;
43
+ afterBody: string;
44
+ };
45
+ frontend: {
46
+ description: string;
47
+ title: string;
48
+ specialistIn: string;
49
+ stack: string;
50
+ prerequisites: string;
51
+ prerequisitesBody: string;
52
+ implementationRules: string;
53
+ rulesBody: string;
54
+ };
55
+ security: {
56
+ description: string;
57
+ title: string;
58
+ analysisFor: string;
59
+ checklist: string;
60
+ checklistBody: string;
61
+ whenToActivate: string;
62
+ whenBody: string;
63
+ expectedOutput: string;
64
+ outputBody: string;
65
+ };
66
+ qa: {
67
+ description: string;
68
+ title: string;
69
+ qualityFor: string;
70
+ nonNegotiable: string;
71
+ nonNegotiableBody: (min: number) => string;
72
+ pyramid: string;
73
+ process: string;
74
+ processBody: string;
75
+ };
76
+ techDebt: {
77
+ description: string;
78
+ title: string;
79
+ controlFor: string;
80
+ currentState: string;
81
+ roadmap: string;
82
+ targets: string;
83
+ rules: string;
84
+ };
85
+ };
86
+
87
+ // Domain & Enriched
88
+ enriched: {
89
+ modules: string;
90
+ endpoints: string;
91
+ domainContext: string;
92
+ untestedModules: string;
93
+ untestedModulesBody: (count: number) => string;
94
+ };
95
+ }
@@ -0,0 +1,80 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { ArchitectPlugin, CustomAntiPatternDetector, PluginContext } from './types/plugin.js';
4
+ import { ArchitectConfig } from './types/core.js';
5
+ import { logger } from '../infrastructure/logger.js';
6
+
7
+ export class PluginLoader {
8
+ private customDetectors: CustomAntiPatternDetector[] = [];
9
+
10
+ constructor(
11
+ private projectPath: string,
12
+ private config: ArchitectConfig
13
+ ) {}
14
+
15
+ public get customAntiPatternDetectors(): CustomAntiPatternDetector[] {
16
+ return this.customDetectors;
17
+ }
18
+
19
+ public async loadPlugins(): Promise<void> {
20
+ if (!this.config.plugins || this.config.plugins.length === 0) {
21
+ return;
22
+ }
23
+
24
+ const context: PluginContext = {
25
+ projectPath: this.projectPath,
26
+ config: this.config
27
+ };
28
+
29
+ for (const pluginSpec of this.config.plugins) {
30
+ try {
31
+ await this.loadSinglePlugin(pluginSpec, context);
32
+ } catch (err) {
33
+ logger.warn(`[Architect Plugin] Failed to load plugin '${pluginSpec}': ${(err as Error).message}`);
34
+ }
35
+ }
36
+ }
37
+
38
+ private async loadSinglePlugin(pluginSpec: string, context: PluginContext): Promise<void> {
39
+ // 1. Resolve path (could be relative to project or node_modules)
40
+ let pluginPath = pluginSpec;
41
+
42
+ // If it starts with ./ or ../ we assume it's relative to the target project
43
+ if (pluginSpec.startsWith('./') || pluginSpec.startsWith('../')) {
44
+ pluginPath = path.resolve(this.projectPath, pluginSpec);
45
+ }
46
+
47
+ // Verify file exists if we are resolving a local JS file to avoid unhelpful stack traces
48
+ if (!pluginSpec.startsWith('@') && !pluginSpec.match(/^[a-z0-9_-]+$/i)) {
49
+ if (!fs.existsSync(pluginPath)) {
50
+ throw new Error(`File not found at ${pluginPath}`);
51
+ }
52
+
53
+ // Node 20+ ESM dynamic imports need absolute file:// URIs on Windows
54
+ if (process.platform === 'win32') {
55
+ pluginPath = `file://${pluginPath.replace(/\\/g, '/')}`;
56
+ }
57
+ }
58
+
59
+ // 2. Load the module using dynamic import
60
+ const pluginModule = await import(pluginPath);
61
+
62
+ // 3. Extract default export mapping
63
+ const plugin: ArchitectPlugin = pluginModule.default || pluginModule;
64
+
65
+ if (!plugin || typeof plugin !== 'object') {
66
+ throw new Error(`Plugin must export an 'ArchitectPlugin' object as default.`);
67
+ }
68
+
69
+ // 4. Register hooks
70
+ if (typeof plugin.detectAntiPatterns === 'function') {
71
+ // Wrap the detector so it automatically receives the PluginContext
72
+ const wrappedDetector: CustomAntiPatternDetector = async (fileTree, deps) => {
73
+ return plugin.detectAntiPatterns!(fileTree, deps, context);
74
+ };
75
+
76
+ this.customDetectors.push(wrappedDetector);
77
+ logger.info(`[Architect Plugin] Registered Custom Rules from: ${plugin.name || pluginSpec}`);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,42 @@
1
+ import { AnalysisReport } from './types/core.js';
2
+ import { ProjectSummary } from './types/summarizer.js';
3
+
4
+ import { PackageReader } from './summarizer/package-reader.js';
5
+ import { ReadmeReader } from './summarizer/readme-reader.js';
6
+ import { KeywordExtractor } from './summarizer/keyword-extractor.js';
7
+ import { PurposeInferrer } from './summarizer/purpose-inferrer.js';
8
+ import { ModuleInferrer } from './summarizer/module-inferrer.js';
9
+
10
+ /**
11
+ * ProjectSummarizer — infers what a project does from its metadata,
12
+ * structure, README, package.json, and file naming conventions.
13
+ *
14
+ * Refactored via Facade pattern in v5.0.0
15
+ */
16
+ export class ProjectSummarizer {
17
+ private packageReader = new PackageReader();
18
+ private readmeReader = new ReadmeReader();
19
+ private keywordExtractor = new KeywordExtractor();
20
+ private purposeInferrer = new PurposeInferrer();
21
+ private moduleInferrer = new ModuleInferrer();
22
+
23
+ summarize(projectPath: string, report: AnalysisReport): ProjectSummary {
24
+ const packageInfo = this.packageReader.readPackageJson(projectPath);
25
+ const readmeContent = this.readmeReader.readReadme(projectPath);
26
+ const modules = this.moduleInferrer.inferModules(report, projectPath);
27
+ const entryPoints = this.purposeInferrer.findEntryPoints(report, projectPath);
28
+ const keywords = this.keywordExtractor.extractKeywords(packageInfo, readmeContent, modules, report);
29
+ const techStack = this.purposeInferrer.buildTechStack(report, packageInfo);
30
+ const description = this.purposeInferrer.buildDescription(packageInfo, readmeContent, report);
31
+ const purpose = this.purposeInferrer.inferPurpose(keywords, modules, report);
32
+
33
+ return {
34
+ description,
35
+ purpose,
36
+ modules,
37
+ techStack,
38
+ entryPoints,
39
+ keywords,
40
+ };
41
+ }
42
+ }
@@ -0,0 +1,112 @@
1
+ // import { readFileSync } from 'fs';
2
+ // import { basename, dirname, join, relative } from 'path';
3
+ import { AnalysisReport } from './types/core.js';
4
+ import { RefactoringPlan, RefactorStep, RefactorRule} from './types/rules.js';
5
+
6
+ // ── Tier 1 Rules ──
7
+ import { HubSplitterRule } from './rules/hub-splitter.js';
8
+ import { BarrelOptimizerRule } from './rules/barrel-optimizer.js';
9
+ import { ImportOrganizerRule } from './rules/import-organizer.js';
10
+ import { ModuleGrouperRule } from './rules/module-grouper.js';
11
+ import { DeadCodeDetectorRule } from './rules/dead-code-detector.js';
12
+
13
+ /**
14
+ * Refactoring Engine v2.0
15
+ * Orchestrates Tier 1 (rule-based) and Tier 2 (AST) refactoring rules.
16
+ */
17
+ export class RefactorEngine {
18
+ private rules: RefactorRule[];
19
+
20
+ constructor() {
21
+ this.rules = [
22
+ // Tier 1: Rule Engine (pattern matching)
23
+ new HubSplitterRule(),
24
+ new BarrelOptimizerRule(),
25
+ new ImportOrganizerRule(),
26
+ new ModuleGrouperRule(),
27
+ new DeadCodeDetectorRule(),
28
+ ];
29
+ }
30
+
31
+ /**
32
+ * Analyze a project and generate a refactoring plan.
33
+ */
34
+ analyze(report: AnalysisReport, projectPath: string): RefactoringPlan {
35
+ const allSteps: RefactorStep[] = [];
36
+ let stepId = 1;
37
+
38
+ // Run each rule
39
+ for (const rule of this.rules) {
40
+ const steps = rule.analyze(report, projectPath);
41
+ for (const step of steps) {
42
+ step.id = stepId++;
43
+ allSteps.push(step);
44
+ }
45
+ }
46
+
47
+ // Sort by priority
48
+ const priorityOrder: Record<string, number> = {
49
+ CRITICAL: 0,
50
+ HIGH: 1,
51
+ MEDIUM: 2,
52
+ LOW: 3,
53
+ };
54
+ allSteps.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
55
+
56
+ // Re-number after sorting
57
+ allSteps.forEach((s, i) => (s.id = i + 1));
58
+
59
+ // Calculate total operations
60
+ const totalOperations = allSteps.reduce(
61
+ (sum, s) => sum + s.operations.length,
62
+ 0
63
+ );
64
+
65
+ // Estimate score after refactoring
66
+ const estimatedScoreAfter = this.estimateScoreAfter(report, allSteps);
67
+
68
+ return {
69
+ timestamp: new Date().toISOString(),
70
+ projectPath,
71
+ currentScore: report.score,
72
+ estimatedScoreAfter,
73
+ steps: allSteps,
74
+ totalOperations,
75
+ tier1Steps: allSteps.filter((s) => s.tier === 1).length,
76
+ tier2Steps: allSteps.filter((s) => s.tier === 2).length,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Estimates the architecture score after applying all refactoring steps.
82
+ */
83
+ private estimateScoreAfter(
84
+ report: AnalysisReport,
85
+ steps: RefactorStep[]
86
+ ): { overall: number; breakdown: Record<string, number> } {
87
+ const breakdown = { ...report.score.breakdown };
88
+
89
+ for (const step of steps) {
90
+ for (const impact of step.scoreImpact) {
91
+ if (impact.metric in breakdown) {
92
+ // Use estimated after value, capped at 95
93
+ const key = impact.metric as keyof typeof breakdown;
94
+ breakdown[key] = Math.min(95, Math.max(breakdown[key], impact.after));
95
+ }
96
+ }
97
+ }
98
+
99
+ // Recalculate overall with same weights
100
+ const overall = Math.round(
101
+ breakdown.modularity * 0.4 +
102
+ breakdown.coupling * 0.25 +
103
+ breakdown.cohesion * 0.2 +
104
+ breakdown.layering * 0.15
105
+ );
106
+
107
+ return {
108
+ overall: Math.min(100, overall),
109
+ breakdown,
110
+ };
111
+ }
112
+ }
@@ -0,0 +1,99 @@
1
+ import { basename, dirname } from 'path';
2
+ import { AnalysisReport } from '../types/core.js';
3
+ import { RefactorRule, RefactorStep, FileOperation } from '../types/rules.js';
4
+
5
+ /**
6
+ * Barrel Optimizer Rule (Tier 1)
7
+ * Analyzes barrel files (__init__.py, index.ts) and suggests optimization.
8
+ * Barrel files that re-export everything create unnecessary coupling.
9
+ */
10
+ export class BarrelOptimizerRule implements RefactorRule {
11
+ name = 'barrel-optimizer';
12
+ tier = 1 as const;
13
+
14
+ private static readonly BARREL_FILES = new Set([
15
+ '__init__.py', 'index.ts', 'index.js', 'index.tsx', 'index.jsx',
16
+ ]);
17
+
18
+ analyze(report: AnalysisReport, _projectPath: string): RefactorStep[] {
19
+ const steps: RefactorStep[] = [];
20
+
21
+ // Find barrel files in the dependency graph
22
+ const barrelNodes = report.dependencyGraph.nodes.filter((n) =>
23
+ BarrelOptimizerRule.BARREL_FILES.has(basename(n))
24
+ );
25
+
26
+ for (const barrel of barrelNodes) {
27
+ // Count how many things this barrel re-exports (outgoing edges)
28
+ const outgoing = report.dependencyGraph.edges.filter(
29
+ (e) => e.from === barrel
30
+ );
31
+ const incoming = report.dependencyGraph.edges.filter(
32
+ (e) => e.to === barrel
33
+ );
34
+
35
+ if (outgoing.length < 3) continue;
36
+
37
+ // Check for "pass-through" pattern: files import from barrel
38
+ // but barrel just re-exports from siblings
39
+ const siblingDir = dirname(barrel);
40
+ const siblingExports = outgoing.filter(
41
+ (e) => dirname(e.to) === siblingDir
42
+ );
43
+
44
+ const operations: FileOperation[] = [];
45
+
46
+ // Suggest direct imports instead of barrel
47
+ for (const consumer of incoming) {
48
+ const consumedModules = outgoing
49
+ .filter((e) => {
50
+ // Check if consumer actually needs this module
51
+ return report.dependencyGraph.edges.some(
52
+ (edge) => edge.from === consumer.from && edge.to === e.to
53
+ );
54
+ })
55
+ .map((e) => e.to);
56
+
57
+ if (consumedModules.length > 0) {
58
+ operations.push({
59
+ type: 'MODIFY',
60
+ path: consumer.from,
61
+ description: `Replace barrel import from \`${basename(barrel)}\` with direct imports: ${consumedModules.map((m) => basename(m)).join(', ')}`,
62
+ });
63
+ }
64
+ }
65
+
66
+ // Suggest simplifying the barrel
67
+ if (siblingExports.length > 5) {
68
+ operations.push({
69
+ type: 'MODIFY',
70
+ path: barrel,
71
+ description: `Simplify ${basename(barrel)}: only re-export public API (${siblingExports.length} re-exports detected, consider reducing)`,
72
+ });
73
+ }
74
+
75
+ if (operations.length > 0) {
76
+ steps.push({
77
+ id: 0,
78
+ tier: 1,
79
+ rule: this.name,
80
+ priority: outgoing.length >= 8 ? 'HIGH' : 'MEDIUM',
81
+ title: `Optimize barrel: ${barrel}`,
82
+ description: `\`${barrel}\` re-exports ${outgoing.length} modules. ` +
83
+ `This creates a "Shotgun Surgery" risk — any change propagates widely.`,
84
+ rationale: `Barrel files that re-export everything make it hard to tree-shake unused code ` +
85
+ `and create implicit dependencies. Direct imports make dependency relationships explicit ` +
86
+ `and reduce the blast radius of changes.`,
87
+ operations,
88
+ scoreImpact: [
89
+ { metric: 'coupling', before: report.score.breakdown.coupling, after: Math.min(95, report.score.breakdown.coupling + 10) },
90
+ { metric: 'layering', before: report.score.breakdown.layering, after: Math.min(95, report.score.breakdown.layering + 5) },
91
+ ],
92
+ aiPrompt: `Analyze the barrel file \`${barrel}\` which re-exports ${outgoing.length} modules. This is creating a "Shotgun Surgery" risk.\nPlease optimize it by:\n1. Removing wildcard exports (e.g., \`export *\`) if any.\n2. Only re-exporting the public API interfaces/classes meant for consumers outside this module.\n3. Updating consumers (${incoming.map(i => basename(i.from)).join(', ')}) to import from the specific files directly instead of passing through this barrel file where appropriate.`,
93
+ });
94
+ }
95
+ }
96
+
97
+ return steps;
98
+ }
99
+ }
@@ -0,0 +1,134 @@
1
+ import { basename } from 'path';
2
+ import { AnalysisReport } from '../types/core.js';
3
+ import { RefactorRule, RefactorStep, FileOperation } from '../types/rules.js';
4
+
5
+ /**
6
+ * Dead Code Detector Rule (Tier 1)
7
+ * Finds files with no incoming edges (nobody imports them)
8
+ * and exports that are never used.
9
+ *
10
+ * Handles both path-style (deepguard/report.py) and
11
+ * dot-notation (deepguard.report) references.
12
+ */
13
+ export class DeadCodeDetectorRule implements RefactorRule {
14
+ name = 'dead-code-detector';
15
+ tier = 1 as const;
16
+
17
+ private static readonly ENTRY_POINTS = new Set([
18
+ 'main.py', 'cli.py', 'app.py', 'manage.py', 'wsgi.py', 'asgi.py',
19
+ 'main.ts', 'main.js', 'app.ts', 'app.js', 'server.ts', 'server.js',
20
+ 'index.html', 'setup.py', 'setup.cfg', 'pyproject.toml',
21
+ ]);
22
+
23
+ analyze(report: AnalysisReport, _projectPath: string): RefactorStep[] {
24
+ const steps: RefactorStep[] = [];
25
+ const edges = report.dependencyGraph.edges;
26
+
27
+ // Build a set of ALL referenced targets (both path and dot-notation)
28
+ const allTargets = new Set<string>();
29
+ const allSources = new Set<string>();
30
+ for (const edge of edges) {
31
+ allTargets.add(edge.to);
32
+ allSources.add(edge.from);
33
+ }
34
+
35
+ // Only consider actual files (with path separators) as candidates
36
+ const fileNodes = report.dependencyGraph.nodes.filter(
37
+ (n) => n.includes('/') || n.includes('\\')
38
+ );
39
+
40
+ // Build incoming edge count considering dot-notation matches
41
+ const incomingCount: Record<string, number> = {};
42
+
43
+ for (const file of fileNodes) {
44
+ incomingCount[file] = 0;
45
+
46
+ // Direct incoming edges
47
+ for (const edge of edges) {
48
+ if (edge.to === file) {
49
+ incomingCount[file]++;
50
+ }
51
+ }
52
+
53
+ // Check dot-notation references:
54
+ // deepguard/report.py might be referenced as deepguard.report or .report
55
+ const dotVariants = this.getDotVariants(file);
56
+ for (const variant of dotVariants) {
57
+ if (allTargets.has(variant)) {
58
+ incomingCount[file]++;
59
+ }
60
+ }
61
+ }
62
+
63
+ // Find orphan files
64
+ const orphans: string[] = [];
65
+
66
+ for (const [file, count] of Object.entries(incomingCount)) {
67
+ const fileName = basename(file);
68
+
69
+ // Skip entry points and config files
70
+ if (DeadCodeDetectorRule.ENTRY_POINTS.has(fileName)) continue;
71
+ if (fileName.startsWith('__')) continue;
72
+ if (fileName.startsWith('.')) continue;
73
+ if (fileName.endsWith('.test.ts') || fileName.endsWith('.spec.ts')) continue;
74
+ if (fileName.endsWith('_test.py') || fileName.endsWith('.test.py')) continue;
75
+
76
+ // Also skip if the file has outgoing edges (it's active code)
77
+ if (allSources.has(file)) continue;
78
+
79
+ if (count === 0) {
80
+ orphans.push(file);
81
+ }
82
+ }
83
+
84
+ if (orphans.length > 0) {
85
+ const operations: FileOperation[] = orphans.map((file) => ({
86
+ type: 'DELETE' as const,
87
+ path: file,
88
+ description: `\`${basename(file)}\` has no incoming dependencies — verify if still needed`,
89
+ }));
90
+
91
+ steps.push({
92
+ id: 0,
93
+ tier: 1,
94
+ rule: this.name,
95
+ priority: orphans.length >= 3 ? 'MEDIUM' : 'LOW',
96
+ title: `Review ${orphans.length} potentially unused file(s)`,
97
+ description: `Found ${orphans.length} file(s) with no incoming dependencies: ` +
98
+ `${orphans.map((f) => `\`${basename(f)}\``).join(', ')}. ` +
99
+ `These may be dead code or missing from the module's public API.`,
100
+ rationale: `Files with zero incoming edges are either entry points (already excluded), ` +
101
+ `or potentially dead code. Removing dead code reduces maintenance burden ` +
102
+ `and improves modularity scores.`,
103
+ operations,
104
+ scoreImpact: [
105
+ { metric: 'modularity', before: report.score.breakdown.modularity, after: Math.min(95, report.score.breakdown.modularity + 5) },
106
+ ],
107
+ aiPrompt: `Analyze the following files: ${orphans.map(f => `\`${f}\``).join(', ')}.\nThese files currently have zero incoming dependencies across the architecture, meaning they might be dead code.\nPlease review them:\n1. If they are unused, explicitly delete them to shrink the blast radius.\n2. If they are entry points that the Architect missed, add them to the ignore list or document their usage.\n3. Make sure to remove any vestigial exports or imports pointing to them from the rest of the codebase.`,
108
+ });
109
+ }
110
+
111
+ return steps;
112
+ }
113
+
114
+ /**
115
+ * Generate dot-notation variants for a file path.
116
+ * "deepguard/report.py" → ["deepguard.report", ".report"]
117
+ */
118
+ private getDotVariants(filePath: string): string[] {
119
+ const variants: string[] = [];
120
+ const withoutExt = filePath.replace(/\.[^.]+$/, '');
121
+ const dotPath = withoutExt.replace(/[/\\]/g, '.');
122
+
123
+ variants.push(dotPath);
124
+
125
+ // Relative dot-notation: .report
126
+ const parts = filePath.split('/');
127
+ if (parts.length >= 2) {
128
+ const lastPart = parts[parts.length - 1].replace(/\.[^.]+$/, '');
129
+ variants.push(`.${lastPart}`);
130
+ }
131
+
132
+ return variants;
133
+ }
134
+ }