deepdebug-local-agent 0.3.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 (50) hide show
  1. package/.dockerignore +24 -0
  2. package/.idea/deepdebug-local-agent.iml +12 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/Dockerfile +46 -0
  6. package/cloudbuild.yaml +42 -0
  7. package/index.js +42 -0
  8. package/mcp-server.js +533 -0
  9. package/package.json +22 -0
  10. package/src/ai-engine.js +861 -0
  11. package/src/analyzers/config-analyzer.js +446 -0
  12. package/src/analyzers/controller-analyzer.js +429 -0
  13. package/src/analyzers/dto-analyzer.js +455 -0
  14. package/src/detectors/build-tool-detector.js +0 -0
  15. package/src/detectors/framework-detector.js +91 -0
  16. package/src/detectors/language-detector.js +89 -0
  17. package/src/detectors/multi-project-detector.js +191 -0
  18. package/src/detectors/service-detector.js +244 -0
  19. package/src/detectors.js +30 -0
  20. package/src/exec-utils.js +215 -0
  21. package/src/fs-utils.js +34 -0
  22. package/src/git/base-git-provider.js +384 -0
  23. package/src/git/git-provider-registry.js +110 -0
  24. package/src/git/github-provider.js +502 -0
  25. package/src/mcp-http-server.js +313 -0
  26. package/src/patch/patch-engine.js +339 -0
  27. package/src/patch-manager.js +816 -0
  28. package/src/patch.js +607 -0
  29. package/src/patch_bkp.js +154 -0
  30. package/src/ports.js +69 -0
  31. package/src/routes/workspace.route.js +528 -0
  32. package/src/runtimes/base-runtime.js +290 -0
  33. package/src/runtimes/java/gradle-runtime.js +378 -0
  34. package/src/runtimes/java/java-integrations.js +339 -0
  35. package/src/runtimes/java/maven-runtime.js +418 -0
  36. package/src/runtimes/node/node-integrations.js +247 -0
  37. package/src/runtimes/node/npm-runtime.js +466 -0
  38. package/src/runtimes/node/yarn-runtime.js +354 -0
  39. package/src/runtimes/runtime-registry.js +256 -0
  40. package/src/server-local.js +576 -0
  41. package/src/server.js +4565 -0
  42. package/src/utils/environment-diagnostics.js +666 -0
  43. package/src/utils/exec-utils.js +264 -0
  44. package/src/utils/fs-utils.js +218 -0
  45. package/src/workspace/detect-port.js +176 -0
  46. package/src/workspace/file-reader.js +54 -0
  47. package/src/workspace/git-client.js +0 -0
  48. package/src/workspace/process-manager.js +619 -0
  49. package/src/workspace/scanner.js +72 -0
  50. package/src/workspace-manager.js +172 -0
