@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,497 @@
1
+ /**
2
+ * Architecture Weather Forecast — Predictive analysis
3
+ *
4
+ * Combines temporal scores + velocity vectors + change coupling
5
+ * to predict which modules will become anti-patterns.
6
+ *
7
+ * Key concept: Pre-Anti-Pattern
8
+ * A module isn't an anti-pattern yet, but its trajectory says it will be.
9
+ * "Your code doesn't have a problem — it WILL have one in 3 months."
10
+ *
11
+ * @author Camilo Girardelli — Girardelli Tecnologia
12
+ * @license MIT
13
+ */
14
+
15
+ import type {
16
+ GitHistoryReport,
17
+ ModuleHistory,
18
+ ChangeCoupling,
19
+ // @ts-ignore - Audit cleanup unused variable
20
+ FileHistory,
21
+ } from '../../infrastructure/git-history.js';
22
+ import type { TemporalReport, TemporalScore } from './temporal-scorer.js';
23
+
24
+ // ═══════════════════════════════════════════════════════════════
25
+ // TYPES
26
+ // ═══════════════════════════════════════════════════════════════
27
+
28
+ export type PreAntiPatternType =
29
+ | 'emerging-god-class'
30
+ | 'emerging-shotgun-surgery'
31
+ | 'emerging-feature-envy'
32
+ | 'bus-factor-risk'
33
+ | 'complexity-spiral'
34
+ | 'coupling-magnet';
35
+
36
+ export interface PreAntiPattern {
37
+ type: PreAntiPatternType;
38
+ module: string;
39
+ severity: 'warning' | 'watch' | 'alert';
40
+ currentScore: number;
41
+ projectedScore: number;
42
+ /** Estimated weeks until threshold breach */
43
+ weeksToThreshold: number;
44
+ /** What threshold will be crossed */
45
+ threshold: number;
46
+ description: string;
47
+ evidence: string[];
48
+ recommendation: string;
49
+ confidence: number;
50
+ }
51
+
52
+ export interface ModuleForecast {
53
+ module: string;
54
+ currentHealth: 'healthy' | 'at-risk' | 'degrading' | 'critical';
55
+ forecast6Months: 'stable' | 'declining' | 'breakdown';
56
+ preAntiPatterns: PreAntiPattern[];
57
+ bottleneckProbability: number; // 0-1
58
+ riskFactors: string[];
59
+ topAction: string;
60
+ }
61
+
62
+ export interface WeatherForecast {
63
+ projectPath: string;
64
+ generatedAt: string;
65
+ overallOutlook: 'sunny' | 'cloudy' | 'stormy';
66
+ headline: string;
67
+ modules: ModuleForecast[];
68
+ preAntiPatterns: PreAntiPattern[];
69
+ topRisks: string[];
70
+ recommendations: string[];
71
+ }
72
+
73
+ export interface ForecastConfig {
74
+ /** Score threshold for anti-pattern (default: 40) */
75
+ antiPatternThreshold?: number;
76
+ /** Churn rate threshold for god class emergence (default: 150 lines/commit) */
77
+ godClassChurnThreshold?: number;
78
+ /** Co-change count for shotgun surgery (default: 5) */
79
+ shotgunCouplingThreshold?: number;
80
+ /** Max bus factor for risk (default: 1) */
81
+ busFatorRiskThreshold?: number;
82
+ /** Weeks for projection (default: 26 — 6 months) */
83
+ forecastWeeks?: number;
84
+ }
85
+
86
+ const DEFAULT_CONFIG: Required<ForecastConfig> = {
87
+ antiPatternThreshold: 40,
88
+ godClassChurnThreshold: 150,
89
+ shotgunCouplingThreshold: 5,
90
+ busFatorRiskThreshold: 1,
91
+ forecastWeeks: 26,
92
+ };
93
+
94
+ // ═══════════════════════════════════════════════════════════════
95
+ // FORECAST ENGINE
96
+ // ═══════════════════════════════════════════════════════════════
97
+
98
+ export class ForecastEngine {
99
+ private config: Required<ForecastConfig>;
100
+
101
+ constructor(config?: ForecastConfig) {
102
+ this.config = { ...DEFAULT_CONFIG, ...config };
103
+ }
104
+
105
+ /**
106
+ * Generate architecture weather forecast.
107
+ */
108
+ forecast(
109
+ gitReport: GitHistoryReport,
110
+ temporalReport: TemporalReport,
111
+ ): WeatherForecast {
112
+ const preAntiPatterns: PreAntiPattern[] = [];
113
+ const moduleForecastMap = new Map<string, ModuleForecast>();
114
+
115
+ // Detect pre-anti-patterns for each module
116
+ for (const module of gitReport.modules) {
117
+ const temporal = temporalReport.modules.find(m => m.module === module.modulePath);
118
+ if (!temporal) continue;
119
+
120
+ const patterns = this.detectPreAntiPatterns(module, temporal, gitReport.changeCouplings);
121
+ preAntiPatterns.push(...patterns);
122
+
123
+ const forecast = this.forecastModule(module, temporal, patterns);
124
+ moduleForecastMap.set(module.modulePath, forecast);
125
+ }
126
+
127
+ // Sort by risk
128
+ const modules = Array.from(moduleForecastMap.values())
129
+ .sort((a, b) => b.bottleneckProbability - a.bottleneckProbability);
130
+
131
+ const outlook = this.classifyOutlook(temporalReport, preAntiPatterns);
132
+ const headline = this.generateHeadline(outlook, preAntiPatterns, modules);
133
+ const topRisks = this.identifyTopRisks(modules, preAntiPatterns);
134
+ const recommendations = this.generateRecommendations(modules, preAntiPatterns);
135
+
136
+ return {
137
+ projectPath: gitReport.projectPath,
138
+ generatedAt: new Date().toISOString(),
139
+ overallOutlook: outlook,
140
+ headline,
141
+ modules,
142
+ preAntiPatterns: preAntiPatterns.sort((a, b) => a.weeksToThreshold - b.weeksToThreshold),
143
+ topRisks,
144
+ recommendations,
145
+ };
146
+ }
147
+
148
+ // ── Pre-Anti-Pattern Detection ──
149
+
150
+ private detectPreAntiPatterns(
151
+ module: ModuleHistory,
152
+ temporal: TemporalScore,
153
+ couplings: ChangeCoupling[],
154
+ ): PreAntiPattern[] {
155
+ const patterns: PreAntiPattern[] = [];
156
+
157
+ patterns.push(...this.detectEmergingGodClass(module, temporal));
158
+ patterns.push(...this.detectEmergingShotgunSurgery(module, couplings));
159
+ patterns.push(...this.detectBusFactorRisk(module, temporal));
160
+ patterns.push(...this.detectComplexitySpiral(module, temporal));
161
+ patterns.push(...this.detectCouplingMagnet(module, couplings, temporal));
162
+
163
+ return patterns;
164
+ }
165
+
166
+ private detectEmergingGodClass(
167
+ module: ModuleHistory,
168
+ temporal: TemporalScore,
169
+ ): PreAntiPattern[] {
170
+ const patterns: PreAntiPattern[] = [];
171
+
172
+ for (const file of module.files) {
173
+ if (file.churnRate < this.config.godClassChurnThreshold) continue;
174
+ if (temporal.velocity.churnTrend <= 0) continue;
175
+
176
+ // Project when churn will exceed critical threshold
177
+ const weeklyGrowth = file.churnRate * (temporal.velocity.churnTrend / 100) / 4;
178
+ const criticalChurn = this.config.godClassChurnThreshold * 2;
179
+ const weeksToThreshold = weeklyGrowth > 0
180
+ ? Math.ceil((criticalChurn - file.churnRate) / weeklyGrowth)
181
+ : Infinity;
182
+
183
+ if (weeksToThreshold <= this.config.forecastWeeks) {
184
+ patterns.push({
185
+ type: 'emerging-god-class',
186
+ module: module.modulePath,
187
+ severity: weeksToThreshold <= 8 ? 'alert' : weeksToThreshold <= 16 ? 'warning' : 'watch',
188
+ currentScore: temporal.staticScore,
189
+ projectedScore: temporal.projectedScore,
190
+ weeksToThreshold,
191
+ threshold: criticalChurn,
192
+ description: `File '${file.path}' has churn rate ${Math.round(file.churnRate)} lines/commit and growing ${Math.round(temporal.velocity.churnTrend)}%`,
193
+ evidence: [
194
+ `Current churn: ${Math.round(file.churnRate)} lines/commit`,
195
+ `Growth rate: ${Math.round(temporal.velocity.churnTrend)}%`,
196
+ `${file.commits} commits in analysis period`,
197
+ `${file.authors.size} contributor(s)`,
198
+ ],
199
+ recommendation: 'Split into smaller, focused modules before complexity makes refactoring prohibitively expensive.',
200
+ confidence: Math.min(0.9, 0.5 + (file.commits / 50)),
201
+ });
202
+ }
203
+ }
204
+
205
+ return patterns;
206
+ }
207
+
208
+ private detectEmergingShotgunSurgery(
209
+ module: ModuleHistory,
210
+ couplings: ChangeCoupling[],
211
+ ): PreAntiPattern[] {
212
+ // Count how many files this module is coupled with
213
+ const moduleCouplings = couplings.filter(
214
+ c => c.fileA.startsWith(module.modulePath) || c.fileB.startsWith(module.modulePath)
215
+ );
216
+
217
+ if (moduleCouplings.length < this.config.shotgunCouplingThreshold) return [];
218
+
219
+ const avgConfidence = moduleCouplings.reduce((s, c) => s + c.confidence, 0) / moduleCouplings.length;
220
+
221
+ return [{
222
+ type: 'emerging-shotgun-surgery',
223
+ module: module.modulePath,
224
+ severity: moduleCouplings.length > 10 ? 'alert' : 'warning',
225
+ currentScore: 0,
226
+ projectedScore: 0,
227
+ weeksToThreshold: 4,
228
+ threshold: this.config.shotgunCouplingThreshold,
229
+ description: `Module '${module.modulePath}' has ${moduleCouplings.length} change-coupled files — changes here ripple across the codebase`,
230
+ evidence: [
231
+ `${moduleCouplings.length} files change together with this module`,
232
+ `Average coupling confidence: ${Math.round(avgConfidence * 100)}%`,
233
+ ...moduleCouplings.slice(0, 3).map(c =>
234
+ `${c.fileA} ↔ ${c.fileB} (${c.cochangeCount} co-changes)`
235
+ ),
236
+ ],
237
+ recommendation: 'Extract shared concerns into a dedicated module. Introduce interfaces to decouple.',
238
+ confidence: avgConfidence,
239
+ }];
240
+ }
241
+
242
+ private detectBusFactorRisk(
243
+ module: ModuleHistory,
244
+ temporal: TemporalScore,
245
+ ): PreAntiPattern[] {
246
+ if (module.busFactor > this.config.busFatorRiskThreshold) return [];
247
+ if (module.aggregateCommits < 5) return []; // too few commits to judge
248
+
249
+ return [{
250
+ type: 'bus-factor-risk',
251
+ module: module.modulePath,
252
+ severity: temporal.trend === 'degrading' ? 'alert' : 'warning',
253
+ currentScore: temporal.staticScore,
254
+ projectedScore: temporal.projectedScore,
255
+ weeksToThreshold: this.config.forecastWeeks,
256
+ threshold: 2,
257
+ description: `Module '${module.modulePath}' has bus factor of ${module.busFactor} — all knowledge in one person`,
258
+ evidence: [
259
+ `Only ${module.busFactor} contributor(s)`,
260
+ `${module.aggregateCommits} total commits`,
261
+ `${module.files.length} files in module`,
262
+ ],
263
+ recommendation: 'Pair programming or code review rotation to spread knowledge. Document critical decisions.',
264
+ confidence: 0.8,
265
+ }];
266
+ }
267
+
268
+ private detectComplexitySpiral(
269
+ module: ModuleHistory,
270
+ temporal: TemporalScore,
271
+ ): PreAntiPattern[] {
272
+ if (temporal.velocity.churnTrend <= 20) return [];
273
+ if (temporal.velocity.direction !== 'accelerating') return [];
274
+
275
+ // Accelerating churn + increasing commit rate = complexity spiral
276
+ const weeklyScoreDecay = (temporal.staticScore - temporal.projectedScore) / temporal.projectionWeeks;
277
+ const weeksToThreshold = weeklyScoreDecay > 0
278
+ ? Math.ceil((temporal.temporalScore - this.config.antiPatternThreshold) / weeklyScoreDecay)
279
+ : Infinity;
280
+
281
+ if (weeksToThreshold > this.config.forecastWeeks) return [];
282
+
283
+ return [{
284
+ type: 'complexity-spiral',
285
+ module: module.modulePath,
286
+ severity: weeksToThreshold <= 8 ? 'alert' : 'warning',
287
+ currentScore: temporal.temporalScore,
288
+ projectedScore: temporal.projectedScore,
289
+ weeksToThreshold,
290
+ threshold: this.config.antiPatternThreshold,
291
+ description: `Module '${module.modulePath}' is in a complexity spiral — accelerating churn with increasing commit frequency`,
292
+ evidence: [
293
+ `Churn trend: +${Math.round(temporal.velocity.churnTrend)}%`,
294
+ `Commit acceleration: +${Math.round(temporal.velocity.commitAcceleration)}%`,
295
+ `Current temporal score: ${temporal.temporalScore}/100`,
296
+ `Projected score in ${temporal.projectionWeeks} weeks: ${temporal.projectedScore}/100`,
297
+ ],
298
+ recommendation: 'Stop adding features to this module. Invest in refactoring and test coverage first.',
299
+ confidence: temporal.projectionConfidence,
300
+ }];
301
+ }
302
+
303
+ private detectCouplingMagnet(
304
+ module: ModuleHistory,
305
+ couplings: ChangeCoupling[],
306
+ temporal: TemporalScore,
307
+ ): PreAntiPattern[] {
308
+ // Files that are increasingly coupled with many others
309
+ const inboundCouplings = couplings.filter(
310
+ c => c.fileB.startsWith(module.modulePath) && c.confidence > 0.5
311
+ );
312
+
313
+ if (inboundCouplings.length < 3) return [];
314
+ if (temporal.velocity.commitAcceleration <= 0) return [];
315
+
316
+ return [{
317
+ type: 'coupling-magnet',
318
+ module: module.modulePath,
319
+ severity: 'watch',
320
+ currentScore: temporal.staticScore,
321
+ projectedScore: temporal.projectedScore,
322
+ weeksToThreshold: 12,
323
+ threshold: 10,
324
+ description: `Module '${module.modulePath}' is becoming a coupling magnet — ${inboundCouplings.length} high-confidence inbound dependencies`,
325
+ evidence: [
326
+ `${inboundCouplings.length} modules depend on changes here`,
327
+ `Module commit rate accelerating: +${Math.round(temporal.velocity.commitAcceleration)}%`,
328
+ ],
329
+ recommendation: 'Extract stable interfaces. Consider the Dependency Inversion Principle to break inbound coupling.',
330
+ confidence: 0.6,
331
+ }];
332
+ }
333
+
334
+ // ── Module Forecast ──
335
+
336
+ private forecastModule(
337
+ module: ModuleHistory,
338
+ temporal: TemporalScore,
339
+ patterns: PreAntiPattern[],
340
+ ): ModuleForecast {
341
+ const health = this.classifyHealth(temporal);
342
+ const forecast6m = this.classify6MonthForecast(temporal, patterns);
343
+ const bottleneckProb = this.calculateBottleneckProbability(temporal, patterns, module);
344
+
345
+ const riskFactors: string[] = [];
346
+ if (temporal.trend === 'degrading') riskFactors.push('Score degrading');
347
+ if (module.busFactor <= 1) riskFactors.push('Single contributor');
348
+ if (temporal.velocity.churnTrend > 30) riskFactors.push('Churn increasing');
349
+ if (patterns.length > 0) riskFactors.push(`${patterns.length} pre-anti-pattern(s)`);
350
+
351
+ const topAction = patterns.length > 0
352
+ ? patterns[0].recommendation
353
+ : temporal.trend === 'degrading'
354
+ ? 'Review recent changes and stabilize'
355
+ : 'No action needed';
356
+
357
+ return {
358
+ module: module.modulePath,
359
+ currentHealth: health,
360
+ forecast6Months: forecast6m,
361
+ preAntiPatterns: patterns,
362
+ bottleneckProbability: bottleneckProb,
363
+ riskFactors,
364
+ topAction,
365
+ };
366
+ }
367
+
368
+ private classifyHealth(temporal: TemporalScore): ModuleForecast['currentHealth'] {
369
+ if (temporal.temporalScore < 30) return 'critical';
370
+ if (temporal.temporalScore < 50 || temporal.trend === 'degrading') return 'degrading';
371
+ if (temporal.temporalScore < 70) return 'at-risk';
372
+ return 'healthy';
373
+ }
374
+
375
+ private classify6MonthForecast(
376
+ temporal: TemporalScore,
377
+ patterns: PreAntiPattern[],
378
+ ): ModuleForecast['forecast6Months'] {
379
+ const alerts = patterns.filter(p => p.severity === 'alert');
380
+ if (alerts.length > 0 || temporal.projectedScore < 30) return 'breakdown';
381
+ if (temporal.trend === 'degrading' || patterns.length > 0) return 'declining';
382
+ return 'stable';
383
+ }
384
+
385
+ private calculateBottleneckProbability(
386
+ temporal: TemporalScore,
387
+ patterns: PreAntiPattern[],
388
+ module: ModuleHistory,
389
+ ): number {
390
+ let prob = 0;
391
+
392
+ // Low score → higher probability
393
+ if (temporal.temporalScore < 50) prob += 0.3;
394
+ else if (temporal.temporalScore < 70) prob += 0.1;
395
+
396
+ // Degrading trend
397
+ if (temporal.trend === 'degrading') prob += 0.2;
398
+
399
+ // Pre-anti-patterns
400
+ prob += Math.min(0.3, patterns.length * 0.1);
401
+
402
+ // Low bus factor
403
+ if (module.busFactor <= 1) prob += 0.1;
404
+
405
+ // High churn
406
+ if (temporal.velocity.churnTrend > 30) prob += 0.1;
407
+
408
+ return Math.min(1, Math.round(prob * 100) / 100);
409
+ }
410
+
411
+ // ── Overall Analysis ──
412
+
413
+ private classifyOutlook(
414
+ temporal: TemporalReport,
415
+ patterns: PreAntiPattern[],
416
+ ): WeatherForecast['overallOutlook'] {
417
+ const alerts = patterns.filter(p => p.severity === 'alert');
418
+ if (alerts.length >= 2 || temporal.overallTrend === 'degrading') return 'stormy';
419
+ if (alerts.length >= 1 || patterns.length >= 3) return 'cloudy';
420
+ return 'sunny';
421
+ }
422
+
423
+ private generateHeadline(
424
+ outlook: WeatherForecast['overallOutlook'],
425
+ patterns: PreAntiPattern[],
426
+ modules: ModuleForecast[],
427
+ ): string {
428
+ const critical = modules.filter(m => m.currentHealth === 'critical').length;
429
+ const degrading = modules.filter(m => m.currentHealth === 'degrading').length;
430
+
431
+ switch (outlook) {
432
+ case 'stormy':
433
+ return `${critical + degrading} module(s) at risk. ${patterns.length} pre-anti-pattern(s) detected. Immediate action recommended.`;
434
+ case 'cloudy':
435
+ return `Architecture trending stable with ${patterns.length} emerging concern(s). Proactive refactoring recommended.`;
436
+ case 'sunny':
437
+ return 'Architecture is healthy and stable. Continue current practices.';
438
+ }
439
+ }
440
+
441
+ private identifyTopRisks(
442
+ modules: ModuleForecast[],
443
+ patterns: PreAntiPattern[],
444
+ ): string[] {
445
+ const risks: string[] = [];
446
+
447
+ const breakdowns = modules.filter(m => m.forecast6Months === 'breakdown');
448
+ if (breakdowns.length > 0) {
449
+ risks.push(`${breakdowns.length} module(s) projected to break down within 6 months: ${breakdowns.map(m => m.module).join(', ')}`);
450
+ }
451
+
452
+ const busRisks = patterns.filter(p => p.type === 'bus-factor-risk');
453
+ if (busRisks.length > 0) {
454
+ risks.push(`Bus factor risk in ${busRisks.length} module(s) — knowledge concentrated in single contributors`);
455
+ }
456
+
457
+ const spirals = patterns.filter(p => p.type === 'complexity-spiral');
458
+ if (spirals.length > 0) {
459
+ risks.push(`Complexity spiral detected in: ${spirals.map(p => p.module).join(', ')}`);
460
+ }
461
+
462
+ return risks.slice(0, 5);
463
+ }
464
+
465
+ private generateRecommendations(
466
+ modules: ModuleForecast[],
467
+ patterns: PreAntiPattern[],
468
+ ): string[] {
469
+ const recs: string[] = [];
470
+
471
+ const critical = modules.filter(m => m.currentHealth === 'critical');
472
+ if (critical.length > 0) {
473
+ recs.push(`Immediate: Stabilize ${critical.map(m => m.module).join(', ')} — freeze features, invest in refactoring`);
474
+ }
475
+
476
+ const godClasses = patterns.filter(p => p.type === 'emerging-god-class');
477
+ if (godClasses.length > 0) {
478
+ recs.push(`Split growing files before they become god classes: ${godClasses.map(p => p.module).join(', ')}`);
479
+ }
480
+
481
+ const shotgun = patterns.filter(p => p.type === 'emerging-shotgun-surgery');
482
+ if (shotgun.length > 0) {
483
+ recs.push(`Decouple modules with high change coupling to prevent shotgun surgery`);
484
+ }
485
+
486
+ const busRisks = patterns.filter(p => p.type === 'bus-factor-risk');
487
+ if (busRisks.length > 0) {
488
+ recs.push(`Spread knowledge: pair programming or rotation for ${busRisks.map(p => p.module).join(', ')}`);
489
+ }
490
+
491
+ if (recs.length === 0) {
492
+ recs.push('Architecture is healthy. Continue monitoring temporal trends.');
493
+ }
494
+
495
+ return recs.slice(0, 5);
496
+ }
497
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Architect v4.0 Analyzers — Temporal & Predictive
3
+ */
4
+
5
+ export { GitHistoryAnalyzer } from '../../infrastructure/git-history.js';
6
+ export type {
7
+ GitCommit,
8
+ FileChange,
9
+ FileHistory,
10
+ ModuleHistory,
11
+ VelocityVector,
12
+ ChangeCoupling,
13
+ GitHistoryReport,
14
+ WeeklySnapshot,
15
+ GitAnalyzerConfig,
16
+ } from '../../infrastructure/git-history.js';
17
+
18
+ export { TemporalScorer } from './temporal-scorer.js';
19
+ export type {
20
+ Trend,
21
+ TemporalScore,
22
+ TemporalReport,
23
+ TemporalScorerConfig,
24
+ } from './temporal-scorer.js';
25
+
26
+ export { ForecastEngine } from './forecast.js';
27
+ export type {
28
+ PreAntiPatternType,
29
+ PreAntiPattern,
30
+ ModuleForecast,
31
+ WeatherForecast,
32
+ ForecastConfig,
33
+ } from './forecast.js';