@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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrettierFormatter - Code formatting using Prettier
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Format code according to consistent style rules
|
|
6
|
+
* - Support multiple languages (JS, TS, CSS, HTML, JSON, etc.)
|
|
7
|
+
* - Detect and use project's Prettier configuration
|
|
8
|
+
* - Provide before/after comparison
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import prettier from 'prettier';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
|
|
15
|
+
class PrettierFormatter {
|
|
16
|
+
constructor(logger = null) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.configCache = new Map();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format code with Prettier
|
|
23
|
+
* @param {string} filePath - Path to file
|
|
24
|
+
* @param {string} content - File content
|
|
25
|
+
* @param {Object} options - Format options
|
|
26
|
+
* @returns {Promise<Object>} Formatted content and changes
|
|
27
|
+
*/
|
|
28
|
+
async format(filePath, content, options = {}) {
|
|
29
|
+
try {
|
|
30
|
+
// Get Prettier configuration
|
|
31
|
+
const config = await this.getPrettierConfig(filePath, options.workingDir);
|
|
32
|
+
|
|
33
|
+
// Determine parser from file extension
|
|
34
|
+
const parser = this.getParser(filePath);
|
|
35
|
+
|
|
36
|
+
// Format the code
|
|
37
|
+
const formatted = await prettier.format(content, {
|
|
38
|
+
...config,
|
|
39
|
+
filepath: filePath,
|
|
40
|
+
parser
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Check if formatting made changes
|
|
44
|
+
const hasChanges = formatted !== content;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
formatted: hasChanges,
|
|
48
|
+
content: formatted,
|
|
49
|
+
original: content,
|
|
50
|
+
changes: hasChanges ? this.describeChanges(content, formatted) : [],
|
|
51
|
+
linesChanged: hasChanges ? this.countChangedLines(content, formatted) : 0
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.logger?.error('Prettier formatting failed', {
|
|
56
|
+
file: filePath,
|
|
57
|
+
error: error.message
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
throw new Error(`Prettier formatting failed: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if file needs formatting
|
|
66
|
+
* @param {string} filePath - Path to file
|
|
67
|
+
* @param {string} content - File content
|
|
68
|
+
* @param {Object} options - Check options
|
|
69
|
+
* @returns {Promise<boolean>} True if file needs formatting
|
|
70
|
+
*/
|
|
71
|
+
async check(filePath, content, options = {}) {
|
|
72
|
+
try {
|
|
73
|
+
const config = await this.getPrettierConfig(filePath, options.workingDir);
|
|
74
|
+
const parser = this.getParser(filePath);
|
|
75
|
+
|
|
76
|
+
const formatted = await prettier.format(content, {
|
|
77
|
+
...config,
|
|
78
|
+
filepath: filePath,
|
|
79
|
+
parser
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return formatted !== content;
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.logger?.warn('Prettier check failed', {
|
|
86
|
+
file: filePath,
|
|
87
|
+
error: error.message
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get Prettier configuration
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
async getPrettierConfig(filePath, workingDir) {
|
|
99
|
+
// Check cache
|
|
100
|
+
const cacheKey = workingDir || path.dirname(filePath);
|
|
101
|
+
if (this.configCache.has(cacheKey)) {
|
|
102
|
+
return this.configCache.get(cacheKey);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let config = {};
|
|
106
|
+
|
|
107
|
+
// Try to load project's Prettier config
|
|
108
|
+
if (workingDir) {
|
|
109
|
+
try {
|
|
110
|
+
const projectConfig = await prettier.resolveConfig(filePath, {
|
|
111
|
+
config: workingDir
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (projectConfig) {
|
|
115
|
+
this.logger?.debug('Found Prettier config', { workingDir });
|
|
116
|
+
config = projectConfig;
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger?.debug('No Prettier config found, using defaults');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Default configuration
|
|
124
|
+
const defaultConfig = {
|
|
125
|
+
semi: true,
|
|
126
|
+
singleQuote: true,
|
|
127
|
+
tabWidth: 2,
|
|
128
|
+
trailingComma: 'es5',
|
|
129
|
+
printWidth: 100,
|
|
130
|
+
arrowParens: 'avoid',
|
|
131
|
+
endOfLine: 'lf',
|
|
132
|
+
...config
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Cache the configuration
|
|
136
|
+
this.configCache.set(cacheKey, defaultConfig);
|
|
137
|
+
|
|
138
|
+
return defaultConfig;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get parser for file type
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
getParser(filePath) {
|
|
146
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
147
|
+
|
|
148
|
+
const parserMap = {
|
|
149
|
+
'.js': 'babel',
|
|
150
|
+
'.jsx': 'babel',
|
|
151
|
+
'.mjs': 'babel',
|
|
152
|
+
'.cjs': 'babel',
|
|
153
|
+
'.ts': 'typescript',
|
|
154
|
+
'.tsx': 'typescript',
|
|
155
|
+
'.json': 'json',
|
|
156
|
+
'.json5': 'json5',
|
|
157
|
+
'.css': 'css',
|
|
158
|
+
'.scss': 'scss',
|
|
159
|
+
'.less': 'less',
|
|
160
|
+
'.html': 'html',
|
|
161
|
+
'.vue': 'vue',
|
|
162
|
+
'.md': 'markdown',
|
|
163
|
+
'.yaml': 'yaml',
|
|
164
|
+
'.yml': 'yaml'
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return parserMap[ext] || 'babel';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Describe changes made by formatting
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
describeChanges(original, formatted) {
|
|
175
|
+
const changes = [];
|
|
176
|
+
const originalLines = original.split('\n');
|
|
177
|
+
const formattedLines = formatted.split('\n');
|
|
178
|
+
|
|
179
|
+
const maxLines = Math.max(originalLines.length, formattedLines.length);
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < maxLines; i++) {
|
|
182
|
+
const origLine = originalLines[i];
|
|
183
|
+
const formattedLine = formattedLines[i];
|
|
184
|
+
|
|
185
|
+
if (origLine !== formattedLine) {
|
|
186
|
+
changes.push({
|
|
187
|
+
line: i + 1,
|
|
188
|
+
type: origLine !== undefined && formattedLine !== undefined
|
|
189
|
+
? 'modified'
|
|
190
|
+
: (origLine !== undefined ? 'removed' : 'added'),
|
|
191
|
+
original: origLine || '',
|
|
192
|
+
formatted: formattedLine || ''
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return changes;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Count changed lines
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
countChangedLines(original, formatted) {
|
|
205
|
+
const originalLines = original.split('\n');
|
|
206
|
+
const formattedLines = formatted.split('\n');
|
|
207
|
+
let changedCount = 0;
|
|
208
|
+
|
|
209
|
+
const maxLines = Math.max(originalLines.length, formattedLines.length);
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < maxLines; i++) {
|
|
212
|
+
if (originalLines[i] !== formattedLines[i]) {
|
|
213
|
+
changedCount++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return changedCount;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get supported file extensions
|
|
222
|
+
* @returns {Array<string>} Array of supported extensions
|
|
223
|
+
*/
|
|
224
|
+
getSupportedExtensions() {
|
|
225
|
+
return [
|
|
226
|
+
'.js', '.jsx', '.mjs', '.cjs',
|
|
227
|
+
'.ts', '.tsx',
|
|
228
|
+
'.json', '.json5',
|
|
229
|
+
'.css', '.scss', '.less',
|
|
230
|
+
'.html', '.vue',
|
|
231
|
+
'.md',
|
|
232
|
+
'.yaml', '.yml'
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if file type is supported
|
|
238
|
+
* @param {string} filePath - Path to file
|
|
239
|
+
* @returns {boolean} True if supported
|
|
240
|
+
*/
|
|
241
|
+
isSupported(filePath) {
|
|
242
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
243
|
+
return this.getSupportedExtensions().includes(ext);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default PrettierFormatter;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PythonAnalyzer - Python code analysis using Python's ast module
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Analyze Python code for syntax errors
|
|
6
|
+
* - Detect import issues
|
|
7
|
+
* - Support Django, Flask, FastAPI frameworks
|
|
8
|
+
* - Use Python's built-in ast module via subprocess
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
|
+
import { STATIC_ANALYSIS } from '../utilities/constants.js';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import os from 'os';
|
|
16
|
+
|
|
17
|
+
class PythonAnalyzer {
|
|
18
|
+
constructor(logger = null) {
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.pythonCommand = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Analyze Python code
|
|
25
|
+
* @param {string} filePath - Path to file
|
|
26
|
+
* @param {string} content - File content
|
|
27
|
+
* @param {Object} options - Analysis options
|
|
28
|
+
* @returns {Promise<Array>} Array of diagnostics
|
|
29
|
+
*/
|
|
30
|
+
async analyze(filePath, content, options = {}) {
|
|
31
|
+
try {
|
|
32
|
+
const diagnostics = [];
|
|
33
|
+
|
|
34
|
+
// Check if Python is available
|
|
35
|
+
const pythonCmd = await this.getPythonCommand();
|
|
36
|
+
if (!pythonCmd) {
|
|
37
|
+
this.logger?.warn('Python not available, skipping analysis');
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Run syntax check using Python's ast module
|
|
42
|
+
const syntaxErrors = await this.checkSyntax(filePath, content, pythonCmd);
|
|
43
|
+
diagnostics.push(...syntaxErrors);
|
|
44
|
+
|
|
45
|
+
this.logger?.debug('Python analysis completed', {
|
|
46
|
+
file: filePath,
|
|
47
|
+
totalDiagnostics: diagnostics.length,
|
|
48
|
+
errors: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR).length,
|
|
49
|
+
warnings: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING).length
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return diagnostics;
|
|
53
|
+
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.logger?.error('Python analysis failed', {
|
|
56
|
+
file: filePath,
|
|
57
|
+
error: error.message
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Return empty array on error to allow other analysis to continue
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check Python syntax using ast module
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
async checkSyntax(filePath, content, pythonCmd) {
|
|
70
|
+
const diagnostics = [];
|
|
71
|
+
|
|
72
|
+
// Create a temporary Python script to check syntax
|
|
73
|
+
const tempDir = os.tmpdir();
|
|
74
|
+
const tempScript = path.join(tempDir, `syntax_check_${Date.now()}.py`);
|
|
75
|
+
const tempFile = path.join(tempDir, `target_${Date.now()}.py`);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Write the content to a temporary file
|
|
79
|
+
await fs.writeFile(tempFile, content, 'utf-8');
|
|
80
|
+
|
|
81
|
+
// Create syntax checker script
|
|
82
|
+
const checkerScript = `
|
|
83
|
+
import ast
|
|
84
|
+
import sys
|
|
85
|
+
import json
|
|
86
|
+
|
|
87
|
+
def check_syntax(filepath):
|
|
88
|
+
try:
|
|
89
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
90
|
+
source = f.read()
|
|
91
|
+
|
|
92
|
+
# Try to parse the file
|
|
93
|
+
ast.parse(source, filename=filepath)
|
|
94
|
+
|
|
95
|
+
# If successful, no errors
|
|
96
|
+
print(json.dumps({"success": True, "errors": []}))
|
|
97
|
+
|
|
98
|
+
except SyntaxError as e:
|
|
99
|
+
error = {
|
|
100
|
+
"file": filepath,
|
|
101
|
+
"line": e.lineno or 1,
|
|
102
|
+
"column": e.offset or 1,
|
|
103
|
+
"message": str(e.msg),
|
|
104
|
+
"text": e.text.strip() if e.text else ""
|
|
105
|
+
}
|
|
106
|
+
print(json.dumps({"success": False, "errors": [error]}))
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
error = {
|
|
110
|
+
"file": filepath,
|
|
111
|
+
"line": 1,
|
|
112
|
+
"column": 1,
|
|
113
|
+
"message": str(e),
|
|
114
|
+
"text": ""
|
|
115
|
+
}
|
|
116
|
+
print(json.dumps({"success": False, "errors": [error]}))
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
if len(sys.argv) > 1:
|
|
120
|
+
check_syntax(sys.argv[1])
|
|
121
|
+
else:
|
|
122
|
+
print(json.dumps({"success": False, "errors": [{"message": "No file specified"}]}))
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
await fs.writeFile(tempScript, checkerScript, 'utf-8');
|
|
126
|
+
|
|
127
|
+
// Run the checker script
|
|
128
|
+
const result = await this.runPythonScript(pythonCmd, tempScript, [tempFile]);
|
|
129
|
+
|
|
130
|
+
// Parse the result
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(result.stdout);
|
|
133
|
+
|
|
134
|
+
if (!parsed.success && parsed.errors) {
|
|
135
|
+
for (const error of parsed.errors) {
|
|
136
|
+
diagnostics.push({
|
|
137
|
+
file: filePath,
|
|
138
|
+
line: error.line || 1,
|
|
139
|
+
column: error.column || 1,
|
|
140
|
+
severity: STATIC_ANALYSIS.SEVERITY.ERROR,
|
|
141
|
+
rule: 'SyntaxError',
|
|
142
|
+
message: error.message,
|
|
143
|
+
category: STATIC_ANALYSIS.CATEGORY.SYNTAX,
|
|
144
|
+
fixable: false,
|
|
145
|
+
source: 'python-ast',
|
|
146
|
+
code: error.text || undefined
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (parseError) {
|
|
151
|
+
this.logger?.warn('Failed to parse Python syntax check result', {
|
|
152
|
+
error: parseError.message,
|
|
153
|
+
stdout: result.stdout
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
} finally {
|
|
158
|
+
// Clean up temporary files
|
|
159
|
+
try {
|
|
160
|
+
await fs.unlink(tempScript);
|
|
161
|
+
await fs.unlink(tempFile);
|
|
162
|
+
} catch {
|
|
163
|
+
// Ignore cleanup errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return diagnostics;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get Python command (python3 or python)
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
async getPythonCommand() {
|
|
175
|
+
if (this.pythonCommand) {
|
|
176
|
+
return this.pythonCommand;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Try python3 first, then python
|
|
180
|
+
const commands = ['python3', 'python'];
|
|
181
|
+
|
|
182
|
+
for (const cmd of commands) {
|
|
183
|
+
try {
|
|
184
|
+
const result = await this.runCommand(cmd, ['--version']);
|
|
185
|
+
if (result.success) {
|
|
186
|
+
this.pythonCommand = cmd;
|
|
187
|
+
this.logger?.debug('Found Python command', { command: cmd, version: result.stdout.trim() });
|
|
188
|
+
return cmd;
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
// Continue to next command
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Run Python script
|
|
200
|
+
* @private
|
|
201
|
+
*/
|
|
202
|
+
async runPythonScript(pythonCmd, scriptPath, args = []) {
|
|
203
|
+
return this.runCommand(pythonCmd, [scriptPath, ...args]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Run command and capture output
|
|
208
|
+
* @private
|
|
209
|
+
*/
|
|
210
|
+
async runCommand(command, args = [], options = {}) {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const proc = spawn(command, args, {
|
|
213
|
+
...options,
|
|
214
|
+
shell: true
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
let stdout = '';
|
|
218
|
+
let stderr = '';
|
|
219
|
+
|
|
220
|
+
proc.stdout?.on('data', (data) => {
|
|
221
|
+
stdout += data.toString();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
proc.stderr?.on('data', (data) => {
|
|
225
|
+
stderr += data.toString();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
proc.on('error', (error) => {
|
|
229
|
+
reject(error);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
proc.on('close', (code) => {
|
|
233
|
+
resolve({
|
|
234
|
+
success: code === 0,
|
|
235
|
+
code,
|
|
236
|
+
stdout,
|
|
237
|
+
stderr
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Timeout after 10 seconds
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
proc.kill();
|
|
244
|
+
reject(new Error('Command timeout'));
|
|
245
|
+
}, 10000);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get supported file extensions
|
|
251
|
+
* @returns {Array<string>} Array of supported extensions
|
|
252
|
+
*/
|
|
253
|
+
getSupportedExtensions() {
|
|
254
|
+
return ['.py'];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if auto-fix is supported
|
|
259
|
+
* @returns {boolean} True if auto-fix is supported
|
|
260
|
+
*/
|
|
261
|
+
supportsAutoFix() {
|
|
262
|
+
return false; // Python auto-fix not implemented yet
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export default PythonAnalyzer;
|