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,290 @@
1
+ /**
2
+ * BaseRuntime
3
+ *
4
+ * Interface abstrata para todos os runtimes de linguagem.
5
+ * Cada linguagem (Java, Node, Python, etc.) deve implementar esta interface.
6
+ *
7
+ * @abstract
8
+ */
9
+ export class BaseRuntime {
10
+
11
+ constructor() {
12
+ if (this.constructor === BaseRuntime) {
13
+ throw new Error('BaseRuntime is abstract and cannot be instantiated directly');
14
+ }
15
+ }
16
+
17
+ // ==========================================
18
+ // IDENTIFICATION
19
+ // ==========================================
20
+
21
+ /**
22
+ * Identificador único do runtime
23
+ * @returns {string} Ex: 'java-maven', 'node-npm', 'python-pip'
24
+ */
25
+ getId() {
26
+ throw new Error('Method getId() must be implemented');
27
+ }
28
+
29
+ /**
30
+ * Nome amigável do runtime
31
+ * @returns {string} Ex: 'Java (Maven)', 'Node.js (npm)'
32
+ */
33
+ getName() {
34
+ throw new Error('Method getName() must be implemented');
35
+ }
36
+
37
+ /**
38
+ * Linguagem do runtime
39
+ * @returns {string} Ex: 'java', 'node', 'python', 'dotnet', 'go'
40
+ */
41
+ getLanguage() {
42
+ throw new Error('Method getLanguage() must be implemented');
43
+ }
44
+
45
+ /**
46
+ * Build tool usado
47
+ * @returns {string} Ex: 'maven', 'gradle', 'npm', 'yarn', 'pip'
48
+ */
49
+ getBuildTool() {
50
+ throw new Error('Method getBuildTool() must be implemented');
51
+ }
52
+
53
+ // ==========================================
54
+ // DETECTION
55
+ // ==========================================
56
+
57
+ /**
58
+ * Arquivos marcadores que indicam este runtime
59
+ * @returns {string[]} Ex: ['pom.xml'], ['package.json']
60
+ */
61
+ getMarkerFiles() {
62
+ throw new Error('Method getMarkerFiles() must be implemented');
63
+ }
64
+
65
+ /**
66
+ * Verifica se este runtime é compatível com o workspace
67
+ * @param {string} workspacePath - Caminho do workspace
68
+ * @returns {Promise<boolean>}
69
+ */
70
+ async isCompatible(workspacePath) {
71
+ throw new Error('Method isCompatible() must be implemented');
72
+ }
73
+
74
+ /**
75
+ * Detecta informações do projeto
76
+ * @param {string} workspacePath - Caminho do workspace
77
+ * @returns {Promise<ProjectInfo>}
78
+ */
79
+ async detectProject(workspacePath) {
80
+ throw new Error('Method detectProject() must be implemented');
81
+ }
82
+
83
+ // ==========================================
84
+ // BUILD & RUN
85
+ // ==========================================
86
+
87
+ /**
88
+ * Instala dependências do projeto
89
+ * @param {string} workspacePath - Caminho do workspace
90
+ * @param {object} options - Opções adicionais
91
+ * @returns {Promise<CommandResult>}
92
+ */
93
+ async install(workspacePath, options = {}) {
94
+ throw new Error('Method install() must be implemented');
95
+ }
96
+
97
+ /**
98
+ * Compila o projeto
99
+ * @param {string} workspacePath - Caminho do workspace
100
+ * @param {object} options - Opções adicionais
101
+ * @returns {Promise<CommandResult>}
102
+ */
103
+ async build(workspacePath, options = {}) {
104
+ throw new Error('Method build() must be implemented');
105
+ }
106
+
107
+ /**
108
+ * Retorna o comando para iniciar o serviço
109
+ * @param {object} options - Opções (port, profile, etc.)
110
+ * @returns {StartCommand} { cmd: string, args: string[], env?: object }
111
+ */
112
+ getStartCommand(options = {}) {
113
+ throw new Error('Method getStartCommand() must be implemented');
114
+ }
115
+
116
+ /**
117
+ * Padrão regex para detectar quando o serviço está pronto
118
+ * @returns {RegExp}
119
+ */
120
+ getReadyPattern() {
121
+ throw new Error('Method getReadyPattern() must be implemented');
122
+ }
123
+
124
+ /**
125
+ * Porta padrão do framework
126
+ * @returns {number}
127
+ */
128
+ getDefaultPort() {
129
+ return 8080;
130
+ }
131
+
132
+ /**
133
+ * Detecta a porta configurada no projeto
134
+ * @param {string} workspacePath - Caminho do workspace
135
+ * @returns {Promise<number|null>}
136
+ */
137
+ async detectPort(workspacePath) {
138
+ return this.getDefaultPort();
139
+ }
140
+
141
+ // ==========================================
142
+ // TEST
143
+ // ==========================================
144
+
145
+ /**
146
+ * Executa testes do projeto
147
+ * @param {string} workspacePath - Caminho do workspace
148
+ * @param {object} options - Opções (testFile, coverage, etc.)
149
+ * @returns {Promise<TestResult>}
150
+ */
151
+ async test(workspacePath, options = {}) {
152
+ throw new Error('Method test() must be implemented');
153
+ }
154
+
155
+ /**
156
+ * Executa um teste específico
157
+ * @param {string} workspacePath - Caminho do workspace
158
+ * @param {string} testFile - Arquivo de teste
159
+ * @returns {Promise<TestResult>}
160
+ */
161
+ async testSingle(workspacePath, testFile) {
162
+ throw new Error('Method testSingle() must be implemented');
163
+ }
164
+
165
+ // ==========================================
166
+ // CODE ANALYSIS
167
+ // ==========================================
168
+
169
+ /**
170
+ * Extensões de arquivo suportadas
171
+ * @returns {string[]} Ex: ['.java'], ['.js', '.ts']
172
+ */
173
+ getFileExtensions() {
174
+ throw new Error('Method getFileExtensions() must be implemented');
175
+ }
176
+
177
+ /**
178
+ * Encontra integrações HTTP no código
179
+ * @param {string} workspacePath - Caminho do workspace
180
+ * @returns {Promise<Integration[]>}
181
+ */
182
+ async findIntegrations(workspacePath) {
183
+ return [];
184
+ }
185
+
186
+ /**
187
+ * Analisa imports/dependências de um arquivo
188
+ * @param {string} filePath - Caminho do arquivo
189
+ * @param {string} content - Conteúdo do arquivo
190
+ * @returns {Promise<Dependency[]>}
191
+ */
192
+ async analyzeImports(filePath, content) {
193
+ return [];
194
+ }
195
+
196
+ // ==========================================
197
+ // PATCH
198
+ // ==========================================
199
+
200
+ /**
201
+ * Valida se um diff é aplicável
202
+ * @param {string} workspacePath - Caminho do workspace
203
+ * @param {string} diff - Unified diff
204
+ * @returns {Promise<ValidationResult>}
205
+ */
206
+ async validatePatch(workspacePath, diff) {
207
+ return { valid: true, errors: [] };
208
+ }
209
+
210
+ /**
211
+ * Ações pós-patch (ex: reformatar código)
212
+ * @param {string} workspacePath - Caminho do workspace
213
+ * @param {string[]} modifiedFiles - Arquivos modificados
214
+ * @returns {Promise<void>}
215
+ */
216
+ async postPatchActions(workspacePath, modifiedFiles) {
217
+ // Override nas implementações específicas
218
+ }
219
+ }
220
+
221
+ // ==========================================
222
+ // TYPE DEFINITIONS
223
+ // ==========================================
224
+
225
+ /**
226
+ * @typedef {object} ProjectInfo
227
+ * @property {string} name - Nome do projeto
228
+ * @property {string} version - Versão
229
+ * @property {string} language - Linguagem
230
+ * @property {string} framework - Framework detectado
231
+ * @property {string} buildTool - Build tool
232
+ * @property {number} port - Porta configurada
233
+ */
234
+
235
+ /**
236
+ * @typedef {object} CommandResult
237
+ * @property {number} code - Exit code
238
+ * @property {string} stdout - Standard output
239
+ * @property {string} stderr - Standard error
240
+ * @property {number} duration - Duração em ms
241
+ */
242
+
243
+ /**
244
+ * @typedef {object} StartCommand
245
+ * @property {string} cmd - Comando
246
+ * @property {string[]} args - Argumentos
247
+ * @property {object} [env] - Variáveis de ambiente adicionais
248
+ */
249
+
250
+ /**
251
+ * @typedef {object} TestResult
252
+ * @property {boolean} success - Se todos os testes passaram
253
+ * @property {number} passed - Número de testes que passaram
254
+ * @property {number} failed - Número de testes que falharam
255
+ * @property {number} skipped - Número de testes ignorados
256
+ * @property {number} duration - Duração em ms
257
+ * @property {string} output - Output completo
258
+ * @property {TestFailure[]} failures - Lista de falhas
259
+ */
260
+
261
+ /**
262
+ * @typedef {object} TestFailure
263
+ * @property {string} testName - Nome do teste
264
+ * @property {string} message - Mensagem de erro
265
+ * @property {string} stackTrace - Stack trace
266
+ */
267
+
268
+ /**
269
+ * @typedef {object} Integration
270
+ * @property {string} type - Tipo (rest-template, feign, axios, etc.)
271
+ * @property {string} file - Arquivo onde foi encontrado
272
+ * @property {number} line - Linha
273
+ * @property {string} targetUrl - URL alvo (se detectável)
274
+ * @property {string} method - Método HTTP (se detectável)
275
+ */
276
+
277
+ /**
278
+ * @typedef {object} Dependency
279
+ * @property {string} name - Nome da dependência
280
+ * @property {string} type - Tipo (import, require, using)
281
+ * @property {boolean} isExternal - Se é externa ao projeto
282
+ */
283
+
284
+ /**
285
+ * @typedef {object} ValidationResult
286
+ * @property {boolean} valid - Se o patch é válido
287
+ * @property {string[]} errors - Lista de erros
288
+ */
289
+
290
+ export default BaseRuntime;
@@ -0,0 +1,378 @@
1
+ import path from 'path';
2
+ import { BaseRuntime } from '../base-runtime.js';
3
+ import { exists, readFile } from '../../utils/fs-utils.js';
4
+ import { run } from '../../utils/exec-utils.js';
5
+
6
+ /**
7
+ * GradleRuntime
8
+ *
9
+ * Runtime para projetos Java com Gradle.
10
+ * Suporta Spring Boot, Quarkus, e projetos Gradle genéricos.
11
+ */
12
+ export class GradleRuntime extends BaseRuntime {
13
+
14
+ constructor() {
15
+ super();
16
+ this.framework = null;
17
+ this.useWrapper = true; // Usar ./gradlew por padrão
18
+ }
19
+
20
+ // ==========================================
21
+ // IDENTIFICATION
22
+ // ==========================================
23
+
24
+ getId() {
25
+ return 'java-gradle';
26
+ }
27
+
28
+ getName() {
29
+ return 'Java (Gradle)';
30
+ }
31
+
32
+ getLanguage() {
33
+ return 'java';
34
+ }
35
+
36
+ getBuildTool() {
37
+ return 'gradle';
38
+ }
39
+
40
+ // ==========================================
41
+ // DETECTION
42
+ // ==========================================
43
+
44
+ getMarkerFiles() {
45
+ return ['build.gradle', 'build.gradle.kts'];
46
+ }
47
+
48
+ async isCompatible(workspacePath) {
49
+ const groovyPath = path.join(workspacePath, 'build.gradle');
50
+ const kotlinPath = path.join(workspacePath, 'build.gradle.kts');
51
+
52
+ return await exists(groovyPath) || await exists(kotlinPath);
53
+ }
54
+
55
+ async detectProject(workspacePath) {
56
+ let buildContent = '';
57
+
58
+ const groovyPath = path.join(workspacePath, 'build.gradle');
59
+ const kotlinPath = path.join(workspacePath, 'build.gradle.kts');
60
+
61
+ if (await exists(groovyPath)) {
62
+ buildContent = await readFile(groovyPath, 'utf8');
63
+ } else if (await exists(kotlinPath)) {
64
+ buildContent = await readFile(kotlinPath, 'utf8');
65
+ }
66
+
67
+ // Verificar se existe wrapper
68
+ const wrapperPath = path.join(workspacePath, 'gradlew');
69
+ this.useWrapper = await exists(wrapperPath);
70
+
71
+ // Detectar nome do projeto
72
+ const settingsPath = path.join(workspacePath, 'settings.gradle');
73
+ const settingsKtsPath = path.join(workspacePath, 'settings.gradle.kts');
74
+ let name = path.basename(workspacePath);
75
+
76
+ if (await exists(settingsPath)) {
77
+ const settings = await readFile(settingsPath, 'utf8');
78
+ const match = settings.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
79
+ if (match) name = match[1];
80
+ } else if (await exists(settingsKtsPath)) {
81
+ const settings = await readFile(settingsKtsPath, 'utf8');
82
+ const match = settings.match(/rootProject\.name\s*=\s*"([^"]+)"/);
83
+ if (match) name = match[1];
84
+ }
85
+
86
+ // Detectar versão
87
+ const versionMatch = buildContent.match(/version\s*=?\s*['"]([^'"]+)['"]/);
88
+ const version = versionMatch ? versionMatch[1] : '0.0.1';
89
+
90
+ // Detectar framework
91
+ const framework = this.detectFramework(buildContent);
92
+ this.framework = framework;
93
+
94
+ // Detectar porta
95
+ const port = await this.detectPort(workspacePath);
96
+
97
+ return {
98
+ name,
99
+ version,
100
+ language: 'java',
101
+ framework,
102
+ buildTool: 'gradle',
103
+ port
104
+ };
105
+ }
106
+
107
+ detectFramework(buildContent) {
108
+ if (buildContent.includes('org.springframework.boot') ||
109
+ buildContent.includes('spring-boot')) {
110
+ return 'spring-boot';
111
+ }
112
+ if (buildContent.includes('quarkus')) {
113
+ return 'quarkus';
114
+ }
115
+ if (buildContent.includes('micronaut')) {
116
+ return 'micronaut';
117
+ }
118
+ return 'java-plain';
119
+ }
120
+
121
+ // ==========================================
122
+ // BUILD & RUN
123
+ // ==========================================
124
+
125
+ getGradleCommand() {
126
+ return this.useWrapper ? './gradlew' : 'gradle';
127
+ }
128
+
129
+ async install(workspacePath, options = {}) {
130
+ const startTime = Date.now();
131
+ const cmd = this.getGradleCommand();
132
+ const result = await run(cmd, ['dependencies', '--quiet'], workspacePath);
133
+
134
+ return {
135
+ ...result,
136
+ duration: Date.now() - startTime
137
+ };
138
+ }
139
+
140
+ async build(workspacePath, options = {}) {
141
+ const startTime = Date.now();
142
+ const cmd = this.getGradleCommand();
143
+ const args = ['build', '-x', 'test', '--quiet'];
144
+
145
+ const result = await run(cmd, args, workspacePath);
146
+
147
+ return {
148
+ ...result,
149
+ duration: Date.now() - startTime
150
+ };
151
+ }
152
+
153
+ getStartCommand(options = {}) {
154
+ const cmd = this.getGradleCommand();
155
+ const framework = options.framework || this.framework || 'spring-boot';
156
+ const port = options.port || 8080;
157
+ const profile = options.profile || 'default';
158
+
159
+ if (framework === 'spring-boot') {
160
+ return {
161
+ cmd,
162
+ args: [
163
+ 'bootRun',
164
+ `--args='--server.port=${port} --spring.profiles.active=${profile}'`,
165
+ '--quiet'
166
+ ],
167
+ env: {
168
+ SERVER_PORT: String(port)
169
+ }
170
+ };
171
+ }
172
+
173
+ if (framework === 'quarkus') {
174
+ return {
175
+ cmd,
176
+ args: [
177
+ 'quarkusDev',
178
+ `-Dquarkus.http.port=${port}`,
179
+ '--quiet'
180
+ ],
181
+ env: {
182
+ QUARKUS_HTTP_PORT: String(port)
183
+ }
184
+ };
185
+ }
186
+
187
+ // Fallback
188
+ return {
189
+ cmd,
190
+ args: ['run', '--quiet'],
191
+ env: {}
192
+ };
193
+ }
194
+
195
+ getReadyPattern() {
196
+ if (this.framework === 'spring-boot') {
197
+ return /Started \w+ in \d+\.?\d* seconds/i;
198
+ }
199
+ if (this.framework === 'quarkus') {
200
+ return /Quarkus .* started in/i;
201
+ }
202
+ return /started|listening|ready/i;
203
+ }
204
+
205
+ getDefaultPort() {
206
+ return 8080;
207
+ }
208
+
209
+ async detectPort(workspacePath) {
210
+ // Mesma lógica do Maven - configs são iguais
211
+ const propsPath = path.join(workspacePath, 'src/main/resources/application.properties');
212
+ if (await exists(propsPath)) {
213
+ const content = await readFile(propsPath, 'utf8');
214
+ const match = content.match(/server\.port\s*=\s*(\d+)/);
215
+ if (match) return parseInt(match[1]);
216
+ }
217
+
218
+ const ymlPath = path.join(workspacePath, 'src/main/resources/application.yml');
219
+ if (await exists(ymlPath)) {
220
+ const content = await readFile(ymlPath, 'utf8');
221
+ const match = content.match(/port:\s*(\d+)/);
222
+ if (match) return parseInt(match[1]);
223
+ }
224
+
225
+ return this.getDefaultPort();
226
+ }
227
+
228
+ // ==========================================
229
+ // TEST
230
+ // ==========================================
231
+
232
+ async test(workspacePath, options = {}) {
233
+ const startTime = Date.now();
234
+ const cmd = this.getGradleCommand();
235
+
236
+ // Clean primeiro
237
+ await run(cmd, ['clean', '--quiet'], workspacePath);
238
+
239
+ // Rodar testes
240
+ const result = await run(cmd, ['test', '--quiet'], workspacePath);
241
+
242
+ const testResult = this.parseTestOutput(result, workspacePath);
243
+ testResult.duration = Date.now() - startTime;
244
+
245
+ return testResult;
246
+ }
247
+
248
+ async testSingle(workspacePath, testFile) {
249
+ const startTime = Date.now();
250
+ const cmd = this.getGradleCommand();
251
+
252
+ // Extrair nome da classe
253
+ const className = path.basename(testFile, '.java');
254
+
255
+ const result = await run(cmd, [
256
+ 'test',
257
+ '--tests',
258
+ className,
259
+ '--quiet'
260
+ ], workspacePath);
261
+
262
+ const testResult = this.parseTestOutput(result, workspacePath);
263
+ testResult.duration = Date.now() - startTime;
264
+
265
+ return testResult;
266
+ }
267
+
268
+ async parseTestOutput(result, workspacePath) {
269
+ const output = result.stdout + result.stderr;
270
+
271
+ // Tentar ler relatório XML do Gradle se disponível
272
+ const reportDir = path.join(workspacePath, 'build/test-results/test');
273
+
274
+ // Parse básico do output
275
+ let passed = 0;
276
+ let failed = 0;
277
+ let skipped = 0;
278
+
279
+ // Gradle output format
280
+ const summaryMatch = output.match(/(\d+)\s+tests?\s+completed,\s+(\d+)\s+failed/);
281
+ if (summaryMatch) {
282
+ const total = parseInt(summaryMatch[1]);
283
+ failed = parseInt(summaryMatch[2]);
284
+ passed = total - failed;
285
+ }
286
+
287
+ const skippedMatch = output.match(/(\d+)\s+skipped/);
288
+ if (skippedMatch) {
289
+ skipped = parseInt(skippedMatch[1]);
290
+ }
291
+
292
+ return {
293
+ success: result.code === 0 && failed === 0,
294
+ passed,
295
+ failed,
296
+ skipped,
297
+ duration: 0,
298
+ output,
299
+ failures: this.extractFailures(output)
300
+ };
301
+ }
302
+
303
+ extractFailures(output) {
304
+ const failures = [];
305
+
306
+ // Gradle mostra falhas com formato específico
307
+ const failureRegex = /(\w+Test)\s+>\s+(\w+).*FAILED/g;
308
+ let match;
309
+
310
+ while ((match = failureRegex.exec(output)) !== null) {
311
+ failures.push({
312
+ testName: `${match[1]}.${match[2]}`,
313
+ message: '',
314
+ stackTrace: ''
315
+ });
316
+ }
317
+
318
+ return failures;
319
+ }
320
+
321
+ // ==========================================
322
+ // CODE ANALYSIS
323
+ // ==========================================
324
+
325
+ getFileExtensions() {
326
+ return ['.java', '.kt']; // Suporte a Kotlin também
327
+ }
328
+
329
+ async findIntegrations(workspacePath) {
330
+ const { JavaIntegrationAnalyzer } = await import('./java-integrations.js');
331
+ const analyzer = new JavaIntegrationAnalyzer(workspacePath);
332
+ return analyzer.findAll();
333
+ }
334
+
335
+ async analyzeImports(filePath, content) {
336
+ // Mesma lógica do Maven
337
+ const imports = [];
338
+ const importRegex = /import\s+(static\s+)?([a-zA-Z0-9_.]+)(?:\.\*)?;/g;
339
+
340
+ let match;
341
+ while ((match = importRegex.exec(content)) !== null) {
342
+ imports.push({
343
+ name: match[2],
344
+ type: match[1] ? 'static-import' : 'import',
345
+ isExternal: !match[2].startsWith('com.')
346
+ });
347
+ }
348
+
349
+ return imports;
350
+ }
351
+
352
+ // ==========================================
353
+ // PATCH
354
+ // ==========================================
355
+
356
+ async validatePatch(workspacePath, diff) {
357
+ const errors = [];
358
+
359
+ const fileRegex = /^[\+\-]{3}\s+[ab]\/(.+)$/gm;
360
+ let match;
361
+
362
+ while ((match = fileRegex.exec(diff)) !== null) {
363
+ const filePath = path.join(workspacePath, match[1]);
364
+ if (match[0].startsWith('---') && !(await exists(filePath))) {
365
+ if (!diff.includes('+++ /dev/null')) {
366
+ errors.push(`File not found: ${match[1]}`);
367
+ }
368
+ }
369
+ }
370
+
371
+ return {
372
+ valid: errors.length === 0,
373
+ errors
374
+ };
375
+ }
376
+ }
377
+
378
+ export default GradleRuntime;