archicore 0.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 (118) hide show
  1. package/README.md +530 -0
  2. package/dist/analyzers/dead-code.d.ts +95 -0
  3. package/dist/analyzers/dead-code.js +327 -0
  4. package/dist/analyzers/duplication.d.ts +90 -0
  5. package/dist/analyzers/duplication.js +344 -0
  6. package/dist/analyzers/security.d.ts +79 -0
  7. package/dist/analyzers/security.js +484 -0
  8. package/dist/architecture/index.d.ts +35 -0
  9. package/dist/architecture/index.js +249 -0
  10. package/dist/cli/commands/analyzers.d.ts +6 -0
  11. package/dist/cli/commands/analyzers.js +431 -0
  12. package/dist/cli/commands/export.d.ts +6 -0
  13. package/dist/cli/commands/export.js +78 -0
  14. package/dist/cli/commands/index.d.ts +8 -0
  15. package/dist/cli/commands/index.js +8 -0
  16. package/dist/cli/commands/init.d.ts +26 -0
  17. package/dist/cli/commands/init.js +140 -0
  18. package/dist/cli/commands/interactive.d.ts +7 -0
  19. package/dist/cli/commands/interactive.js +522 -0
  20. package/dist/cli/commands/projects.d.ts +6 -0
  21. package/dist/cli/commands/projects.js +249 -0
  22. package/dist/cli/index.d.ts +7 -0
  23. package/dist/cli/index.js +7 -0
  24. package/dist/cli/ui/box.d.ts +17 -0
  25. package/dist/cli/ui/box.js +62 -0
  26. package/dist/cli/ui/colors.d.ts +49 -0
  27. package/dist/cli/ui/colors.js +86 -0
  28. package/dist/cli/ui/index.d.ts +9 -0
  29. package/dist/cli/ui/index.js +9 -0
  30. package/dist/cli/ui/prompt.d.ts +34 -0
  31. package/dist/cli/ui/prompt.js +122 -0
  32. package/dist/cli/ui/spinner.d.ts +29 -0
  33. package/dist/cli/ui/spinner.js +80 -0
  34. package/dist/cli/ui/table.d.ts +33 -0
  35. package/dist/cli/ui/table.js +84 -0
  36. package/dist/cli/utils/config.d.ts +23 -0
  37. package/dist/cli/utils/config.js +73 -0
  38. package/dist/cli/utils/index.d.ts +6 -0
  39. package/dist/cli/utils/index.js +6 -0
  40. package/dist/cli/utils/session.d.ts +27 -0
  41. package/dist/cli/utils/session.js +117 -0
  42. package/dist/cli.d.ts +8 -0
  43. package/dist/cli.js +295 -0
  44. package/dist/code-index/ast-parser.d.ts +16 -0
  45. package/dist/code-index/ast-parser.js +330 -0
  46. package/dist/code-index/dependency-graph.d.ts +16 -0
  47. package/dist/code-index/dependency-graph.js +161 -0
  48. package/dist/code-index/index.d.ts +44 -0
  49. package/dist/code-index/index.js +124 -0
  50. package/dist/code-index/symbol-extractor.d.ts +13 -0
  51. package/dist/code-index/symbol-extractor.js +150 -0
  52. package/dist/export/index.d.ts +92 -0
  53. package/dist/export/index.js +676 -0
  54. package/dist/github/github-service.d.ts +146 -0
  55. package/dist/github/github-service.js +609 -0
  56. package/dist/impact-engine/index.d.ts +25 -0
  57. package/dist/impact-engine/index.js +284 -0
  58. package/dist/index.d.ts +60 -0
  59. package/dist/index.js +149 -0
  60. package/dist/metrics/index.d.ts +136 -0
  61. package/dist/metrics/index.js +525 -0
  62. package/dist/orchestrator/deepseek-optimizer.d.ts +67 -0
  63. package/dist/orchestrator/deepseek-optimizer.js +320 -0
  64. package/dist/orchestrator/index.d.ts +34 -0
  65. package/dist/orchestrator/index.js +305 -0
  66. package/dist/pr-guardian/index.d.ts +143 -0
  67. package/dist/pr-guardian/index.js +553 -0
  68. package/dist/refactoring/index.d.ts +108 -0
  69. package/dist/refactoring/index.js +580 -0
  70. package/dist/rules-engine/index.d.ts +129 -0
  71. package/dist/rules-engine/index.js +482 -0
  72. package/dist/semantic-memory/embedding-service.d.ts +24 -0
  73. package/dist/semantic-memory/embedding-service.js +120 -0
  74. package/dist/semantic-memory/index.d.ts +45 -0
  75. package/dist/semantic-memory/index.js +206 -0
  76. package/dist/semantic-memory/vector-store.d.ts +27 -0
  77. package/dist/semantic-memory/vector-store.js +166 -0
  78. package/dist/server/index.d.ts +28 -0
  79. package/dist/server/index.js +141 -0
  80. package/dist/server/middleware/api-auth.d.ts +43 -0
  81. package/dist/server/middleware/api-auth.js +256 -0
  82. package/dist/server/routes/admin.d.ts +5 -0
  83. package/dist/server/routes/admin.js +123 -0
  84. package/dist/server/routes/api.d.ts +7 -0
  85. package/dist/server/routes/api.js +362 -0
  86. package/dist/server/routes/auth.d.ts +16 -0
  87. package/dist/server/routes/auth.js +191 -0
  88. package/dist/server/routes/developer.d.ts +8 -0
  89. package/dist/server/routes/developer.js +439 -0
  90. package/dist/server/routes/github.d.ts +7 -0
  91. package/dist/server/routes/github.js +495 -0
  92. package/dist/server/routes/upload.d.ts +7 -0
  93. package/dist/server/routes/upload.js +196 -0
  94. package/dist/server/services/api-key-service.d.ts +81 -0
  95. package/dist/server/services/api-key-service.js +281 -0
  96. package/dist/server/services/auth-service.d.ts +40 -0
  97. package/dist/server/services/auth-service.js +315 -0
  98. package/dist/server/services/project-service.d.ts +123 -0
  99. package/dist/server/services/project-service.js +533 -0
  100. package/dist/server/services/token-service.d.ts +107 -0
  101. package/dist/server/services/token-service.js +416 -0
  102. package/dist/server/services/upload-service.d.ts +93 -0
  103. package/dist/server/services/upload-service.js +464 -0
  104. package/dist/types/api.d.ts +188 -0
  105. package/dist/types/api.js +86 -0
  106. package/dist/types/github.d.ts +335 -0
  107. package/dist/types/github.js +5 -0
  108. package/dist/types/index.d.ts +265 -0
  109. package/dist/types/index.js +32 -0
  110. package/dist/types/user.d.ts +69 -0
  111. package/dist/types/user.js +42 -0
  112. package/dist/utils/file-utils.d.ts +20 -0
  113. package/dist/utils/file-utils.js +163 -0
  114. package/dist/utils/logger.d.ts +17 -0
  115. package/dist/utils/logger.js +41 -0
  116. package/dist/watcher/index.d.ts +125 -0
  117. package/dist/watcher/index.js +397 -0
  118. package/package.json +71 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Refactoring Suggestions Engine
