@loxia-labs/loxia-autopilot-one 1.0.1
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/LICENSE +267 -0
- package/README.md +509 -0
- package/bin/cli.js +117 -0
- package/package.json +94 -0
- package/scripts/install-scanners.js +236 -0
- package/src/analyzers/CSSAnalyzer.js +297 -0
- package/src/analyzers/ConfigValidator.js +690 -0
- package/src/analyzers/ESLintAnalyzer.js +320 -0
- package/src/analyzers/JavaScriptAnalyzer.js +261 -0
- package/src/analyzers/PrettierFormatter.js +247 -0
- package/src/analyzers/PythonAnalyzer.js +266 -0
- package/src/analyzers/SecurityAnalyzer.js +729 -0
- package/src/analyzers/TypeScriptAnalyzer.js +247 -0
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
- package/src/analyzers/codeCloneDetector/detector.js +203 -0
- package/src/analyzers/codeCloneDetector/index.js +160 -0
- package/src/analyzers/codeCloneDetector/parser.js +199 -0
- package/src/analyzers/codeCloneDetector/reporter.js +148 -0
- package/src/analyzers/codeCloneDetector/scanner.js +59 -0
- package/src/core/agentPool.js +1474 -0
- package/src/core/agentScheduler.js +2147 -0
- package/src/core/contextManager.js +709 -0
- package/src/core/messageProcessor.js +732 -0
- package/src/core/orchestrator.js +548 -0
- package/src/core/stateManager.js +877 -0
- package/src/index.js +631 -0
- package/src/interfaces/cli.js +549 -0
- package/src/interfaces/webServer.js +2162 -0
- package/src/modules/fileExplorer/controller.js +280 -0
- package/src/modules/fileExplorer/index.js +37 -0
- package/src/modules/fileExplorer/middleware.js +92 -0
- package/src/modules/fileExplorer/routes.js +125 -0
- package/src/modules/fileExplorer/types.js +44 -0
- package/src/services/aiService.js +1232 -0
- package/src/services/apiKeyManager.js +164 -0
- package/src/services/benchmarkService.js +366 -0
- package/src/services/budgetService.js +539 -0
- package/src/services/contextInjectionService.js +247 -0
- package/src/services/conversationCompactionService.js +637 -0
- package/src/services/errorHandler.js +810 -0
- package/src/services/fileAttachmentService.js +544 -0
- package/src/services/modelRouterService.js +366 -0
- package/src/services/modelsService.js +322 -0
- package/src/services/qualityInspector.js +796 -0
- package/src/services/tokenCountingService.js +536 -0
- package/src/tools/agentCommunicationTool.js +1344 -0
- package/src/tools/agentDelayTool.js +485 -0
- package/src/tools/asyncToolManager.js +604 -0
- package/src/tools/baseTool.js +800 -0
- package/src/tools/browserTool.js +920 -0
- package/src/tools/cloneDetectionTool.js +621 -0
- package/src/tools/dependencyResolverTool.js +1215 -0
- package/src/tools/fileContentReplaceTool.js +875 -0
- package/src/tools/fileSystemTool.js +1107 -0
- package/src/tools/fileTreeTool.js +853 -0
- package/src/tools/imageTool.js +901 -0
- package/src/tools/importAnalyzerTool.js +1060 -0
- package/src/tools/jobDoneTool.js +248 -0
- package/src/tools/seekTool.js +956 -0
- package/src/tools/staticAnalysisTool.js +1778 -0
- package/src/tools/taskManagerTool.js +2873 -0
- package/src/tools/terminalTool.js +2304 -0
- package/src/tools/webTool.js +1430 -0
- package/src/types/agent.js +519 -0
- package/src/types/contextReference.js +972 -0
- package/src/types/conversation.js +730 -0
- package/src/types/toolCommand.js +747 -0
- package/src/utilities/attachmentValidator.js +292 -0
- package/src/utilities/configManager.js +582 -0
- package/src/utilities/constants.js +722 -0
- package/src/utilities/directoryAccessManager.js +535 -0
- package/src/utilities/fileProcessor.js +307 -0
- package/src/utilities/logger.js +436 -0
- package/src/utilities/tagParser.js +1246 -0
- package/src/utilities/toolConstants.js +317 -0
- package/web-ui/build/index.html +15 -0
- package/web-ui/build/logo.png +0 -0
- package/web-ui/build/logo2.png +0 -0
- package/web-ui/build/static/index-CjkkcnFA.js +344 -0
- package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloneDetectionTool - Detect duplicated code (code clones) for refactoring
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Identify exact and similar code clones across the codebase
|
|
6
|
+
* - Provide refactoring recommendations with priorities
|
|
7
|
+
* - Help reduce technical debt and improve maintainability
|
|
8
|
+
* - Support JavaScript, TypeScript, JSX, TSX, Vue files
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { BaseTool } from './baseTool.js';
|
|
12
|
+
import TagParser from '../utilities/tagParser.js';
|
|
13
|
+
import DirectoryAccessManager from '../utilities/directoryAccessManager.js';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
TOOL_STATUS,
|
|
18
|
+
SYSTEM_DEFAULTS
|
|
19
|
+
} from '../utilities/constants.js';
|
|
20
|
+
|
|
21
|
+
class CloneDetectionTool extends BaseTool {
|
|
22
|
+
constructor(config = {}, logger = null) {
|
|
23
|
+
super(config, logger);
|
|
24
|
+
|
|
25
|
+
// Tool metadata
|
|
26
|
+
this.requiresProject = true;
|
|
27
|
+
this.isAsync = false;
|
|
28
|
+
this.timeout = config.timeout || 120000; // 2 minutes default
|
|
29
|
+
this.maxConcurrentOperations = config.maxConcurrentOperations || 1;
|
|
30
|
+
|
|
31
|
+
// Clone detection settings
|
|
32
|
+
this.defaultMinTokens = config.defaultMinTokens || 50;
|
|
33
|
+
this.defaultMinLines = config.defaultMinLines || 5;
|
|
34
|
+
this.defaultSimilarityThreshold = config.defaultSimilarityThreshold || 0.85;
|
|
35
|
+
this.maxFileSize = config.maxFileSize || 500000; // 500KB per file
|
|
36
|
+
|
|
37
|
+
// Directory access manager
|
|
38
|
+
this.directoryAccessManager = new DirectoryAccessManager(config, logger);
|
|
39
|
+
|
|
40
|
+
// Clone detector will be initialized lazily when needed
|
|
41
|
+
this.cloneDetector = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get tool description for LLM consumption
|
|
46
|
+
* @returns {string} Tool description
|
|
47
|
+
*/
|
|
48
|
+
getDescription() {
|
|
49
|
+
return `
|
|
50
|
+
Code Clone Detection Tool: Find duplicated code for refactoring opportunities
|
|
51
|
+
|
|
52
|
+
This tool identifies exact and similar code patterns (code clones) across your codebase to help reduce duplication, improve maintainability, and identify refactoring opportunities.
|
|
53
|
+
|
|
54
|
+
WHAT IT DETECTS:
|
|
55
|
+
- Exact Clones (Type 1): Identical code with different formatting/comments
|
|
56
|
+
- Similar Clones (Type 2/3): Structurally similar code with minor variations
|
|
57
|
+
|
|
58
|
+
SUPPORTED LANGUAGES:
|
|
59
|
+
- JavaScript (.js, .jsx, .mjs, .cjs)
|
|
60
|
+
- TypeScript (.ts, .tsx)
|
|
61
|
+
- Vue (.vue)
|
|
62
|
+
|
|
63
|
+
USAGE - XML FORMAT:
|
|
64
|
+
|
|
65
|
+
Detect clones in entire directory:
|
|
66
|
+
[tool id="clonedetection"]
|
|
67
|
+
<detect-clones directory="." />
|
|
68
|
+
[/tool]
|
|
69
|
+
|
|
70
|
+
Detect clones in specific directory:
|
|
71
|
+
[tool id="clonedetection"]
|
|
72
|
+
<detect-clones directory="src/services" />
|
|
73
|
+
[/tool]
|
|
74
|
+
|
|
75
|
+
Custom sensitivity (lower minTokens = more sensitive):
|
|
76
|
+
[tool id="clonedetection"]
|
|
77
|
+
<detect-clones directory="src" min-tokens="30" min-lines="5" />
|
|
78
|
+
[/tool]
|
|
79
|
+
|
|
80
|
+
Filter by priority:
|
|
81
|
+
[tool id="clonedetection"]
|
|
82
|
+
<detect-clones directory="." priority-filter="high" max-results="5" />
|
|
83
|
+
[/tool]
|
|
84
|
+
|
|
85
|
+
Get summary only:
|
|
86
|
+
[tool id="clonedetection"]
|
|
87
|
+
<detect-clones directory="." output-mode="summary" />
|
|
88
|
+
[/tool]
|
|
89
|
+
|
|
90
|
+
USAGE - JSON FORMAT:
|
|
91
|
+
|
|
92
|
+
\`\`\`json
|
|
93
|
+
{
|
|
94
|
+
"toolId": "clonedetection",
|
|
95
|
+
"actions": [
|
|
96
|
+
{
|
|
97
|
+
"type": "detect-clones",
|
|
98
|
+
"directory": "src",
|
|
99
|
+
"minTokens": 50,
|
|
100
|
+
"minLines": 5,
|
|
101
|
+
"similarityThreshold": 0.85,
|
|
102
|
+
"priorityFilter": "high",
|
|
103
|
+
"maxResults": 10,
|
|
104
|
+
"outputMode": "summary"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
PARAMETERS:
|
|
111
|
+
- directory: Directory to analyze (required)
|
|
112
|
+
- min-tokens: Minimum token count (default: 50, lower = more sensitive)
|
|
113
|
+
- min-lines: Minimum line count (default: 5)
|
|
114
|
+
- similarity-threshold: 0-1 similarity threshold (default: 0.85)
|
|
115
|
+
- priority-filter: Filter by priority (high/medium/low, optional)
|
|
116
|
+
- max-results: Maximum number of clones to return (optional)
|
|
117
|
+
- output-mode: summary|detailed|recommendations (default: detailed)
|
|
118
|
+
|
|
119
|
+
OUTPUT FORMAT:
|
|
120
|
+
Returns clone detection results with:
|
|
121
|
+
- summary: Overall statistics (total clones, duplication %, priority breakdown)
|
|
122
|
+
- clones: Array of detected clone groups
|
|
123
|
+
- id: Clone identifier
|
|
124
|
+
- type: exact | similar
|
|
125
|
+
- confidence: Detection confidence (0-1)
|
|
126
|
+
- instances: Array of code locations with file paths and line numbers
|
|
127
|
+
- metrics: tokenCount, lineCount, instanceCount, impactScore, filesCovered
|
|
128
|
+
- refactoringAdvice:
|
|
129
|
+
- priority: high | medium | low
|
|
130
|
+
- strategy: extract-function | extract-class | extract-module | extract-constant
|
|
131
|
+
- suggestedName: Recommended name for extracted code
|
|
132
|
+
- reasoning: Why this refactoring is recommended
|
|
133
|
+
- estimatedEffort: low | medium | high
|
|
134
|
+
- benefits: Array of benefits
|
|
135
|
+
- actionableSteps: Step-by-step refactoring instructions
|
|
136
|
+
|
|
137
|
+
OUTPUT MODES:
|
|
138
|
+
- summary: High-level overview only (duplication %, top 5 clones)
|
|
139
|
+
- detailed: Full clone details with code snippets (default)
|
|
140
|
+
- recommendations: Refactoring priorities with actionable steps
|
|
141
|
+
|
|
142
|
+
DETECTION ALGORITHM:
|
|
143
|
+
- Uses AST (Abstract Syntax Tree) parsing via Babel
|
|
144
|
+
- Token-based similarity with normalization
|
|
145
|
+
- Longest Common Subsequence (LCS) algorithm for similarity
|
|
146
|
+
- Configurable sensitivity and thresholds
|
|
147
|
+
|
|
148
|
+
EXAMPLES:
|
|
149
|
+
|
|
150
|
+
Find all clones in project:
|
|
151
|
+
[tool id="clonedetection"]
|
|
152
|
+
<detect-clones directory="." />
|
|
153
|
+
[/tool]
|
|
154
|
+
|
|
155
|
+
High-priority refactoring opportunities only:
|
|
156
|
+
[tool id="clonedetection"]
|
|
157
|
+
<detect-clones directory="src" priority-filter="high" max-results="10" />
|
|
158
|
+
[/tool]
|
|
159
|
+
|
|
160
|
+
More sensitive detection (finds smaller clones):
|
|
161
|
+
[tool id="clonedetection"]
|
|
162
|
+
<detect-clones directory="." min-tokens="30" similarity-threshold="0.80" />
|
|
163
|
+
[/tool]
|
|
164
|
+
|
|
165
|
+
Quick overview:
|
|
166
|
+
[tool id="clonedetection"]
|
|
167
|
+
<detect-clones directory="." output-mode="summary" />
|
|
168
|
+
[/tool]
|
|
169
|
+
|
|
170
|
+
LIMITATIONS:
|
|
171
|
+
- Analyzes JavaScript/TypeScript family only
|
|
172
|
+
- Performance depends on codebase size (500ms per file typical)
|
|
173
|
+
- Maximum file size: ${Math.round(this.maxFileSize / 1024)}KB per file
|
|
174
|
+
- Analysis timeout: ${this.timeout / 1000} seconds
|
|
175
|
+
|
|
176
|
+
USE CASES:
|
|
177
|
+
- Identify refactoring opportunities before code review
|
|
178
|
+
- Track technical debt and code duplication metrics
|
|
179
|
+
- Find candidates for DRY (Don't Repeat Yourself) refactoring
|
|
180
|
+
- Prioritize maintenance work by impact score
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Parse parameters from tool command content
|
|
186
|
+
* @param {string} content - Raw tool command content
|
|
187
|
+
* @returns {Object} Parsed parameters
|
|
188
|
+
*/
|
|
189
|
+
parseParameters(content) {
|
|
190
|
+
try {
|
|
191
|
+
const params = {};
|
|
192
|
+
const actions = [];
|
|
193
|
+
|
|
194
|
+
this.logger?.debug('CloneDetection tool parsing parameters', {
|
|
195
|
+
contentLength: content.length,
|
|
196
|
+
contentPreview: content.substring(0, 200)
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Extract self-closing <detect-clones> tags
|
|
200
|
+
const detectClonesPattern = /<detect-clones\s+(.+?)\/>/g;
|
|
201
|
+
let match;
|
|
202
|
+
|
|
203
|
+
while ((match = detectClonesPattern.exec(content)) !== null) {
|
|
204
|
+
const attributeString = match[1].trim();
|
|
205
|
+
const parser = new TagParser();
|
|
206
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
207
|
+
|
|
208
|
+
const action = {
|
|
209
|
+
type: 'detect-clones',
|
|
210
|
+
...attributes
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Normalize attribute names
|
|
214
|
+
if (action['min-tokens']) {
|
|
215
|
+
action.minTokens = parseInt(action['min-tokens'], 10);
|
|
216
|
+
delete action['min-tokens'];
|
|
217
|
+
}
|
|
218
|
+
if (action['min-lines']) {
|
|
219
|
+
action.minLines = parseInt(action['min-lines'], 10);
|
|
220
|
+
delete action['min-lines'];
|
|
221
|
+
}
|
|
222
|
+
if (action['similarity-threshold']) {
|
|
223
|
+
action.similarityThreshold = parseFloat(action['similarity-threshold']);
|
|
224
|
+
delete action['similarity-threshold'];
|
|
225
|
+
}
|
|
226
|
+
if (action['priority-filter']) {
|
|
227
|
+
action.priorityFilter = action['priority-filter'];
|
|
228
|
+
delete action['priority-filter'];
|
|
229
|
+
}
|
|
230
|
+
if (action['max-results']) {
|
|
231
|
+
action.maxResults = parseInt(action['max-results'], 10);
|
|
232
|
+
delete action['max-results'];
|
|
233
|
+
}
|
|
234
|
+
if (action['output-mode']) {
|
|
235
|
+
action.outputMode = action['output-mode'];
|
|
236
|
+
delete action['output-mode'];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
actions.push(action);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
params.actions = actions;
|
|
243
|
+
params.rawContent = content.trim();
|
|
244
|
+
|
|
245
|
+
this.logger?.debug('Parsed CloneDetection tool parameters', {
|
|
246
|
+
totalActions: actions.length,
|
|
247
|
+
actionTypes: actions.map(a => a.type)
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return params;
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
throw new Error(`Failed to parse clone detection parameters: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get required parameters
|
|
259
|
+
* @returns {Array<string>} Array of required parameter names
|
|
260
|
+
*/
|
|
261
|
+
getRequiredParameters() {
|
|
262
|
+
return ['actions'];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Custom parameter validation
|
|
267
|
+
* @param {Object} params - Parameters to validate
|
|
268
|
+
* @returns {Object} Validation result
|
|
269
|
+
*/
|
|
270
|
+
customValidateParameters(params) {
|
|
271
|
+
const errors = [];
|
|
272
|
+
|
|
273
|
+
if (!params.actions || !Array.isArray(params.actions) || params.actions.length === 0) {
|
|
274
|
+
errors.push('At least one action is required');
|
|
275
|
+
} else {
|
|
276
|
+
// Validate each action
|
|
277
|
+
for (const [index, action] of params.actions.entries()) {
|
|
278
|
+
if (!action.type) {
|
|
279
|
+
errors.push(`Action ${index + 1}: type is required`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (action.type === 'detect-clones') {
|
|
284
|
+
if (!action.directory) {
|
|
285
|
+
errors.push(`Action ${index + 1}: directory is required for detect-clones`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate numeric parameters
|
|
289
|
+
if (action.minTokens !== undefined && (action.minTokens < 10 || action.minTokens > 1000)) {
|
|
290
|
+
errors.push(`Action ${index + 1}: min-tokens must be between 10 and 1000`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (action.minLines !== undefined && (action.minLines < 1 || action.minLines > 100)) {
|
|
294
|
+
errors.push(`Action ${index + 1}: min-lines must be between 1 and 100`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (action.similarityThreshold !== undefined &&
|
|
298
|
+
(action.similarityThreshold < 0.5 || action.similarityThreshold > 1.0)) {
|
|
299
|
+
errors.push(`Action ${index + 1}: similarity-threshold must be between 0.5 and 1.0`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Validate enum parameters
|
|
303
|
+
if (action.priorityFilter && !['high', 'medium', 'low'].includes(action.priorityFilter)) {
|
|
304
|
+
errors.push(`Action ${index + 1}: priority-filter must be high, medium, or low`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (action.outputMode && !['summary', 'detailed', 'recommendations'].includes(action.outputMode)) {
|
|
308
|
+
errors.push(`Action ${index + 1}: output-mode must be summary, detailed, or recommendations`);
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
errors.push(`Action ${index + 1}: unknown action type: ${action.type}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
valid: errors.length === 0,
|
|
318
|
+
errors
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Execute tool with parsed parameters
|
|
324
|
+
* @param {Object} params - Parsed parameters
|
|
325
|
+
* @param {Object} context - Execution context
|
|
326
|
+
* @returns {Promise<Object>} Execution result
|
|
327
|
+
*/
|
|
328
|
+
async execute(params, context) {
|
|
329
|
+
const { actions } = params;
|
|
330
|
+
const { projectDir, agentId, directoryAccess } = context;
|
|
331
|
+
|
|
332
|
+
// Get directory access configuration
|
|
333
|
+
const accessConfig = directoryAccess ||
|
|
334
|
+
this.directoryAccessManager.createDirectoryAccess({
|
|
335
|
+
workingDirectory: projectDir || process.cwd(),
|
|
336
|
+
writeEnabledDirectories: [],
|
|
337
|
+
readOnlyDirectories: [projectDir || process.cwd()],
|
|
338
|
+
restrictToProject: true
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const workingDir = this.directoryAccessManager.getWorkingDirectory(accessConfig);
|
|
342
|
+
const results = [];
|
|
343
|
+
|
|
344
|
+
for (const action of actions) {
|
|
345
|
+
try {
|
|
346
|
+
if (action.type === 'detect-clones') {
|
|
347
|
+
const result = await this.detectClones(
|
|
348
|
+
action.directory,
|
|
349
|
+
workingDir,
|
|
350
|
+
accessConfig,
|
|
351
|
+
action
|
|
352
|
+
);
|
|
353
|
+
results.push(result);
|
|
354
|
+
} else {
|
|
355
|
+
throw new Error(`Unknown action type: ${action.type}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
} catch (error) {
|
|
359
|
+
this.logger?.error('Clone detection action failed', {
|
|
360
|
+
action: action.type,
|
|
361
|
+
error: error.message
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
results.push({
|
|
365
|
+
directory: action.directory,
|
|
366
|
+
error: error.message,
|
|
367
|
+
success: false
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
success: true,
|
|
374
|
+
results,
|
|
375
|
+
toolUsed: 'clonedetection'
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Detect clones in directory
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
async detectClones(directory, workingDir, accessConfig, options = {}) {
|
|
384
|
+
const fullDir = path.isAbsolute(directory)
|
|
385
|
+
? path.normalize(directory)
|
|
386
|
+
: path.resolve(workingDir, directory);
|
|
387
|
+
|
|
388
|
+
// Validate read access
|
|
389
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
|
|
390
|
+
if (!accessResult.allowed) {
|
|
391
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
// Get clone detector instance
|
|
396
|
+
const detector = await this.getCloneDetector();
|
|
397
|
+
|
|
398
|
+
// Prepare configuration
|
|
399
|
+
const config = {
|
|
400
|
+
minTokens: options.minTokens || this.defaultMinTokens,
|
|
401
|
+
minLines: options.minLines || this.defaultMinLines,
|
|
402
|
+
similarityThreshold: options.similarityThreshold || this.defaultSimilarityThreshold,
|
|
403
|
+
include: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs', '**/*.ts', '**/*.tsx', '**/*.vue'],
|
|
404
|
+
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.test.js', '**/*.spec.js'],
|
|
405
|
+
maxFileSize: this.maxFileSize
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
this.logger?.info('Starting clone detection', {
|
|
409
|
+
directory: fullDir,
|
|
410
|
+
config
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Run clone detection (without output file)
|
|
414
|
+
const report = await detector.run(fullDir, null);
|
|
415
|
+
|
|
416
|
+
if (!report) {
|
|
417
|
+
return {
|
|
418
|
+
directory: this.directoryAccessManager.createRelativePath(fullDir, accessConfig),
|
|
419
|
+
fullPath: fullDir,
|
|
420
|
+
success: true,
|
|
421
|
+
summary: {
|
|
422
|
+
totalFiles: 0,
|
|
423
|
+
totalClones: 0,
|
|
424
|
+
duplicationPercentage: 0
|
|
425
|
+
},
|
|
426
|
+
clones: [],
|
|
427
|
+
message: 'No files found or no clones detected'
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Apply filters
|
|
432
|
+
let filteredClones = report.clones;
|
|
433
|
+
|
|
434
|
+
// Filter by priority
|
|
435
|
+
if (options.priorityFilter) {
|
|
436
|
+
filteredClones = filteredClones.filter(
|
|
437
|
+
clone => clone.refactoringAdvice.priority === options.priorityFilter
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Limit results
|
|
442
|
+
if (options.maxResults) {
|
|
443
|
+
filteredClones = filteredClones.slice(0, options.maxResults);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Format output based on mode
|
|
447
|
+
const outputMode = options.outputMode || 'detailed';
|
|
448
|
+
|
|
449
|
+
if (outputMode === 'summary') {
|
|
450
|
+
return this.formatSummaryOutput(report, filteredClones, fullDir, accessConfig);
|
|
451
|
+
} else if (outputMode === 'recommendations') {
|
|
452
|
+
return this.formatRecommendationsOutput(report, filteredClones, fullDir, accessConfig);
|
|
453
|
+
} else {
|
|
454
|
+
return this.formatDetailedOutput(report, filteredClones, fullDir, accessConfig);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
} catch (error) {
|
|
458
|
+
throw new Error(`Failed to detect clones in ${directory}: ${error.message}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Format summary output
|
|
464
|
+
* @private
|
|
465
|
+
*/
|
|
466
|
+
formatSummaryOutput(report, clones, fullDir, accessConfig) {
|
|
467
|
+
return {
|
|
468
|
+
directory: this.directoryAccessManager.createRelativePath(fullDir, accessConfig),
|
|
469
|
+
fullPath: fullDir,
|
|
470
|
+
success: true,
|
|
471
|
+
outputMode: 'summary',
|
|
472
|
+
summary: {
|
|
473
|
+
totalFiles: report.summary.totalFiles,
|
|
474
|
+
totalClones: report.summary.totalClones,
|
|
475
|
+
duplicatedLines: report.summary.totalDuplicatedLines,
|
|
476
|
+
duplicationPercentage: report.summary.duplicationPercentage,
|
|
477
|
+
priorityCounts: report.summary.priorityCounts,
|
|
478
|
+
topClones: clones.slice(0, 5).map(clone => ({
|
|
479
|
+
id: clone.id,
|
|
480
|
+
type: clone.type,
|
|
481
|
+
confidence: clone.confidence,
|
|
482
|
+
instances: clone.metrics.instanceCount,
|
|
483
|
+
lines: clone.metrics.lineCount,
|
|
484
|
+
priority: clone.refactoringAdvice.priority,
|
|
485
|
+
strategy: clone.refactoringAdvice.strategy,
|
|
486
|
+
locations: clone.instances.map(i => `${i.file}:${i.startLine}-${i.endLine}`)
|
|
487
|
+
}))
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Format recommendations output
|
|
494
|
+
* @private
|
|
495
|
+
*/
|
|
496
|
+
formatRecommendationsOutput(report, clones, fullDir, accessConfig) {
|
|
497
|
+
return {
|
|
498
|
+
directory: this.directoryAccessManager.createRelativePath(fullDir, accessConfig),
|
|
499
|
+
fullPath: fullDir,
|
|
500
|
+
success: true,
|
|
501
|
+
outputMode: 'recommendations',
|
|
502
|
+
summary: {
|
|
503
|
+
totalFiles: report.summary.totalFiles,
|
|
504
|
+
totalClones: report.summary.totalClones,
|
|
505
|
+
duplicationPercentage: report.summary.duplicationPercentage
|
|
506
|
+
},
|
|
507
|
+
recommendations: clones.map(clone => ({
|
|
508
|
+
id: clone.id,
|
|
509
|
+
priority: clone.refactoringAdvice.priority,
|
|
510
|
+
strategy: clone.refactoringAdvice.strategy,
|
|
511
|
+
suggestedName: clone.refactoringAdvice.suggestedName,
|
|
512
|
+
reasoning: clone.refactoringAdvice.reasoning,
|
|
513
|
+
effort: clone.refactoringAdvice.estimatedEffort,
|
|
514
|
+
benefits: clone.refactoringAdvice.benefits,
|
|
515
|
+
steps: clone.refactoringAdvice.actionableSteps,
|
|
516
|
+
metrics: {
|
|
517
|
+
instances: clone.metrics.instanceCount,
|
|
518
|
+
lines: clone.metrics.lineCount,
|
|
519
|
+
files: clone.metrics.filesCovered,
|
|
520
|
+
impact: clone.metrics.impactScore
|
|
521
|
+
},
|
|
522
|
+
locations: clone.instances.map(i => ({
|
|
523
|
+
file: i.file,
|
|
524
|
+
startLine: i.startLine,
|
|
525
|
+
endLine: i.endLine
|
|
526
|
+
}))
|
|
527
|
+
}))
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Format detailed output
|
|
533
|
+
* @private
|
|
534
|
+
*/
|
|
535
|
+
formatDetailedOutput(report, clones, fullDir, accessConfig) {
|
|
536
|
+
return {
|
|
537
|
+
directory: this.directoryAccessManager.createRelativePath(fullDir, accessConfig),
|
|
538
|
+
fullPath: fullDir,
|
|
539
|
+
success: true,
|
|
540
|
+
outputMode: 'detailed',
|
|
541
|
+
summary: report.summary,
|
|
542
|
+
clones: clones.map(clone => ({
|
|
543
|
+
...clone,
|
|
544
|
+
// Truncate code snippets for agent readability
|
|
545
|
+
instances: clone.instances.map(instance => ({
|
|
546
|
+
...instance,
|
|
547
|
+
code: instance.code.split('\n').slice(0, 10).join('\n') +
|
|
548
|
+
(instance.code.split('\n').length > 10 ? '\n... (truncated)' : '')
|
|
549
|
+
}))
|
|
550
|
+
}))
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Get clone detector instance (lazy initialization)
|
|
556
|
+
* @private
|
|
557
|
+
*/
|
|
558
|
+
async getCloneDetector() {
|
|
559
|
+
if (!this.cloneDetector) {
|
|
560
|
+
const { CloneDetectionTool } = await import('../analyzers/codeCloneDetector/index.js');
|
|
561
|
+
|
|
562
|
+
const config = {
|
|
563
|
+
minTokens: this.defaultMinTokens,
|
|
564
|
+
minLines: this.defaultMinLines,
|
|
565
|
+
similarityThreshold: this.defaultSimilarityThreshold,
|
|
566
|
+
include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.vue'],
|
|
567
|
+
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**'],
|
|
568
|
+
maxFileSize: this.maxFileSize
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
this.cloneDetector = new CloneDetectionTool(config);
|
|
572
|
+
this.logger?.debug('Clone detector initialized', { config });
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return this.cloneDetector;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Get supported actions for this tool
|
|
580
|
+
* @returns {Array<string>} Array of supported action names
|
|
581
|
+
*/
|
|
582
|
+
getSupportedActions() {
|
|
583
|
+
return ['detect-clones'];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Get parameter schema for validation
|
|
588
|
+
* @returns {Object} Parameter schema
|
|
589
|
+
*/
|
|
590
|
+
getParameterSchema() {
|
|
591
|
+
return {
|
|
592
|
+
type: 'object',
|
|
593
|
+
properties: {
|
|
594
|
+
actions: {
|
|
595
|
+
type: 'array',
|
|
596
|
+
minItems: 1,
|
|
597
|
+
items: {
|
|
598
|
+
type: 'object',
|
|
599
|
+
properties: {
|
|
600
|
+
type: {
|
|
601
|
+
type: 'string',
|
|
602
|
+
enum: this.getSupportedActions()
|
|
603
|
+
},
|
|
604
|
+
directory: { type: 'string' },
|
|
605
|
+
minTokens: { type: 'number', minimum: 10, maximum: 1000 },
|
|
606
|
+
minLines: { type: 'number', minimum: 1, maximum: 100 },
|
|
607
|
+
similarityThreshold: { type: 'number', minimum: 0.5, maximum: 1.0 },
|
|
608
|
+
priorityFilter: { type: 'string', enum: ['high', 'medium', 'low'] },
|
|
609
|
+
maxResults: { type: 'number', minimum: 1 },
|
|
610
|
+
outputMode: { type: 'string', enum: ['summary', 'detailed', 'recommendations'] }
|
|
611
|
+
},
|
|
612
|
+
required: ['type', 'directory']
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
required: ['actions']
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export default CloneDetectionTool;
|