@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.
Files changed (80) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +509 -0
  3. package/bin/cli.js +117 -0
  4. package/package.json +94 -0
  5. package/scripts/install-scanners.js +236 -0
  6. package/src/analyzers/CSSAnalyzer.js +297 -0
  7. package/src/analyzers/ConfigValidator.js +690 -0
  8. package/src/analyzers/ESLintAnalyzer.js +320 -0
  9. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  10. package/src/analyzers/PrettierFormatter.js +247 -0
  11. package/src/analyzers/PythonAnalyzer.js +266 -0
  12. package/src/analyzers/SecurityAnalyzer.js +729 -0
  13. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  14. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  15. package/src/analyzers/codeCloneDetector/detector.js +203 -0
  16. package/src/analyzers/codeCloneDetector/index.js +160 -0
  17. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  18. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  19. package/src/analyzers/codeCloneDetector/scanner.js +59 -0
  20. package/src/core/agentPool.js +1474 -0
  21. package/src/core/agentScheduler.js +2147 -0
  22. package/src/core/contextManager.js +709 -0
  23. package/src/core/messageProcessor.js +732 -0
  24. package/src/core/orchestrator.js +548 -0
  25. package/src/core/stateManager.js +877 -0
  26. package/src/index.js +631 -0
  27. package/src/interfaces/cli.js +549 -0
  28. package/src/interfaces/webServer.js +2162 -0
  29. package/src/modules/fileExplorer/controller.js +280 -0
  30. package/src/modules/fileExplorer/index.js +37 -0
  31. package/src/modules/fileExplorer/middleware.js +92 -0
  32. package/src/modules/fileExplorer/routes.js +125 -0
  33. package/src/modules/fileExplorer/types.js +44 -0
  34. package/src/services/aiService.js +1232 -0
  35. package/src/services/apiKeyManager.js +164 -0
  36. package/src/services/benchmarkService.js +366 -0
  37. package/src/services/budgetService.js +539 -0
  38. package/src/services/contextInjectionService.js +247 -0
  39. package/src/services/conversationCompactionService.js +637 -0
  40. package/src/services/errorHandler.js +810 -0
  41. package/src/services/fileAttachmentService.js +544 -0
  42. package/src/services/modelRouterService.js +366 -0
  43. package/src/services/modelsService.js +322 -0
  44. package/src/services/qualityInspector.js +796 -0
  45. package/src/services/tokenCountingService.js +536 -0
  46. package/src/tools/agentCommunicationTool.js +1344 -0
  47. package/src/tools/agentDelayTool.js +485 -0
  48. package/src/tools/asyncToolManager.js +604 -0
  49. package/src/tools/baseTool.js +800 -0
  50. package/src/tools/browserTool.js +920 -0
  51. package/src/tools/cloneDetectionTool.js +621 -0
  52. package/src/tools/dependencyResolverTool.js +1215 -0
  53. package/src/tools/fileContentReplaceTool.js +875 -0
  54. package/src/tools/fileSystemTool.js +1107 -0
  55. package/src/tools/fileTreeTool.js +853 -0
  56. package/src/tools/imageTool.js +901 -0
  57. package/src/tools/importAnalyzerTool.js +1060 -0
  58. package/src/tools/jobDoneTool.js +248 -0
  59. package/src/tools/seekTool.js +956 -0
  60. package/src/tools/staticAnalysisTool.js +1778 -0
  61. package/src/tools/taskManagerTool.js +2873 -0
  62. package/src/tools/terminalTool.js +2304 -0
  63. package/src/tools/webTool.js +1430 -0
  64. package/src/types/agent.js +519 -0
  65. package/src/types/contextReference.js +972 -0
  66. package/src/types/conversation.js +730 -0
  67. package/src/types/toolCommand.js +747 -0
  68. package/src/utilities/attachmentValidator.js +292 -0
  69. package/src/utilities/configManager.js +582 -0
  70. package/src/utilities/constants.js +722 -0
  71. package/src/utilities/directoryAccessManager.js +535 -0
  72. package/src/utilities/fileProcessor.js +307 -0
  73. package/src/utilities/logger.js +436 -0
  74. package/src/utilities/tagParser.js +1246 -0
  75. package/src/utilities/toolConstants.js +317 -0
  76. package/web-ui/build/index.html +15 -0
  77. package/web-ui/build/logo.png +0 -0
  78. package/web-ui/build/logo2.png +0 -0
  79. package/web-ui/build/static/index-CjkkcnFA.js +344 -0
  80. 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;