3
+ *
4
+ * Автоматические предложения по рефакторингу:
5
+ * - Extract Method/Function
6
+ * - Extract Class/Module
7
+ * - Rename suggestions
8
+ * - Move to appropriate layer
9
+ * - Simplify conditionals
10
+ * - Remove code smells
11
+ */
12
+ import { DependencyGraph, Symbol } from '../types/index.js';
13
+ import { ProjectMetrics } from '../metrics/index.js';
14
+ import { DuplicationResult } from '../analyzers/duplication.js';
15
+ import { DeadCodeResult } from '../analyzers/dead-code.js';
16
+ import { RulesCheckResult } from '../rules-engine/index.js';
17
+ export interface RefactoringSuggestion {
18
+ id: string;
19
+ type: RefactoringType;
20
+ priority: 'critical' | 'high' | 'medium' | 'low';
21
+ effort: 'trivial' | 'easy' | 'moderate' | 'hard';
22
+ file: string;
23
+ line?: number;
24
+ endLine?: number;
25
+ title: string;
26
+ description: string;
27
+ rationale: string;
28
+ suggestedCode?: string;
29
+ relatedFiles?: string[];
30
+ estimatedImpact: Impact;
31
+ }
32
+ export type RefactoringType = 'extract-method' | 'extract-class' | 'extract-module' | 'inline-method' | 'move-method' | 'move-to-layer' | 'rename' | 'simplify-conditional' | 'remove-dead-code' | 'eliminate-duplication' | 'reduce-complexity' | 'fix-coupling' | 'introduce-parameter-object' | 'replace-magic-number' | 'extract-constant' | 'split-file';
33
+ export interface Impact {
34
+ complexity: number;
35
+ maintainability: number;
36
+ testability: number;
37
+ readability: number;
38
+ }
39
+ export interface RefactoringResult {
40
+ suggestions: RefactoringSuggestion[];
41
+ summary: RefactoringSummary;
42
+ }
43
+ export interface RefactoringSummary {
44
+ totalSuggestions: number;
45
+ byPriority: {
46
+ critical: number;
47
+ high: number;
48
+ medium: number;
49
+ low: number;
50
+ };
51
+ byType: Record<RefactoringType, number>;
52
+ estimatedTotalEffort: string;
53
+ potentialImprovements: {
54
+ complexityReduction: number;
55
+ maintainabilityGain: number;
56
+ };
57
+ }
58
+ export declare class RefactoringEngine {
59
+ /**
60
+ * Генерация всех предложений по рефакторингу
61
+ */
62
+ analyze(graph: DependencyGraph, symbols: Map<string, Symbol>, fileContents: Map<string, string>, metrics: ProjectMetrics, duplication?: DuplicationResult, deadCode?: DeadCodeResult, rulesResult?: RulesCheckResult): Promise<RefactoringResult>;
63
+ /**
64
+ * Анализ метрик для предложений
65
+ */
66
+ private analyzeMetrics;
67
+ /**
68
+ * Анализ дублирования
69
+ */
70
+ private analyzeDuplication;
71
+ /**
72
+ * Анализ мёртвого кода
73
+ */
74
+ private analyzeDeadCode;
75
+ /**
76
+ * Анализ нарушений правил
77
+ */
78
+ private analyzeRuleViolations;
79
+ /**
80
+ * Конвертация нарушения в предложение
81
+ */
82
+ private violationToSuggestion;
83
+ /**
84
+ * Анализ code smells
85
+ */
86
+ private analyzeCodeSmells;
87
+ /**
88
+ * Анализ структуры проекта
89
+ */
90
+ private analyzeProjectStructure;
91
+ /**
92
+ * Генерация кода для извлечения
93
+ */
94
+ private generateExtractionSuggestion;
95
+ /**
96
+ * Приоритизация предложений
97
+ */
98
+ private prioritizeSuggestions;
99
+ /**
100
+ * Расчёт сводки
101
+ */
102
+ private calculateSummary;
103
+ /**
104
+ * Хеширование строки
105
+ */
106
+ private hashString;
107
+ }
108
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,580 @@
1
+ /**
2
+ * Refactoring Suggestions Engine
3
+ *
4
+ * Автоматические предложения по рефакторингу:
5
+ * - Extract Method/Function
6
+ * - Extract Class/Module
7
+ * - Rename suggestions
8
+ * - Move to appropriate layer
9
+ * - Simplify conditionals
10
+ * - Remove code smells
11
+ */
12
+ import { SymbolKind } from '../types/index.js';
13
+ import { Logger } from '../utils/logger.js';
14
+ // Code smell patterns
15
+ const CODE_SMELLS = {
16
+ longMethod: { maxLines: 50, maxComplexity: 10 },
17
+ longParameterList: { maxParams: 5 },
18
+ largeClass: { maxMethods: 20, maxLines: 500 },
19
+ godClass: { maxDependencies: 15 },
20
+ featureEnvy: { threshold: 3 }, // вызовов методов другого класса
21
+ dataClump: { minOccurrences: 3 },
22
+ primitiveObsession: { threshold: 5 },
23
+ switchStatement: { maxCases: 5 },
24
+ magicNumber: { pattern: /(?<![.\d])\b\d{2,}\b(?!\.\d)/ },
25
+ deepNesting: { maxDepth: 4 }
26
+ };
27
+ export class RefactoringEngine {
28
+ /**
29
+ * Генерация всех предложений по рефакторингу
30
+ */
31
+ async analyze(graph, symbols, fileContents, metrics, duplication, deadCode, rulesResult) {
32
+ Logger.progress('Generating refactoring suggestions...');
33
+ const suggestions = [];
34
+ // 1. Анализ метрик
35
+ suggestions.push(...this.analyzeMetrics(metrics, fileContents));
36
+ // 2. Анализ дублирования
37
+ if (duplication) {
38
+ suggestions.push(...this.analyzeDuplication(duplication));
39
+ }
40
+ // 3. Анализ мёртвого кода
41
+ if (deadCode) {
42
+ suggestions.push(...this.analyzeDeadCode(deadCode));
43
+ }
44
+ // 4. Анализ нарушений правил
45
+ if (rulesResult) {
46
+ suggestions.push(...this.analyzeRuleViolations(rulesResult));
47
+ }
48
+ // 5. Анализ code smells
49
+ suggestions.push(...this.analyzeCodeSmells(fileContents, symbols));
50
+ // 6. Анализ структуры проекта
51
+ suggestions.push(...this.analyzeProjectStructure(graph, symbols));
52
+ // 7. Сортировка по приоритету
53
+ const sorted = this.prioritizeSuggestions(suggestions);
54
+ // 8. Сводка
55
+ const summary = this.calculateSummary(sorted);
56
+ Logger.success(`Generated ${sorted.length} refactoring suggestions`);
57
+ return { suggestions: sorted, summary };
58
+ }
59
+ /**
60
+ * Анализ метрик для предложений
61
+ */
62
+ analyzeMetrics(metrics, _fileContents) {
63
+ const suggestions = [];
64
+ for (const fileMetrics of metrics.files) {
65
+ const filePath = fileMetrics.filePath;
66
+ // Высокая сложность
67
+ if (fileMetrics.complexity.cyclomatic > CODE_SMELLS.longMethod.maxComplexity) {
68
+ suggestions.push({
69
+ id: `complexity-${this.hashString(filePath)}`,
70
+ type: 'reduce-complexity',
71
+ priority: fileMetrics.complexity.cyclomatic > 20 ? 'high' : 'medium',
72
+ effort: 'moderate',
73
+ file: filePath,
74
+ title: 'Reduce cyclomatic complexity',
75
+ description: `File has cyclomatic complexity of ${fileMetrics.complexity.cyclomatic}`,
76
+ rationale: 'High complexity makes code harder to test and maintain',
77
+ estimatedImpact: { complexity: -5, maintainability: -3, testability: -4, readability: -3 }
78
+ });
79
+ }
80
+ // Высокая когнитивная сложность
81
+ if (fileMetrics.complexity.cognitive > 15) {
82
+ suggestions.push({
83
+ id: `cognitive-${this.hashString(filePath)}`,
84
+ type: 'simplify-conditional',
85
+ priority: 'medium',
86
+ effort: 'moderate',
87
+ file: filePath,
88
+ title: 'Simplify cognitive complexity',
89
+ description: `Cognitive complexity is ${fileMetrics.complexity.cognitive}`,
90
+ rationale: 'High cognitive complexity makes code hard to understand',
91
+ estimatedImpact: { complexity: -4, maintainability: -3, testability: -2, readability: -5 }
92
+ });
93
+ }
94
+ // Низкий индекс поддерживаемости
95
+ if (fileMetrics.maintainability < 40) {
96
+ suggestions.push({
97
+ id: `maintain-${this.hashString(filePath)}`,
98
+ type: 'split-file',
99
+ priority: 'high',
100
+ effort: 'hard',
101
+ file: filePath,
102
+ title: 'Improve maintainability',
103
+ description: `Maintainability index is ${fileMetrics.maintainability.toFixed(0)} (critical)`,
104
+ rationale: 'Low maintainability index indicates technical debt',
105
+ estimatedImpact: { complexity: -6, maintainability: -8, testability: -4, readability: -5 }
106
+ });
107
+ }
108
+ // Высокая связанность (coupling)
109
+ if (fileMetrics.coupling.efferentCoupling > 15) {
110
+ suggestions.push({
111
+ id: `coupling-${this.hashString(filePath)}`,
112
+ type: 'fix-coupling',
113
+ priority: 'medium',
114
+ effort: 'moderate',
115
+ file: filePath,
116
+ title: 'Reduce outgoing dependencies',
117
+ description: `File has ${fileMetrics.coupling.efferentCoupling} outgoing dependencies`,
118
+ rationale: 'High coupling makes changes risky and testing difficult',
119
+ estimatedImpact: { complexity: -3, maintainability: -5, testability: -4, readability: -2 }
120
+ });
121
+ }
122
+ // Слишком много строк
123
+ if (fileMetrics.loc.code > CODE_SMELLS.largeClass.maxLines) {
124
+ suggestions.push({
125
+ id: `loc-${this.hashString(filePath)}`,
126
+ type: 'split-file',
127
+ priority: 'medium',
128
+ effort: 'hard',
129
+ file: filePath,
130
+ title: 'Split large file',
131
+ description: `File has ${fileMetrics.loc.code} lines of code`,
132
+ rationale: 'Large files are hard to navigate and understand',
133
+ estimatedImpact: { complexity: -4, maintainability: -4, testability: -3, readability: -6 }
134
+ });
135
+ }
136
+ }
137
+ // Hotspots
138
+ for (const hotspot of metrics.hotspots.slice(0, 5)) {
139
+ suggestions.push({
140
+ id: `hotspot-${this.hashString(hotspot.filePath)}`,
141
+ type: 'reduce-complexity',
142
+ priority: 'high',
143
+ effort: 'hard',
144
+ file: hotspot.filePath,
145
+ title: 'Address code hotspot',
146
+ description: `This file is a hotspot with score ${hotspot.score.toFixed(0)}`,
147
+ rationale: 'Hotspots are high-risk areas that need attention',
148
+ estimatedImpact: { complexity: -6, maintainability: -5, testability: -5, readability: -4 }
149
+ });
150
+ }
151
+ return suggestions;
152
+ }
153
+ /**
154
+ * Анализ дублирования
155
+ */
156
+ analyzeDuplication(duplication) {
157
+ const suggestions = [];
158
+ for (const clone of duplication.clones) {
159
+ const isExact = clone.type === 'exact';
160
+ const multiFile = new Set(clone.instances.map(i => i.file)).size > 1;
161
+ suggestions.push({
162
+ id: `dup-${clone.id}`,
163
+ type: 'eliminate-duplication',
164
+ priority: clone.linesCount > 20 ? 'high' : 'medium',
165
+ effort: multiFile ? 'moderate' : 'easy',
166
+ file: clone.instances[0].file,
167
+ line: clone.instances[0].startLine,
168
+ endLine: clone.instances[0].endLine,
169
+ title: isExact ? 'Extract duplicated code' : 'Refactor similar code',
170
+ description: `${clone.instances.length} instances of ${isExact ? 'identical' : 'similar'} code (${clone.linesCount} lines)`,
171
+ rationale: 'Code duplication increases maintenance cost and bug risk',
172
+ suggestedCode: this.generateExtractionSuggestion(clone),
173
+ relatedFiles: clone.instances.map(i => i.file),
174
+ estimatedImpact: {
175
+ complexity: -2,
176
+ maintainability: -(clone.instances.length * 2),
177
+ testability: -2,
178
+ readability: -3
179
+ }
180
+ });
181
+ }
182
+ return suggestions;
183
+ }
184
+ /**
185
+ * Анализ мёртвого кода
186
+ */
187
+ analyzeDeadCode(deadCode) {
188
+ const suggestions = [];
189
+ // Неиспользуемые экспорты
190
+ for (const unused of deadCode.unusedExports.slice(0, 20)) {
191
+ suggestions.push({
192
+ id: `unused-export-${this.hashString(unused.file + unused.name)}`,
193
+ type: 'remove-dead-code',
194
+ priority: 'low',
195
+ effort: 'trivial',
196
+ file: unused.file,
197
+ line: unused.line,
198
+ title: `Remove unused export: ${unused.name}`,
199
+ description: `${unused.type} "${unused.name}" is exported but never imported`,
200
+ rationale: 'Dead code clutters the codebase and confuses developers',
201
+ estimatedImpact: { complexity: -1, maintainability: -1, testability: 0, readability: -1 }
202
+ });
203
+ }
204
+ // Недостижимый код
205
+ for (const unreachable of deadCode.unreachableCode) {
206
+ suggestions.push({
207
+ id: `unreachable-${this.hashString(unreachable.file + unreachable.startLine)}`,
208
+ type: 'remove-dead-code',
209
+ priority: 'medium',
210
+ effort: 'trivial',
211
+ file: unreachable.file,
212
+ line: unreachable.startLine,
213
+ endLine: unreachable.endLine,
214
+ title: 'Remove unreachable code',
215
+ description: unreachable.reason,
216
+ rationale: 'Unreachable code is dead weight and potentially confusing',
217
+ estimatedImpact: { complexity: -1, maintainability: -1, testability: 0, readability: -2 }
218
+ });
219
+ }
220
+ // Пустые блоки
221
+ for (const empty of deadCode.emptyBlocks) {
222
+ suggestions.push({
223
+ id: `empty-${this.hashString(empty.file + empty.line)}`,
224
+ type: 'remove-dead-code',
225
+ priority: 'low',
226
+ effort: 'trivial',
227
+ file: empty.file,
228
+ line: empty.line,
229
+ title: `Remove empty ${empty.type}${empty.name ? `: ${empty.name}` : ''}`,
230
+ description: `Empty ${empty.type} block does nothing`,
231
+ rationale: 'Empty blocks are usually placeholder code that was never completed',
232
+ estimatedImpact: { complexity: 0, maintainability: -1, testability: 0, readability: -1 }
233
+ });
234
+ }
235
+ // Закомментированный код
236
+ for (const commented of deadCode.commentedCode.slice(0, 10)) {
237
+ suggestions.push({
238
+ id: `commented-${this.hashString(commented.file + commented.startLine)}`,
239
+ type: 'remove-dead-code',
240
+ priority: 'low',
241
+ effort: 'trivial',
242
+ file: commented.file,
243
+ line: commented.startLine,
244
+ endLine: commented.endLine,
245
+ title: 'Remove commented-out code',
246
+ description: `${commented.linesCount} lines of commented code`,
247
+ rationale: 'Commented code should be deleted - use version control instead',
248
+ estimatedImpact: { complexity: 0, maintainability: -1, testability: 0, readability: -2 }
249
+ });
250
+ }
251
+ return suggestions;
252
+ }
253
+ /**
254
+ * Анализ нарушений правил
255
+ */
256
+ analyzeRuleViolations(rulesResult) {
257
+ const suggestions = [];
258
+ for (const violation of rulesResult.violations) {
259
+ const suggestion = this.violationToSuggestion(violation);
260
+ if (suggestion) {
261
+ suggestions.push(suggestion);
262
+ }
263
+ }
264
+ return suggestions;
265
+ }
266
+ /**
267
+ * Конвертация нарушения в предложение
268
+ */
269
+ violationToSuggestion(violation) {
270
+ const baseImpact = { complexity: -2, maintainability: -3, testability: -2, readability: -2 };
271
+ // Use ruleId to determine refactoring type
272
+ const ruleType = violation.ruleId?.split('-')[0] || '';
273
+ switch (ruleType) {
274
+ case 'no-circular':
275
+ return {
276
+ id: `circular-${this.hashString(violation.file)}`,
277
+ type: 'fix-coupling',
278
+ priority: 'high',
279
+ effort: 'hard',
280
+ file: violation.file,
281
+ title: 'Break circular dependency',
282
+ description: violation.message,
283
+ rationale: 'Circular dependencies cause build issues and tight coupling',
284
+ estimatedImpact: { ...baseImpact, complexity: -5 }
285
+ };
286
+ case 'layer-boundary':
287
+ return {
288
+ id: `layer-${this.hashString(violation.file + violation.line)}`,
289
+ type: 'move-to-layer',
290
+ priority: 'medium',
291
+ effort: 'moderate',
292
+ file: violation.file,
293
+ line: violation.line,
294
+ title: 'Fix layer boundary violation',
295
+ description: violation.message,
296
+ rationale: 'Layer violations break architectural integrity',
297
+ estimatedImpact: baseImpact
298
+ };
299
+ case 'max-dependencies':
300
+ return {
301
+ id: `deps-${this.hashString(violation.file)}`,
302
+ type: 'split-file',
303
+ priority: 'medium',
304
+ effort: 'hard',
305
+ file: violation.file,
306
+ title: 'Reduce dependencies',
307
+ description: violation.message,
308
+ rationale: 'Too many dependencies indicates the file does too much',
309
+ estimatedImpact: { ...baseImpact, maintainability: -5 }
310
+ };
311
+ case 'no-import':
312
+ return {
313
+ id: `import-${this.hashString(violation.file + violation.line)}`,
314
+ type: 'move-method',
315
+ priority: 'medium',
316
+ effort: 'easy',
317
+ file: violation.file,
318
+ line: violation.line,
319
+ title: 'Remove forbidden import',
320
+ description: violation.message,
321
+ rationale: 'This import violates architectural rules',
322
+ estimatedImpact: baseImpact
323
+ };
324
+ default:
325
+ return null;
326
+ }
327
+ }
328
+ /**
329
+ * Анализ code smells
330
+ */
331
+ analyzeCodeSmells(fileContents, _symbols) {
332
+ const suggestions = [];
333
+ for (const [filePath, content] of fileContents) {
334
+ const lines = content.split('\n');
335
+ // Long parameter lists
336
+ const funcPattern = /(?:function|const|let|var)\s+(\w+)\s*(?:=\s*(?:async\s*)?\(|\()([^)]*)\)/g;
337
+ let match;
338
+ while ((match = funcPattern.exec(content)) !== null) {
339
+ const params = match[2].split(',').filter(p => p.trim());
340
+ if (params.length > CODE_SMELLS.longParameterList.maxParams) {
341
+ const line = content.substring(0, match.index).split('\n').length;
342
+ suggestions.push({
343
+ id: `params-${this.hashString(filePath + match[1])}`,
344
+ type: 'introduce-parameter-object',
345
+ priority: 'low',
346
+ effort: 'easy',
347
+ file: filePath,
348
+ line,
349
+ title: `Introduce parameter object for ${match[1]}`,
350
+ description: `Function has ${params.length} parameters`,
351
+ rationale: 'Long parameter lists are hard to remember and use correctly',
352
+ estimatedImpact: { complexity: -1, maintainability: -2, testability: -1, readability: -3 }
353
+ });
354
+ }
355
+ }
356
+ // Magic numbers
357
+ for (let i = 0; i < lines.length; i++) {
358
+ const line = lines[i];
359
+ // Пропускаем комментарии и импорты
360
+ if (line.trim().startsWith('//') || line.includes('import'))
361
+ continue;
362
+ const magicMatch = line.match(/(?<![.\d])\b(\d{2,})\b(?!\.\d)/);
363
+ if (magicMatch && !line.includes('const') && !line.includes('=')) {
364
+ const num = magicMatch[1];
365
+ // Исключаем общие значения
366
+ if (!['100', '1000', '10', '60', '24', '365'].includes(num)) {
367
+ suggestions.push({
368
+ id: `magic-${this.hashString(filePath + i + num)}`,
369
+ type: 'extract-constant',
370
+ priority: 'low',
371
+ effort: 'trivial',
372
+ file: filePath,
373
+ line: i + 1,
374
+ title: `Extract magic number ${num}`,
375
+ description: `Magic number ${num} should be a named constant`,
376
+ rationale: 'Magic numbers are hard to understand and maintain',
377
+ estimatedImpact: { complexity: 0, maintainability: -1, testability: 0, readability: -2 }
378
+ });
379
+ }
380
+ }
381
+ }
382
+ // Deep nesting
383
+ let maxNesting = 0;
384
+ let currentNesting = 0;
385
+ let deepestLine = 0;
386
+ for (let i = 0; i < lines.length; i++) {
387
+ const opens = (lines[i].match(/{/g) || []).length;
388
+ const closes = (lines[i].match(/}/g) || []).length;
389
+ currentNesting += opens - closes;
390
+ if (currentNesting > maxNesting) {
391
+ maxNesting = currentNesting;
392
+ deepestLine = i + 1;
393
+ }
394
+ }
395
+ if (maxNesting > CODE_SMELLS.deepNesting.maxDepth) {
396
+ suggestions.push({
397
+ id: `nesting-${this.hashString(filePath)}`,
398
+ type: 'extract-method',
399
+ priority: 'medium',
400
+ effort: 'moderate',
401
+ file: filePath,
402
+ line: deepestLine,
403
+ title: 'Reduce nesting depth',
404
+ description: `Maximum nesting depth is ${maxNesting}`,
405
+ rationale: 'Deep nesting makes code hard to follow and test',
406
+ estimatedImpact: { complexity: -4, maintainability: -3, testability: -3, readability: -5 }
407
+ });
408
+ }
409
+ // Switch with many cases
410
+ const switchPattern = /switch\s*\([^)]+\)\s*{([^}]+(?:{[^}]*}[^}]*)*)}/g;
411
+ while ((match = switchPattern.exec(content)) !== null) {
412
+ const cases = (match[1].match(/case\s+/g) || []).length;
413
+ if (cases > CODE_SMELLS.switchStatement.maxCases) {
414
+ const line = content.substring(0, match.index).split('\n').length;
415
+ suggestions.push({
416
+ id: `switch-${this.hashString(filePath + line)}`,
417
+ type: 'simplify-conditional',
418
+ priority: 'medium',
419
+ effort: 'moderate',
420
+ file: filePath,
421
+ line,
422
+ title: 'Replace switch with polymorphism',
423
+ description: `Switch statement has ${cases} cases`,
424
+ rationale: 'Large switches often indicate missing abstraction',
425
+ suggestedCode: '// Consider using a Map<string, Handler> or Strategy pattern',
426
+ estimatedImpact: { complexity: -3, maintainability: -4, testability: -3, readability: -2 }
427
+ });
428
+ }
429
+ }
430
+ }
431
+ return suggestions;
432
+ }
433
+ /**
434
+ * Анализ структуры проекта
435
+ */
436
+ analyzeProjectStructure(graph, symbols) {
437
+ const suggestions = [];
438
+ // God classes (слишком много зависимостей)
439
+ const dependencyCount = new Map();
440
+ for (const [file, edges] of graph.edges) {
441
+ dependencyCount.set(file, edges.length);
442
+ }
443
+ for (const [file, count] of dependencyCount) {
444
+ if (count > CODE_SMELLS.godClass.maxDependencies) {
445
+ suggestions.push({
446
+ id: `god-${this.hashString(file)}`,
447
+ type: 'extract-class',
448
+ priority: 'high',
449
+ effort: 'hard',
450
+ file,
451
+ title: 'Split god class/module',
452
+ description: `File has ${count} dependencies - likely doing too much`,
453
+ rationale: 'God classes violate Single Responsibility Principle',
454
+ estimatedImpact: { complexity: -6, maintainability: -7, testability: -5, readability: -4 }
455
+ });
456
+ }
457
+ }
458
+ // Файлы с множеством экспортов разных типов
459
+ const fileExports = new Map();
460
+ for (const [, symbol] of symbols) {
461
+ if (!symbol.exports)
462
+ continue;
463
+ const file = symbol.location.filePath;
464
+ const current = fileExports.get(file) || { functions: 0, classes: 0, types: 0 };
465
+ if (symbol.kind === SymbolKind.Function)
466
+ current.functions++;
467
+ else if (symbol.kind === SymbolKind.Class)
468
+ current.classes++;
469
+ else if (symbol.kind === SymbolKind.Interface || symbol.kind === SymbolKind.Type)
470
+ current.types++;
471
+ fileExports.set(file, current);
472
+ }
473
+ for (const [file, exports] of fileExports) {
474
+ const total = exports.functions + exports.classes + exports.types;
475
+ if (total > 10 && exports.classes > 0 && exports.functions > 3) {
476
+ suggestions.push({
477
+ id: `mixed-${this.hashString(file)}`,
478
+ type: 'extract-module',
479
+ priority: 'medium',
480
+ effort: 'moderate',
481
+ file,
482
+ title: 'Separate concerns into modules',
483
+ description: `File exports ${exports.classes} classes, ${exports.functions} functions, ${exports.types} types`,
484
+ rationale: 'Mixed exports suggest the file has multiple responsibilities',
485
+ estimatedImpact: { complexity: -3, maintainability: -4, testability: -3, readability: -3 }
486
+ });
487
+ }
488
+ }
489
+ return suggestions;
490
+ }
491
+ /**
492
+ * Генерация кода для извлечения
493
+ */
494
+ generateExtractionSuggestion(clone) {
495
+ const sample = clone.instances[0].code;
496
+ const lines = sample.split('\n');
497
+ // Пытаемся определить, что это за код
498
+ const hasReturn = sample.includes('return');
499
+ const hasAsync = sample.includes('await');
500
+ let suggestion = `// Extract to a shared function:\n`;
501
+ suggestion += `${hasAsync ? 'async ' : ''}function extractedFunction(/* params */)`;
502
+ suggestion += hasReturn ? ': ReturnType' : ': void';
503
+ suggestion += ` {\n`;
504
+ suggestion += lines.map(l => ` ${l}`).join('\n');
505
+ suggestion += `\n}`;
506
+ return suggestion;
507
+ }
508
+ /**
509
+ * Приоритизация предложений
510
+ */
511
+ prioritizeSuggestions(suggestions) {
512
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
513
+ const effortOrder = { trivial: 0, easy: 1, moderate: 2, hard: 3 };
514
+ return suggestions.sort((a, b) => {
515
+ // Сначала по приоритету
516
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
517
+ if (priorityDiff !== 0)
518
+ return priorityDiff;
519
+ // При равном приоритете - по усилиям (проще первым)
520
+ const effortDiff = effortOrder[a.effort] - effortOrder[b.effort];
521
+ if (effortDiff !== 0)
522
+ return effortDiff;
523
+ // При равных усилиях - по общему импакту
524
+ const impactA = Math.abs(a.estimatedImpact.complexity + a.estimatedImpact.maintainability);
525
+ const impactB = Math.abs(b.estimatedImpact.complexity + b.estimatedImpact.maintainability);
526
+ return impactB - impactA;
527
+ });
528
+ }
529
+ /**
530
+ * Расчёт сводки
531
+ */
532
+ calculateSummary(suggestions) {
533
+ const byPriority = { critical: 0, high: 0, medium: 0, low: 0 };
534
+ const byType = {};
535
+ let totalEffortHours = 0;
536
+ let totalComplexityReduction = 0;
537
+ let totalMaintainabilityGain = 0;
538
+ for (const s of suggestions) {
539
+ byPriority[s.priority]++;
540
+ byType[s.type] = (byType[s.type] || 0) + 1;
541
+ // Оценка усилий в часах
542
+ const effortHours = { trivial: 0.25, easy: 0.5, moderate: 2, hard: 4 };
543
+ totalEffortHours += effortHours[s.effort];
544
+ totalComplexityReduction += Math.abs(s.estimatedImpact.complexity);
545
+ totalMaintainabilityGain += Math.abs(s.estimatedImpact.maintainability);
546
+ }
547
+ // Форматирование времени
548
+ let estimatedTotalEffort;
549
+ if (totalEffortHours < 8) {
550
+ estimatedTotalEffort = `${totalEffortHours.toFixed(1)} hours`;
551
+ }
552
+ else {
553
+ const days = totalEffortHours / 8;
554
+ estimatedTotalEffort = `${days.toFixed(1)} days`;
555
+ }
556
+ return {
557
+ totalSuggestions: suggestions.length,
558
+ byPriority,
559
+ byType: byType,
560
+ estimatedTotalEffort,
561
+ potentialImprovements: {
562
+ complexityReduction: totalComplexityReduction,
563
+ maintainabilityGain: totalMaintainabilityGain
564
+ }
565
+ };
566
+ }
567
+ /**
568
+ * Хеширование строки
569
+ */
570
+ hashString(str) {
571
+ let hash = 0;
572
+ for (let i = 0; i < str.length; i++) {
573
+ const char = str.charCodeAt(i);
574
+ hash = ((hash << 5) - hash) + char;
575
+ hash = hash & hash;
576
+ }
577
+ return Math.abs(hash).toString(16).substring(0, 8);
578
+ }
579
+ }
580
+ //# sourceMappingURL=index.js.map