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.
- package/README.md +530 -0
- package/dist/analyzers/dead-code.d.ts +95 -0
- package/dist/analyzers/dead-code.js +327 -0
- package/dist/analyzers/duplication.d.ts +90 -0
- package/dist/analyzers/duplication.js +344 -0
- package/dist/analyzers/security.d.ts +79 -0
- package/dist/analyzers/security.js +484 -0
- package/dist/architecture/index.d.ts +35 -0
- package/dist/architecture/index.js +249 -0
- package/dist/cli/commands/analyzers.d.ts +6 -0
- package/dist/cli/commands/analyzers.js +431 -0
- package/dist/cli/commands/export.d.ts +6 -0
- package/dist/cli/commands/export.js +78 -0
- package/dist/cli/commands/index.d.ts +8 -0
- package/dist/cli/commands/index.js +8 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +140 -0
- package/dist/cli/commands/interactive.d.ts +7 -0
- package/dist/cli/commands/interactive.js +522 -0
- package/dist/cli/commands/projects.d.ts +6 -0
- package/dist/cli/commands/projects.js +249 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/ui/box.d.ts +17 -0
- package/dist/cli/ui/box.js +62 -0
- package/dist/cli/ui/colors.d.ts +49 -0
- package/dist/cli/ui/colors.js +86 -0
- package/dist/cli/ui/index.d.ts +9 -0
- package/dist/cli/ui/index.js +9 -0
- package/dist/cli/ui/prompt.d.ts +34 -0
- package/dist/cli/ui/prompt.js +122 -0
- package/dist/cli/ui/spinner.d.ts +29 -0
- package/dist/cli/ui/spinner.js +80 -0
- package/dist/cli/ui/table.d.ts +33 -0
- package/dist/cli/ui/table.js +84 -0
- package/dist/cli/utils/config.d.ts +23 -0
- package/dist/cli/utils/config.js +73 -0
- package/dist/cli/utils/index.d.ts +6 -0
- package/dist/cli/utils/index.js +6 -0
- package/dist/cli/utils/session.d.ts +27 -0
- package/dist/cli/utils/session.js +117 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +295 -0
- package/dist/code-index/ast-parser.d.ts +16 -0
- package/dist/code-index/ast-parser.js +330 -0
- package/dist/code-index/dependency-graph.d.ts +16 -0
- package/dist/code-index/dependency-graph.js +161 -0
- package/dist/code-index/index.d.ts +44 -0
- package/dist/code-index/index.js +124 -0
- package/dist/code-index/symbol-extractor.d.ts +13 -0
- package/dist/code-index/symbol-extractor.js +150 -0
- package/dist/export/index.d.ts +92 -0
- package/dist/export/index.js +676 -0
- package/dist/github/github-service.d.ts +146 -0
- package/dist/github/github-service.js +609 -0
- package/dist/impact-engine/index.d.ts +25 -0
- package/dist/impact-engine/index.js +284 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +149 -0
- package/dist/metrics/index.d.ts +136 -0
- package/dist/metrics/index.js +525 -0
- package/dist/orchestrator/deepseek-optimizer.d.ts +67 -0
- package/dist/orchestrator/deepseek-optimizer.js +320 -0
- package/dist/orchestrator/index.d.ts +34 -0
- package/dist/orchestrator/index.js +305 -0
- package/dist/pr-guardian/index.d.ts +143 -0
- package/dist/pr-guardian/index.js +553 -0
- package/dist/refactoring/index.d.ts +108 -0
- package/dist/refactoring/index.js +580 -0
- package/dist/rules-engine/index.d.ts +129 -0
- package/dist/rules-engine/index.js +482 -0
- package/dist/semantic-memory/embedding-service.d.ts +24 -0
- package/dist/semantic-memory/embedding-service.js +120 -0
- package/dist/semantic-memory/index.d.ts +45 -0
- package/dist/semantic-memory/index.js +206 -0
- package/dist/semantic-memory/vector-store.d.ts +27 -0
- package/dist/semantic-memory/vector-store.js +166 -0
- package/dist/server/index.d.ts +28 -0
- package/dist/server/index.js +141 -0
- package/dist/server/middleware/api-auth.d.ts +43 -0
- package/dist/server/middleware/api-auth.js +256 -0
- package/dist/server/routes/admin.d.ts +5 -0
- package/dist/server/routes/admin.js +123 -0
- package/dist/server/routes/api.d.ts +7 -0
- package/dist/server/routes/api.js +362 -0
- package/dist/server/routes/auth.d.ts +16 -0
- package/dist/server/routes/auth.js +191 -0
- package/dist/server/routes/developer.d.ts +8 -0
- package/dist/server/routes/developer.js +439 -0
- package/dist/server/routes/github.d.ts +7 -0
- package/dist/server/routes/github.js +495 -0
- package/dist/server/routes/upload.d.ts +7 -0
- package/dist/server/routes/upload.js +196 -0
- package/dist/server/services/api-key-service.d.ts +81 -0
- package/dist/server/services/api-key-service.js +281 -0
- package/dist/server/services/auth-service.d.ts +40 -0
- package/dist/server/services/auth-service.js +315 -0
- package/dist/server/services/project-service.d.ts +123 -0
- package/dist/server/services/project-service.js +533 -0
- package/dist/server/services/token-service.d.ts +107 -0
- package/dist/server/services/token-service.js +416 -0
- package/dist/server/services/upload-service.d.ts +93 -0
- package/dist/server/services/upload-service.js +464 -0
- package/dist/types/api.d.ts +188 -0
- package/dist/types/api.js +86 -0
- package/dist/types/github.d.ts +335 -0
- package/dist/types/github.js +5 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +32 -0
- package/dist/types/user.d.ts +69 -0
- package/dist/types/user.js +42 -0
- package/dist/utils/file-utils.d.ts +20 -0
- package/dist/utils/file-utils.js +163 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.js +41 -0
- package/dist/watcher/index.d.ts +125 -0
- package/dist/watcher/index.js +397 -0
- 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,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
|