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,249 @@
1
+ /**
2
+ * Architecture Knowledge Layer
3
+ *
4
+ * Слой архитектурных знаний:
5
+ * - Bounded Contexts (границы контекстов)
6
+ * - Domain Entities (доменные сущности)
7
+ * - Architectural Rules (архитектурные правила)
8
+ * - Invariants (инварианты)
9
+ */
10
+ import { Logger } from '../utils/logger.js';
11
+ import { readFile, writeFile } from 'fs/promises';
12
+ export class ArchitectureKnowledge {
13
+ model;
14
+ configPath;
15
+ constructor(configPath = '.aiarhitector/architecture.json') {
16
+ this.configPath = configPath;
17
+ this.model = {
18
+ boundedContexts: [],
19
+ entities: [],
20
+ rules: [],
21
+ invariants: []
22
+ };
23
+ }
24
+ async load() {
25
+ try {
26
+ const content = await readFile(this.configPath, 'utf-8');
27
+ this.model = JSON.parse(content);
28
+ Logger.success('Architecture model loaded');
29
+ }
30
+ catch (error) {
31
+ Logger.warn('No architecture model found, using defaults');
32
+ this.initializeDefaults();
33
+ }
34
+ }
35
+ async save() {
36
+ try {
37
+ const content = JSON.stringify(this.model, null, 2);
38
+ await writeFile(this.configPath, content, 'utf-8');
39
+ Logger.success('Architecture model saved');
40
+ }
41
+ catch (error) {
42
+ Logger.error('Failed to save architecture model', error);
43
+ throw error;
44
+ }
45
+ }
46
+ getModel() {
47
+ return this.model;
48
+ }
49
+ addBoundedContext(context) {
50
+ this.model.boundedContexts.push(context);
51
+ Logger.success(`Added bounded context: ${context.name}`);
52
+ }
53
+ addEntity(entity) {
54
+ this.model.entities.push(entity);
55
+ Logger.success(`Added entity: ${entity.name}`);
56
+ }
57
+ addRule(rule) {
58
+ this.model.rules.push(rule);
59
+ Logger.success(`Added rule: ${rule.description}`);
60
+ }
61
+ addInvariant(invariant) {
62
+ this.model.invariants.push(invariant);
63
+ Logger.success(`Added invariant: ${invariant.description}`);
64
+ }
65
+ validateArchitecture(context) {
66
+ Logger.progress('Validating architecture...');
67
+ const violations = [];
68
+ for (const rule of this.model.rules) {
69
+ const ruleViolations = rule.validator(context);
70
+ violations.push(...ruleViolations);
71
+ }
72
+ violations.push(...this.validateBoundedContexts(context));
73
+ violations.push(...this.validateInvariants(context));
74
+ if (violations.length > 0) {
75
+ Logger.warn(`Found ${violations.length} architecture violations`);
76
+ }
77
+ else {
78
+ Logger.success('Architecture validation passed');
79
+ }
80
+ return violations;
81
+ }
82
+ validateBoundedContexts(context) {
83
+ const violations = [];
84
+ for (const boundedContext of this.model.boundedContexts) {
85
+ for (const prohibited of boundedContext.prohibitedDependencies) {
86
+ const hasProhibitedDep = this.checkDependency(context.graph, boundedContext.modules, prohibited);
87
+ if (hasProhibitedDep) {
88
+ violations.push({
89
+ rule: `bounded-context-isolation:${boundedContext.name}`,
90
+ severity: 'error',
91
+ message: `Bounded context "${boundedContext.name}" has prohibited dependency on "${prohibited}"`,
92
+ suggestion: `Remove dependency or refactor to use proper interface`
93
+ });
94
+ }
95
+ }
96
+ }
97
+ return violations;
98
+ }
99
+ validateInvariants(_context) {
100
+ const violations = [];
101
+ for (const invariant of this.model.invariants) {
102
+ Logger.info(`Checking invariant: ${invariant.description}`);
103
+ }
104
+ return violations;
105
+ }
106
+ checkDependency(graph, modules, targetModule) {
107
+ for (const module of modules) {
108
+ const edges = graph.edges.get(module) || [];
109
+ for (const edge of edges) {
110
+ const targetNode = graph.nodes.get(edge.to);
111
+ if (targetNode?.filePath.includes(targetModule)) {
112
+ return true;
113
+ }
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+ getBoundedContext(name) {
119
+ return this.model.boundedContexts.find(c => c.name === name);
120
+ }
121
+ getEntity(name) {
122
+ return this.model.entities.find(e => e.name === name);
123
+ }
124
+ getContextForFile(filePath) {
125
+ for (const context of this.model.boundedContexts) {
126
+ for (const module of context.modules) {
127
+ if (filePath.includes(module)) {
128
+ return context;
129
+ }
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+ initializeDefaults() {
135
+ this.model = {
136
+ boundedContexts: [
137
+ {
138
+ id: 'core',
139
+ name: 'Core',
140
+ description: 'Core domain logic',
141
+ modules: ['src/core'],
142
+ dependencies: [],
143
+ prohibitedDependencies: ['src/ui', 'src/external']
144
+ },
145
+ {
146
+ id: 'ui',
147
+ name: 'UI',
148
+ description: 'User interface layer',
149
+ modules: ['src/ui'],
150
+ dependencies: ['src/core'],
151
+ prohibitedDependencies: ['src/external']
152
+ },
153
+ {
154
+ id: 'external',
155
+ name: 'External',
156
+ description: 'External integrations',
157
+ modules: ['src/external'],
158
+ dependencies: ['src/core'],
159
+ prohibitedDependencies: ['src/ui']
160
+ }
161
+ ],
162
+ entities: [],
163
+ rules: this.createDefaultRules(),
164
+ invariants: []
165
+ };
166
+ }
167
+ createDefaultRules() {
168
+ return [
169
+ {
170
+ id: 'no-circular-deps',
171
+ description: 'No circular dependencies allowed',
172
+ type: 'dependency',
173
+ severity: 'error',
174
+ validator: (context) => {
175
+ return this.detectCircularDependencies(context.graph);
176
+ }
177
+ },
178
+ {
179
+ id: 'naming-convention',
180
+ description: 'Follow naming conventions',
181
+ type: 'naming',
182
+ severity: 'warning',
183
+ pattern: '^[A-Z][a-zA-Z0-9]*$',
184
+ validator: (_context) => {
185
+ return [];
186
+ }
187
+ }
188
+ ];
189
+ }
190
+ detectCircularDependencies(graph) {
191
+ const violations = [];
192
+ const visited = new Set();
193
+ const recursionStack = new Set();
194
+ const hasCycle = (nodeId, path) => {
195
+ visited.add(nodeId);
196
+ recursionStack.add(nodeId);
197
+ const edges = graph.edges.get(nodeId) || [];
198
+ for (const edge of edges) {
199
+ if (!visited.has(edge.to)) {
200
+ if (hasCycle(edge.to, [...path, nodeId])) {
201
+ return true;
202
+ }
203
+ }
204
+ else if (recursionStack.has(edge.to)) {
205
+ violations.push({
206
+ rule: 'no-circular-deps',
207
+ severity: 'error',
208
+ message: `Circular dependency detected: ${[...path, nodeId, edge.to].join(' -> ')}`,
209
+ suggestion: 'Refactor to break the circular dependency'
210
+ });
211
+ return true;
212
+ }
213
+ }
214
+ recursionStack.delete(nodeId);
215
+ return false;
216
+ };
217
+ for (const nodeId of graph.nodes.keys()) {
218
+ if (!visited.has(nodeId)) {
219
+ hasCycle(nodeId, []);
220
+ }
221
+ }
222
+ return violations;
223
+ }
224
+ generateReport() {
225
+ let report = '# Architecture Report\n\n';
226
+ report += '## Bounded Contexts\n\n';
227
+ for (const context of this.model.boundedContexts) {
228
+ report += `### ${context.name}\n`;
229
+ report += `${context.description}\n\n`;
230
+ report += `**Modules:** ${context.modules.join(', ')}\n`;
231
+ report += `**Dependencies:** ${context.dependencies.join(', ') || 'None'}\n`;
232
+ report += `**Prohibited:** ${context.prohibitedDependencies.join(', ') || 'None'}\n\n`;
233
+ }
234
+ report += '## Domain Entities\n\n';
235
+ for (const entity of this.model.entities) {
236
+ report += `### ${entity.name}\n`;
237
+ report += `**Context:** ${entity.context}\n`;
238
+ report += `**Properties:** ${entity.properties.length}\n`;
239
+ report += `**Relationships:** ${entity.relationships.length}\n\n`;
240
+ }
241
+ report += '## Rules\n\n';
242
+ for (const rule of this.model.rules) {
243
+ report += `- **${rule.description}** (${rule.severity})\n`;
244
+ }
245
+ return report;
246
+ }
247
+ }
248
+ export * from '../types/index.js';
249
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * ArchiCore CLI - Analyzer Commands
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function registerAnalyzerCommands(program: Command): void;
6
+ //# sourceMappingURL=analyzers.d.ts.map
@@ -0,0 +1,431 @@
1
+ /**
2
+ * ArchiCore CLI - Analyzer Commands
3
+ */
4
+ import { loadConfig } from '../utils/config.js';
5
+ import { colors, icons, createSpinner, printSuccess, printError, printSection, printKeyValue, metricsTable, formatSeverity, } from '../ui/index.js';
6
+ async function getProjectId() {
7
+ const config = await loadConfig();
8
+ if (!config.activeProjectId) {
9
+ throw new Error('No project selected. Use "archicore projects select <id>" first.');
10
+ }
11
+ return config.activeProjectId;
12
+ }
13
+ async function fetchAnalysis(endpoint, projectId) {
14
+ const config = await loadConfig();
15
+ const id = projectId || await getProjectId();
16
+ const response = await fetch(`${config.serverUrl}/api/projects/${id}/${endpoint}`);
17
+ if (!response.ok) {
18
+ const error = await response.json();
19
+ throw new Error(error.error || `Failed to run ${endpoint}`);
20
+ }
21
+ return response.json();
22
+ }
23
+ export function registerAnalyzerCommands(program) {
24
+ // Dead Code
25
+ program
26
+ .command('dead-code')
27
+ .description('Find dead/unused code')
28
+ .option('-p, --project <id>', 'Project ID')
29
+ .option('--json', 'Output as JSON')
30
+ .action(async (options) => {
31
+ const spinner = createSpinner('Analyzing dead code...').start();
32
+ try {
33
+ const data = await fetchAnalysis('dead-code', options.project);
34
+ spinner.succeed('Analysis complete');
35
+ if (options.json) {
36
+ console.log(JSON.stringify(data, null, 2));
37
+ return;
38
+ }
39
+ const result = data.deadCode;
40
+ printSection('Dead Code Analysis');
41
+ // Summary
42
+ console.log(colors.highlight(' Summary'));
43
+ printKeyValue(' Unused Exports', String(result.unusedExports?.length || 0));
44
+ printKeyValue(' Unused Variables', String(result.unusedVariables?.length || 0));
45
+ printKeyValue(' Unreachable Code', String(result.unreachableCode?.length || 0));
46
+ printKeyValue(' Empty Blocks', String(result.emptyBlocks?.length || 0));
47
+ printKeyValue(' Commented Code', String(result.commentedCode?.length || 0));
48
+ console.log();
49
+ if (result.summary) {
50
+ printKeyValue(' Total Issues', String(result.summary.totalIssues || 0));
51
+ printKeyValue(' Cleanup Time', result.summary.estimatedCleanupTime || 'N/A');
52
+ }
53
+ // Unused Exports
54
+ if (result.unusedExports?.length > 0) {
55
+ printSection('Unused Exports');
56
+ for (const item of result.unusedExports.slice(0, 15)) {
57
+ console.log(` ${colors.error(icons.error)} ${colors.code(item.name)}`);
58
+ console.log(` ${colors.dim(item.filePath)}:${item.line || ''}`);
59
+ }
60
+ if (result.unusedExports.length > 15) {
61
+ console.log(colors.muted(` ... and ${result.unusedExports.length - 15} more`));
62
+ }
63
+ }
64
+ // Unused Variables
65
+ if (result.unusedVariables?.length > 0) {
66
+ printSection('Unused Variables');
67
+ for (const item of result.unusedVariables.slice(0, 15)) {
68
+ console.log(` ${colors.warning(icons.warning)} ${colors.code(item.name)}`);
69
+ console.log(` ${colors.dim(item.filePath)}:${item.line || ''}`);
70
+ }
71
+ if (result.unusedVariables.length > 15) {
72
+ console.log(colors.muted(` ... and ${result.unusedVariables.length - 15} more`));
73
+ }
74
+ }
75
+ console.log();
76
+ }
77
+ catch (error) {
78
+ spinner.fail('Analysis failed');
79
+ printError(String(error));
80
+ process.exit(1);
81
+ }
82
+ });
83
+ // Duplication
84
+ program
85
+ .command('duplication')
86
+ .description('Find code duplication')
87
+ .option('-p, --project <id>', 'Project ID')
88
+ .option('--min-lines <n>', 'Minimum lines to consider', '5')
89
+ .option('--json', 'Output as JSON')
90
+ .action(async (options) => {
91
+ const spinner = createSpinner('Analyzing duplication...').start();
92
+ try {
93
+ const data = await fetchAnalysis('duplication', options.project);
94
+ spinner.succeed('Analysis complete');
95
+ if (options.json) {
96
+ console.log(JSON.stringify(data, null, 2));
97
+ return;
98
+ }
99
+ const result = data.duplication;
100
+ printSection('Code Duplication Analysis');
101
+ printKeyValue(' Duplication', `${(result.duplicationPercentage || 0).toFixed(1)}%`);
102
+ printKeyValue(' Clone Groups', String(result.clones?.length || 0));
103
+ console.log();
104
+ if (result.clones?.length > 0) {
105
+ printSection('Clone Groups');
106
+ for (const clone of result.clones.slice(0, 10)) {
107
+ console.log(` ${colors.warning(icons.warning)} ${colors.highlight(`${clone.lines || '?'} lines`)} duplicated in ${clone.instances?.length || 0} locations`);
108
+ for (const instance of (clone.instances || []).slice(0, 3)) {
109
+ console.log(` ${colors.dim(instance.filePath)}:${instance.startLine}-${instance.endLine}`);
110
+ }
111
+ if ((clone.instances?.length || 0) > 3) {
112
+ console.log(colors.muted(` ... and ${clone.instances.length - 3} more locations`));
113
+ }
114
+ console.log();
115
+ }
116
+ if (result.clones.length > 10) {
117
+ console.log(colors.muted(` ... and ${result.clones.length - 10} more clone groups`));
118
+ }
119
+ }
120
+ console.log();
121
+ }
122
+ catch (error) {
123
+ spinner.fail('Analysis failed');
124
+ printError(String(error));
125
+ process.exit(1);
126
+ }
127
+ });
128
+ // Security
129
+ program
130
+ .command('security')
131
+ .description('Security vulnerability analysis')
132
+ .option('-p, --project <id>', 'Project ID')
133
+ .option('--severity <level>', 'Minimum severity (low|medium|high|critical)')
134
+ .option('--json', 'Output as JSON')
135
+ .action(async (options) => {
136
+ const spinner = createSpinner('Analyzing security...').start();
137
+ try {
138
+ const data = await fetchAnalysis('security', options.project);
139
+ spinner.succeed('Analysis complete');
140
+ if (options.json) {
141
+ console.log(JSON.stringify(data, null, 2));
142
+ return;
143
+ }
144
+ const result = data.security;
145
+ let vulnerabilities = result.vulnerabilities || [];
146
+ // Filter by severity if specified
147
+ if (options.severity) {
148
+ const levels = ['low', 'medium', 'high', 'critical'];
149
+ const minLevel = levels.indexOf(options.severity.toLowerCase());
150
+ if (minLevel >= 0) {
151
+ vulnerabilities = vulnerabilities.filter((v) => levels.indexOf(v.severity?.toLowerCase() || 'low') >= minLevel);
152
+ }
153
+ }
154
+ printSection('Security Analysis');
155
+ // Count by severity
156
+ const counts = {
157
+ critical: vulnerabilities.filter((v) => v.severity === 'critical').length,
158
+ high: vulnerabilities.filter((v) => v.severity === 'high').length,
159
+ medium: vulnerabilities.filter((v) => v.severity === 'medium').length,
160
+ low: vulnerabilities.filter((v) => v.severity === 'low').length,
161
+ };
162
+ console.log(` ${colors.critical(`${icons.severityCritical} Critical: ${counts.critical}`)}`);
163
+ console.log(` ${colors.high(`${icons.severityHigh} High: ${counts.high}`)}`);
164
+ console.log(` ${colors.medium(`${icons.severityMedium} Medium: ${counts.medium}`)}`);
165
+ console.log(` ${colors.low(`${icons.severityLow} Low: ${counts.low}`)}`);
166
+ console.log();
167
+ if (vulnerabilities.length > 0) {
168
+ printSection('Vulnerabilities');
169
+ for (const vuln of vulnerabilities.slice(0, 20)) {
170
+ const severityMap = {
171
+ critical: colors.critical,
172
+ high: colors.high,
173
+ medium: colors.medium,
174
+ low: colors.low,
175
+ };
176
+ const severityColor = severityMap[vuln.severity?.toLowerCase() || 'low'] || colors.muted;
177
+ console.log(` ${severityColor(formatSeverity(vuln.severity))} ${colors.highlight(vuln.type || vuln.category)}`);
178
+ console.log(` ${vuln.description || vuln.message}`);
179
+ console.log(` ${colors.dim(vuln.filePath)}:${vuln.line || ''}`);
180
+ if (vuln.remediation) {
181
+ console.log(` ${colors.info(icons.info)} ${vuln.remediation}`);
182
+ }
183
+ console.log();
184
+ }
185
+ if (vulnerabilities.length > 20) {
186
+ console.log(colors.muted(` ... and ${vulnerabilities.length - 20} more vulnerabilities`));
187
+ }
188
+ }
189
+ else {
190
+ printSuccess('No vulnerabilities found!');
191
+ }
192
+ console.log();
193
+ }
194
+ catch (error) {
195
+ spinner.fail('Analysis failed');
196
+ printError(String(error));
197
+ process.exit(1);
198
+ }
199
+ });
200
+ // Metrics
201
+ program
202
+ .command('metrics')
203
+ .description('Calculate code metrics')
204
+ .option('-p, --project <id>', 'Project ID')
205
+ .option('-f, --file <path>', 'Analyze specific file')
206
+ .option('--json', 'Output as JSON')
207
+ .action(async (options) => {
208
+ const spinner = createSpinner('Calculating metrics...').start();
209
+ try {
210
+ const data = await fetchAnalysis('metrics', options.project);
211
+ spinner.succeed('Metrics calculated');
212
+ if (options.json) {
213
+ console.log(JSON.stringify(data, null, 2));
214
+ return;
215
+ }
216
+ const result = data.metrics;
217
+ printSection('Code Metrics');
218
+ // Summary
219
+ if (result.summary) {
220
+ console.log(colors.highlight(' Summary'));
221
+ printKeyValue(' Total Files', String(result.summary.totalFiles || 0));
222
+ printKeyValue(' Total Lines', String(result.summary.totalLines || 0));
223
+ printKeyValue(' Avg Complexity', String((result.summary.avgComplexity || 0).toFixed(2)));
224
+ printKeyValue(' Avg Maintainability', String((result.summary.avgMaintainability || 0).toFixed(1)));
225
+ console.log();
226
+ }
227
+ // File metrics
228
+ if (result.files?.length > 0) {
229
+ printSection('File Metrics');
230
+ const metrics = result.files.map((f) => ({
231
+ file: f.filePath || f.file,
232
+ complexity: f.complexity || f.cyclomaticComplexity || 0,
233
+ maintainability: f.maintainability || f.maintainabilityIndex || 0,
234
+ linesOfCode: f.linesOfCode || f.loc || 0,
235
+ }));
236
+ // Sort by complexity descending
237
+ metrics.sort((a, b) => b.complexity - a.complexity);
238
+ console.log(metricsTable(metrics.slice(0, 20)));
239
+ if (metrics.length > 20) {
240
+ console.log(colors.muted(` ... and ${metrics.length - 20} more files`));
241
+ }
242
+ }
243
+ console.log();
244
+ }
245
+ catch (error) {
246
+ spinner.fail('Analysis failed');
247
+ printError(String(error));
248
+ process.exit(1);
249
+ }
250
+ });
251
+ // Rules
252
+ program
253
+ .command('rules')
254
+ .description('Check architectural rules')
255
+ .option('-p, --project <id>', 'Project ID')
256
+ .option('--json', 'Output as JSON')
257
+ .action(async (options) => {
258
+ const spinner = createSpinner('Checking rules...').start();
259
+ try {
260
+ const data = await fetchAnalysis('rules', options.project);
261
+ spinner.succeed('Rules checked');
262
+ if (options.json) {
263
+ console.log(JSON.stringify(data, null, 2));
264
+ return;
265
+ }
266
+ const result = data.rules;
267
+ const violations = result.violations || [];
268
+ printSection('Architectural Rules');
269
+ printKeyValue(' Rules Checked', String(result.rulesChecked || 0));
270
+ printKeyValue(' Violations', String(violations.length));
271
+ printKeyValue(' Status', violations.length === 0
272
+ ? colors.success('PASS')
273
+ : colors.error('FAIL'));
274
+ console.log();
275
+ if (violations.length > 0) {
276
+ printSection('Violations');
277
+ for (const v of violations.slice(0, 20)) {
278
+ const severityColor = v.severity === 'error' ? colors.error : colors.warning;
279
+ console.log(` ${severityColor(v.severity?.toUpperCase() || 'WARNING')} ${colors.highlight(v.rule || v.type)}`);
280
+ console.log(` ${v.message || v.description}`);
281
+ if (v.filePath) {
282
+ console.log(` ${colors.dim(v.filePath)}:${v.line || ''}`);
283
+ }
284
+ console.log();
285
+ }
286
+ if (violations.length > 20) {
287
+ console.log(colors.muted(` ... and ${violations.length - 20} more violations`));
288
+ }
289
+ }
290
+ else {
291
+ printSuccess('All rules pass!');
292
+ }
293
+ console.log();
294
+ }
295
+ catch (error) {
296
+ spinner.fail('Analysis failed');
297
+ printError(String(error));
298
+ process.exit(1);
299
+ }
300
+ });
301
+ // Refactoring
302
+ program
303
+ .command('refactoring')
304
+ .description('Get refactoring suggestions')
305
+ .option('-p, --project <id>', 'Project ID')
306
+ .option('--json', 'Output as JSON')
307
+ .action(async (options) => {
308
+ const spinner = createSpinner('Analyzing refactoring opportunities...').start();
309
+ try {
310
+ const data = await fetchAnalysis('refactoring', options.project);
311
+ spinner.succeed('Analysis complete');
312
+ if (options.json) {
313
+ console.log(JSON.stringify(data, null, 2));
314
+ return;
315
+ }
316
+ const suggestions = data.refactoring?.suggestions || data.suggestions || [];
317
+ printSection('Refactoring Suggestions');
318
+ printKeyValue(' Total Suggestions', String(suggestions.length));
319
+ console.log();
320
+ if (suggestions.length > 0) {
321
+ // Group by type
322
+ const byType = {};
323
+ for (const s of suggestions) {
324
+ const type = s.type || 'other';
325
+ if (!byType[type])
326
+ byType[type] = [];
327
+ byType[type].push(s);
328
+ }
329
+ for (const [type, items] of Object.entries(byType)) {
330
+ console.log(colors.secondary(` ${type.replace(/-/g, ' ').toUpperCase()} (${items.length})`));
331
+ console.log();
332
+ for (const item of items.slice(0, 5)) {
333
+ const priority = item.priority || 'medium';
334
+ const priorityColor = priority === 'high' ? colors.high : priority === 'low' ? colors.low : colors.medium;
335
+ console.log(` ${priorityColor(icons.bullet)} ${item.description || item.message}`);
336
+ if (item.filePath) {
337
+ console.log(` ${colors.dim(item.filePath)}:${item.line || ''}`);
338
+ }
339
+ if (item.effort) {
340
+ console.log(` ${colors.muted(`Effort: ${item.effort}`)}`);
341
+ }
342
+ }
343
+ if (items.length > 5) {
344
+ console.log(colors.muted(` ... and ${items.length - 5} more`));
345
+ }
346
+ console.log();
347
+ }
348
+ }
349
+ else {
350
+ printSuccess('No refactoring suggestions - code looks good!');
351
+ }
352
+ console.log();
353
+ }
354
+ catch (error) {
355
+ spinner.fail('Analysis failed');
356
+ printError(String(error));
357
+ process.exit(1);
358
+ }
359
+ });
360
+ // Full Analysis
361
+ program
362
+ .command('full-analysis')
363
+ .alias('analyze-all')
364
+ .description('Run all analyzers')
365
+ .option('-p, --project <id>', 'Project ID')
366
+ .option('--json', 'Output as JSON')
367
+ .action(async (options) => {
368
+ const spinner = createSpinner('Running full analysis...').start();
369
+ try {
370
+ const config = await loadConfig();
371
+ const projectId = options.project || config.activeProjectId;
372
+ if (!projectId) {
373
+ spinner.fail('No project selected');
374
+ printError('Use "archicore projects select <id>" first.');
375
+ process.exit(1);
376
+ }
377
+ const response = await fetch(`${config.serverUrl}/api/projects/${projectId}/full-analysis`, {
378
+ method: 'POST',
379
+ });
380
+ if (!response.ok) {
381
+ const error = await response.json();
382
+ throw new Error(error.error || 'Failed to run analysis');
383
+ }
384
+ const data = await response.json();
385
+ spinner.succeed('Full analysis complete');
386
+ if (options.json) {
387
+ console.log(JSON.stringify(data, null, 2));
388
+ return;
389
+ }
390
+ printSection('Full Analysis Summary');
391
+ // Dead Code
392
+ const dc = data.deadCode;
393
+ console.log(colors.secondary(' Dead Code'));
394
+ console.log(` Unused: ${dc?.unusedExports?.length || 0} exports, ${dc?.unusedVariables?.length || 0} variables`);
395
+ console.log();
396
+ // Duplication
397
+ const dup = data.duplication;
398
+ console.log(colors.secondary(' Duplication'));
399
+ console.log(` ${(dup?.duplicationPercentage || 0).toFixed(1)}% duplicated, ${dup?.clones?.length || 0} clone groups`);
400
+ console.log();
401
+ // Security
402
+ const sec = data.security;
403
+ const vulns = sec?.vulnerabilities || [];
404
+ console.log(colors.secondary(' Security'));
405
+ console.log(` ${vulns.length} vulnerabilities found`);
406
+ console.log();
407
+ // Metrics
408
+ const met = data.metrics;
409
+ console.log(colors.secondary(' Metrics'));
410
+ console.log(` Avg complexity: ${(met?.summary?.avgComplexity || 0).toFixed(2)}`);
411
+ console.log(` Avg maintainability: ${(met?.summary?.avgMaintainability || 0).toFixed(1)}`);
412
+ console.log();
413
+ // Rules
414
+ const rules = data.rules;
415
+ console.log(colors.secondary(' Rules'));
416
+ console.log(` ${rules?.violations?.length || 0} violations`);
417
+ console.log();
418
+ // Refactoring
419
+ const ref = data.refactoring;
420
+ console.log(colors.secondary(' Refactoring'));
421
+ console.log(` ${ref?.suggestions?.length || 0} suggestions`);
422
+ console.log();
423
+ }
424
+ catch (error) {
425
+ spinner.fail('Analysis failed');
426
+ printError(String(error));
427
+ process.exit(1);
428
+ }
429
+ });
430
+ }
431
+ //# sourceMappingURL=analyzers.js.map