@@ -0,0 +1,576 @@
1
+ // ============================================
2
+ // 🆕 ADICIONAR ESTES ENDPOINTS AO server-local.js
3
+ // Colocar ANTES da seção "START SERVER"
4
+ // NÃO modificar endpoints existentes
5
+ // ============================================
6
+
7
+ // ============================================
8
+ // 🆕 DRY-RUN ENDPOINT (Sprint 1.3)
9
+ // Valida e simula patch SEM modificar arquivos
10
+ // ============================================
11
+
12
+ /**
13
+ * POST /workspace/patch/dry-run
14
+ * Validates and simulates patch application WITHOUT modifying files
15
+ * Useful for preview before actually applying
16
+ *
17
+ * Body: { "diff": "unified diff text" }
18
+ *
19
+ * Response:
20
+ * {
21
+ * "ok": true,
22
+ * "valid": true,
23
+ * "canApply": true,
24
+ * "wouldModify": ["src/Main.java"],
25
+ * "hunksCount": 3,
26
+ * "fileChecks": [{ "path": "...", "exists": true, "lineCount": 100 }],
27
+ * "warnings": []
28
+ * }
29
+ */
30
+ app.post("/workspace/patch/dry-run", async (req, res) => {
31
+ if (!WORKSPACE_ROOT) {
32
+ return res.status(400).json({
33
+ error: "workspace not set",
34
+ hint: "call POST /workspace/open first"
35
+ });
36
+ }
37
+
38
+ const { diff } = req.body || {};
39
+ if (!diff) return res.status(400).json({ error: "diff is required" });
40
+
41
+ console.log(`🔍 Dry-run patch validation requested`);
42
+
43
+ try {
44
+ // 1. Validate diff format
45
+ const validation = validateDiff(diff);
46
+ if (!validation.valid) {
47
+ return res.json({
48
+ ok: true,
49
+ valid: false,
50
+ canApply: false,
51
+ errors: validation.errors,
52
+ wouldModify: [],
53
+ hunksCount: 0
54
+ });
55
+ }
56
+
57
+ // 2. Extract target files
58
+ const targetFiles = extractTargetFiles(diff);
59
+
60
+ // 3. Check if files exist and get line counts
61
+ const fileChecks = await Promise.all(
62
+ targetFiles.map(async (relPath) => {
63
+ const fullPath = path.join(WORKSPACE_ROOT, relPath);
64
+ const fileExists = await exists(fullPath);
65
+ let lineCount = 0;
66
+ let size = 0;
67
+
68
+ if (fileExists) {
69
+ try {
70
+ const content = await readFile(fullPath, 'utf8');
71
+ lineCount = content.split('\n').length;
72
+ size = Buffer.byteLength(content, 'utf8');
73
+ } catch (e) {
74
+ // File exists but can't be read
75
+ }
76
+ }
77
+
78
+ return {
79
+ path: relPath,
80
+ exists: fileExists,
81
+ lineCount,
82
+ size
83
+ };
84
+ })
85
+ );
86
+
87
+ // 4. Count hunks
88
+ const hunkMatches = diff.match(/@@ -\d+,?\d* \+\d+,?\d* @@/g) || [];
89
+ const hunkCount = hunkMatches.length;
90
+
91
+ // 5. Analyze changes
92
+ const addedLines = (diff.match(/^\+[^+]/gm) || []).length;
93
+ const removedLines = (diff.match(/^-[^-]/gm) || []).length;
94
+
95
+ // 6. Check if all files exist (for modification patches)
96
+ const isNewFile = diff.includes('--- /dev/null');
97
+ const allFilesExist = fileChecks.every(f => f.exists) || isNewFile;
98
+ const missingFiles = fileChecks.filter(f => !f.exists && !isNewFile);
99
+
100
+ console.log(`✅ Dry-run complete: ${targetFiles.length} files, ${hunkCount} hunks, +${addedLines}/-${removedLines} lines`);
101
+
102
+ res.json({
103
+ ok: true,
104
+ valid: true,
105
+ canApply: allFilesExist,
106
+ wouldModify: targetFiles,
107
+ hunksCount: hunkCount,
108
+ changes: {
109
+ addedLines,
110
+ removedLines,
111
+ netChange: addedLines - removedLines
112
+ },
113
+ fileChecks,
114
+ missingFiles: missingFiles.map(f => f.path),
115
+ isNewFile,
116
+ warnings: missingFiles.length > 0 ? [`${missingFiles.length} file(s) not found in workspace`] : []
117
+ });
118
+ } catch (err) {
119
+ console.error(`❌ Dry-run error: ${err.message}`);
120
+ res.status(500).json({ ok: false, error: err.message });
121
+ }
122
+ });
123
+
124
+ /**
125
+ * DELETE /workspace/backups/:backupId
126
+ * Deletes a specific backup
127
+ */
128
+ app.delete("/workspace/backups/:backupId", (req, res) => {
129
+ const { backupId } = req.params;
130
+
131
+ if (!BACKUPS.has(backupId)) {
132
+ return res.status(404).json({
133
+ error: "backup not found",
134
+ availableBackups: Array.from(BACKUPS.keys())
135
+ });
136
+ }
137
+
138
+ const backup = BACKUPS.get(backupId);
139
+ BACKUPS.delete(backupId);
140
+
141
+ console.log(`🗑️ Backup deleted: ${backupId}`);
142
+
143
+ res.json({
144
+ ok: true,
145
+ deleted: backupId,
146
+ deletedFiles: backup.files.map(f => f.path)
147
+ });
148
+ });
149
+
150
+ // ============================================
151
+ // 🆕 DETECT PORT ENDPOINT (Sprint 1.2)
152
+ // Multi-language, multi-framework port detection
153
+ // ============================================
154
+
155
+ /**
156
+ * GET /workspace/detect-port
157
+ * Detects port from service configuration files
158
+ * Supports: Java (Spring Boot), Node.js, Python, .NET, Go, Ruby
159
+ *
160
+ * Query params:
161
+ * - servicePath: relative path to service (optional, defaults to workspace root)
162
+ * - language: java, node, python, dotnet, go, ruby (optional, auto-detect)
163
+ * - framework: spring-boot, express, flask, fastapi, etc (optional)
164
+ */
165
+ app.get("/workspace/detect-port", async (req, res) => {
166
+ if (!WORKSPACE_ROOT) {
167
+ return res.status(400).json({
168
+ error: "workspace not set",
169
+ hint: "call POST /workspace/open first"
170
+ });
171
+ }
172
+
173
+ const { servicePath = '', language, framework } = req.query;
174
+
175
+ try {
176
+ const fullPath = servicePath ? path.join(WORKSPACE_ROOT, servicePath) : WORKSPACE_ROOT;
177
+ console.log(`🔍 Detecting port for service at: ${fullPath}`);
178
+
179
+ let port = null;
180
+ let detectionMethod = null;
181
+ let detectedLanguage = language;
182
+
183
+ // Auto-detect language if not provided
184
+ if (!detectedLanguage) {
185
+ const meta = await detectProject(fullPath);
186
+ detectedLanguage = meta.language;
187
+ }
188
+
189
+ // Detect port based on language/framework
190
+ if (detectedLanguage === 'java') {
191
+ const result = await detectSpringBootPortLocal(fullPath);
192
+ port = result.port;
193
+ detectionMethod = result.method;
194
+ } else if (['node', 'javascript', 'typescript'].includes(detectedLanguage)) {
195
+ const result = await detectNodePortLocal(fullPath);
196
+ port = result.port;
197
+ detectionMethod = result.method;
198
+ } else if (detectedLanguage === 'python') {
199
+ const result = await detectPythonPortLocal(fullPath);
200
+ port = result.port;
201
+ detectionMethod = result.method;
202
+ } else if (['dotnet', 'csharp'].includes(detectedLanguage)) {
203
+ const result = await detectDotNetPortLocal(fullPath);
204
+ port = result.port;
205
+ detectionMethod = result.method;
206
+ } else if (detectedLanguage === 'go') {
207
+ const result = await detectGoPortLocal(fullPath);
208
+ port = result.port;
209
+ detectionMethod = result.method;
210
+ } else if (detectedLanguage === 'ruby') {
211
+ const result = await detectRubyPortLocal(fullPath);
212
+ port = result.port;
213
+ detectionMethod = result.method;
214
+ } else {
215
+ // Fallback to global detection
216
+ port = await detectPort(fullPath);
217
+ detectionMethod = 'global-detection';
218
+ }
219
+
220
+ console.log(`✅ Detected port: ${port || 'default'} via ${detectionMethod}`);
221
+
222
+ res.json({
223
+ ok: true,
224
+ port,
225
+ language: detectedLanguage,
226
+ framework: framework || null,
227
+ detectionMethod,
228
+ servicePath: servicePath || '/',
229
+ workspaceRoot: WORKSPACE_ROOT
230
+ });
231
+ } catch (err) {
232
+ console.error('❌ Error detecting port:', err.message);
233
+ res.status(500).json({ ok: false, error: err.message });
234
+ }
235
+ });
236
+
237
+ // ============================================
238
+ // PORT DETECTION HELPER FUNCTIONS
239
+ // ============================================
240
+
241
+ /**
242
+ * Detects Spring Boot port from configuration files
243
+ */
244
+ async function detectSpringBootPortLocal(servicePath) {
245
+ const candidates = [
246
+ path.join(servicePath, 'src/main/resources/application.yml'),
247
+ path.join(servicePath, 'src/main/resources/application.yaml'),
248
+ path.join(servicePath, 'src/main/resources/application.properties'),
249
+ path.join(servicePath, 'src/main/resources/application-local.yml'),
250
+ path.join(servicePath, 'src/main/resources/application-local.yaml'),
251
+ path.join(servicePath, 'src/main/resources/application-local.properties'),
252
+ path.join(servicePath, 'src/main/resources/application-dev.yml'),
253
+ path.join(servicePath, 'src/main/resources/application-dev.properties'),
254
+ path.join(servicePath, 'application.yml'),
255
+ path.join(servicePath, 'application.yaml'),
256
+ path.join(servicePath, 'application.properties')
257
+ ];
258
+
259
+ for (const filePath of candidates) {
260
+ if (await exists(filePath)) {
261
+ try {
262
+ const content = await readFile(filePath, 'utf8');
263
+
264
+ // YAML format - multiple patterns
265
+ if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
266
+ // Pattern: server:\n port: 8090
267
+ const simpleMatch = content.match(/server:\s*\n\s+port:\s*(\d+)/);
268
+ if (simpleMatch) {
269
+ return { port: parseInt(simpleMatch[1]), method: `yaml:${path.basename(filePath)}` };
270
+ }
271
+
272
+ // Pattern: ${PORT:8080} or ${SERVER_PORT:8080}
273
+ const envMatch = content.match(/port:\s*\$\{[A-Z_]+:(\d+)\}/);
274
+ if (envMatch) {
275
+ return { port: parseInt(envMatch[1]), method: `yaml-env-default:${path.basename(filePath)}` };
276
+ }
277
+
278
+ // Pattern: port: 8090 (simple)
279
+ const directMatch = content.match(/^\s*port:\s*(\d+)/m);
280
+ if (directMatch) {
281
+ return { port: parseInt(directMatch[1]), method: `yaml-direct:${path.basename(filePath)}` };
282
+ }
283
+ }
284
+
285
+ // Properties format
286
+ if (filePath.endsWith('.properties')) {
287
+ const match = content.match(/server\.port\s*=\s*(\d+)/);
288
+ if (match) {
289
+ return { port: parseInt(match[1]), method: `properties:${path.basename(filePath)}` };
290
+ }
291
+
292
+ // ${PORT:8080} pattern
293
+ const envMatch = content.match(/server\.port\s*=\s*\$\{[A-Z_]+:(\d+)\}/);
294
+ if (envMatch) {
295
+ return { port: parseInt(envMatch[1]), method: `properties-env-default:${path.basename(filePath)}` };
296
+ }
297
+ }
298
+ } catch (e) {
299
+ continue;
300
+ }
301
+ }
302
+ }
303
+
304
+ return { port: 8080, method: 'spring-boot-default' };
305
+ }
306
+
307
+ /**
308
+ * Detects Node.js port from configuration files
309
+ */
310
+ async function detectNodePortLocal(servicePath) {
311
+ // Priority 1: .env files
312
+ const envFiles = ['.env.local', '.env.development', '.env'];
313
+ for (const envFile of envFiles) {
314
+ const envPath = path.join(servicePath, envFile);
315
+ if (await exists(envPath)) {
316
+ try {
317
+ const content = await readFile(envPath, 'utf8');
318
+ const match = content.match(/^PORT\s*=\s*(\d+)/mi);
319
+ if (match) {
320
+ return { port: parseInt(match[1]), method: `dotenv:${envFile}` };
321
+ }
322
+ } catch (e) {
323
+ continue;
324
+ }
325
+ }
326
+ }
327
+
328
+ // Priority 2: package.json scripts
329
+ const pkgPath = path.join(servicePath, 'package.json');
330
+ if (await exists(pkgPath)) {
331
+ try {
332
+ const content = await readFile(pkgPath, 'utf8');
333
+ const pkg = JSON.parse(content);
334
+ const scripts = JSON.stringify(pkg.scripts || {});
335
+
336
+ // Patterns: --port 3000, --port=3000, PORT=3000, -p 3000
337
+ const match = scripts.match(/--port[=\s]+(\d+)|PORT[=\s]+(\d+)|-p\s+(\d+)/i);
338
+ if (match) {
339
+ const port = parseInt(match[1] || match[2] || match[3]);
340
+ return { port, method: 'package-json-scripts' };
341
+ }
342
+
343
+ // Check for port in config
344
+ if (pkg.config?.port) {
345
+ return { port: parseInt(pkg.config.port), method: 'package-json-config' };
346
+ }
347
+ } catch (e) {
348
+ // Continue to default
349
+ }
350
+ }
351
+
352
+ // Priority 3: Check for common config files
353
+ const configFiles = ['config.json', 'config/default.json', '.env.example'];
354
+ for (const configFile of configFiles) {
355
+ const configPath = path.join(servicePath, configFile);
356
+ if (await exists(configPath)) {
357
+ try {
358
+ const content = await readFile(configPath, 'utf8');
359
+ const match = content.match(/["']?port["']?\s*[=:]\s*(\d+)/i);
360
+ if (match) {
361
+ return { port: parseInt(match[1]), method: `config:${configFile}` };
362
+ }
363
+ } catch (e) {
364
+ continue;
365
+ }
366
+ }
367
+ }
368
+
369
+ return { port: 3000, method: 'node-default' };
370
+ }
371
+
372
+ /**
373
+ * Detects Python port from configuration files
374
+ */
375
+ async function detectPythonPortLocal(servicePath) {
376
+ // Priority 1: .env files
377
+ const envFiles = ['.env', '.env.local', '.env.development'];
378
+ for (const envFile of envFiles) {
379
+ const envPath = path.join(servicePath, envFile);
380
+ if (await exists(envPath)) {
381
+ try {
382
+ const content = await readFile(envPath, 'utf8');
383
+ const match = content.match(/^PORT\s*=\s*(\d+)/mi);
384
+ if (match) {
385
+ return { port: parseInt(match[1]), method: `dotenv:${envFile}` };
386
+ }
387
+ } catch (e) {
388
+ continue;
389
+ }
390
+ }
391
+ }
392
+
393
+ // Priority 2: Python config files
394
+ const configFiles = [
395
+ 'config.py', 'settings.py', 'app/config.py', 'src/config.py',
396
+ 'config/settings.py', 'core/settings.py'
397
+ ];
398
+ for (const configFile of configFiles) {
399
+ const configPath = path.join(servicePath, configFile);
400
+ if (await exists(configPath)) {
401
+ try {
402
+ const content = await readFile(configPath, 'utf8');
403
+ const match = content.match(/PORT\s*=\s*(\d+)/i);
404
+ if (match) {
405
+ return { port: parseInt(match[1]), method: `python-config:${configFile}` };
406
+ }
407
+ } catch (e) {
408
+ continue;
409
+ }
410
+ }
411
+ }
412
+
413
+ // Priority 3: Check for Dockerfile with EXPOSE
414
+ const dockerfilePath = path.join(servicePath, 'Dockerfile');
415
+ if (await exists(dockerfilePath)) {
416
+ try {
417
+ const content = await readFile(dockerfilePath, 'utf8');
418
+ const match = content.match(/EXPOSE\s+(\d+)/);
419
+ if (match) {
420
+ return { port: parseInt(match[1]), method: 'dockerfile-expose' };
421
+ }
422
+ } catch (e) {
423
+ // Continue to default
424
+ }
425
+ }
426
+
427
+ return { port: 8000, method: 'python-default' };
428
+ }
429
+
430
+ /**
431
+ * Detects .NET port from launchSettings.json
432
+ */
433
+ async function detectDotNetPortLocal(servicePath) {
434
+ // Priority 1: launchSettings.json
435
+ const launchSettings = path.join(servicePath, 'Properties/launchSettings.json');
436
+ if (await exists(launchSettings)) {
437
+ try {
438
+ const content = await readFile(launchSettings, 'utf8');
439
+ const settings = JSON.parse(content);
440
+
441
+ const profiles = settings.profiles || {};
442
+ for (const [profileName, profile] of Object.entries(profiles)) {
443
+ if (profile.applicationUrl) {
444
+ const match = profile.applicationUrl.match(/:(\d+)/);
445
+ if (match) {
446
+ return { port: parseInt(match[1]), method: `launchSettings:${profileName}` };
447
+ }
448
+ }
449
+ }
450
+ } catch (e) {
451
+ // Continue to next method
452
+ }
453
+ }
454
+
455
+ // Priority 2: appsettings.json
456
+ const appSettingsFiles = ['appsettings.Development.json', 'appsettings.json'];
457
+ for (const settingsFile of appSettingsFiles) {
458
+ const appSettings = path.join(servicePath, settingsFile);
459
+ if (await exists(appSettings)) {
460
+ try {
461
+ const content = await readFile(appSettings, 'utf8');
462
+ const settings = JSON.parse(content);
463
+
464
+ if (settings.Kestrel?.Endpoints?.Http?.Url) {
465
+ const match = settings.Kestrel.Endpoints.Http.Url.match(/:(\d+)/);
466
+ if (match) {
467
+ return { port: parseInt(match[1]), method: `appsettings:${settingsFile}` };
468
+ }
469
+ }
470
+ } catch (e) {
471
+ continue;
472
+ }
473
+ }
474
+ }
475
+
476
+ return { port: 5000, method: 'dotnet-default' };
477
+ }
478
+
479
+ /**
480
+ * Detects Go port from configuration
481
+ */
482
+ async function detectGoPortLocal(servicePath) {
483
+ // Priority 1: .env files
484
+ const envPath = path.join(servicePath, '.env');
485
+ if (await exists(envPath)) {
486
+ try {
487
+ const content = await readFile(envPath, 'utf8');
488
+ const match = content.match(/^PORT\s*=\s*(\d+)/mi);
489
+ if (match) {
490
+ return { port: parseInt(match[1]), method: 'dotenv' };
491
+ }
492
+ } catch (e) {
493
+ // Continue
494
+ }
495
+ }
496
+
497
+ // Priority 2: config.yaml/config.json
498
+ const configFiles = ['config.yaml', 'config.yml', 'config.json', 'configs/config.yaml'];
499
+ for (const configFile of configFiles) {
500
+ const configPath = path.join(servicePath, configFile);
501
+ if (await exists(configPath)) {
502
+ try {
503
+ const content = await readFile(configPath, 'utf8');
504
+ const match = content.match(/port['"]*\s*[=:]\s*['"]*(\d+)/i);
505
+ if (match) {
506
+ return { port: parseInt(match[1]), method: `config:${configFile}` };
507
+ }
508
+ } catch (e) {
509
+ continue;
510
+ }
511
+ }
512
+ }
513
+
514
+ return { port: 8080, method: 'go-default' };
515
+ }
516
+
517
+ /**
518
+ * Detects Ruby/Rails port from configuration
519
+ */
520
+ async function detectRubyPortLocal(servicePath) {
521
+ // Priority 1: .env
522
+ const envPath = path.join(servicePath, '.env');
523
+ if (await exists(envPath)) {
524
+ try {
525
+ const content = await readFile(envPath, 'utf8');
526
+ const match = content.match(/^PORT\s*=\s*(\d+)/mi);
527
+ if (match) {
528
+ return { port: parseInt(match[1]), method: 'dotenv' };
529
+ }
530
+ } catch (e) {
531
+ // Continue
532
+ }
533
+ }
534
+
535
+ // Priority 2: Puma config
536
+ const pumaConfig = path.join(servicePath, 'config/puma.rb');
537
+ if (await exists(pumaConfig)) {
538
+ try {
539
+ const content = await readFile(pumaConfig, 'utf8');
540
+ const match = content.match(/port\s+(\d+)/i);
541
+ if (match) {
542
+ return { port: parseInt(match[1]), method: 'puma-config' };
543
+ }
544
+ } catch (e) {
545
+ // Continue
546
+ }
547
+ }
548
+
549
+ return { port: 3000, method: 'ruby-default' };
550
+ }
551
+
552
+ // ============================================
553
+ // HELPER FUNCTIONS (if not already present)
554
+ // ============================================
555
+
556
+ /**
557
+ * Helper: Extract target files from diff
558
+ * (Add only if not already present in server-local.js)
559
+ */
560
+ function extractTargetFiles(diff) {
561
+ const files = [];
562
+ const lines = diff.split('\n');
563
+
564
+ for (const line of lines) {
565
+ if (line.startsWith('+++ ')) {
566
+ let filePath = line.substring(4).trim();
567
+ // Remove prefixes like b/ or ./
568
+ filePath = filePath.replace(/^[ab]\//, '').replace(/^\.\//,'');
569
+ if (filePath && filePath !== '/dev/null') {
570
+ files.push(filePath);
571
+ }
572
+ }
573
+ }
574
+
575
+ return files;
576
+ }