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,502 @@
1
+ import { BaseGitProvider } from './base-git-provider.js';
2
+ import { run } from '../utils/exec-utils.js';
3
+
4
+ /**
5
+ * GitHubProvider
6
+ *
7
+ * Implementação do Git provider para GitHub.
8
+ */
9
+ export class GitHubProvider extends BaseGitProvider {
10
+
11
+ constructor(config = {}) {
12
+ super(config);
13
+ this.clientId = config.clientId || process.env.GITHUB_CLIENT_ID;
14
+ this.clientSecret = config.clientSecret || process.env.GITHUB_CLIENT_SECRET;
15
+ }
16
+
17
+ // ==========================================
18
+ // IDENTIFICATION
19
+ // ==========================================
20
+
21
+ getId() {
22
+ return 'github';
23
+ }
24
+
25
+ getName() {
26
+ return 'GitHub';
27
+ }
28
+
29
+ getApiBaseUrl() {
30
+ return 'https://api.github.com';
31
+ }
32
+
33
+ // ==========================================
34
+ // AUTHENTICATION
35
+ // ==========================================
36
+
37
+ getAuthorizationUrl(redirectUri, state) {
38
+ const params = new URLSearchParams({
39
+ client_id: this.clientId,
40
+ redirect_uri: redirectUri,
41
+ scope: 'repo user',
42
+ state
43
+ });
44
+
45
+ return `https://github.com/login/oauth/authorize?${params}`;
46
+ }
47
+
48
+ async exchangeCodeForToken(code, redirectUri) {
49
+ const response = await fetch('https://github.com/login/oauth/access_token', {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ 'Accept': 'application/json'
54
+ },
55
+ body: JSON.stringify({
56
+ client_id: this.clientId,
57
+ client_secret: this.clientSecret,
58
+ code,
59
+ redirect_uri: redirectUri
60
+ })
61
+ });
62
+
63
+ const data = await response.json();
64
+
65
+ if (data.error) {
66
+ throw new Error(data.error_description || data.error);
67
+ }
68
+
69
+ this.accessToken = data.access_token;
70
+
71
+ return {
72
+ accessToken: data.access_token,
73
+ tokenType: data.token_type,
74
+ scope: data.scope
75
+ };
76
+ }
77
+
78
+ async validateToken() {
79
+ try {
80
+ await this.getCurrentUser();
81
+ return true;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ // ==========================================
88
+ // API HELPERS
89
+ // ==========================================
90
+
91
+ async apiRequest(endpoint, options = {}) {
92
+ const url = endpoint.startsWith('http')
93
+ ? endpoint
94
+ : `${this.getApiBaseUrl()}${endpoint}`;
95
+
96
+ const response = await fetch(url, {
97
+ ...options,
98
+ headers: {
99
+ 'Authorization': `Bearer ${this.accessToken}`,
100
+ 'Accept': 'application/vnd.github.v3+json',
101
+ 'Content-Type': 'application/json',
102
+ ...options.headers
103
+ }
104
+ });
105
+
106
+ if (!response.ok) {
107
+ const error = await response.json().catch(() => ({}));
108
+ throw new Error(error.message || `GitHub API error: ${response.status}`);
109
+ }
110
+
111
+ return response.json();
112
+ }
113
+
114
+ // ==========================================
115
+ // USER
116
+ // ==========================================
117
+
118
+ async getCurrentUser() {
119
+ const user = await this.apiRequest('/user');
120
+
121
+ return {
122
+ id: String(user.id),
123
+ username: user.login,
124
+ email: user.email,
125
+ name: user.name,
126
+ avatarUrl: user.avatar_url
127
+ };
128
+ }
129
+
130
+ // ==========================================
131
+ // REPOSITORIES
132
+ // ==========================================
133
+
134
+ async listRepositories(options = {}) {
135
+ const { page = 1, perPage = 30, sort = 'updated' } = options;
136
+
137
+ const repos = await this.apiRequest(
138
+ `/user/repos?page=${page}&per_page=${perPage}&sort=${sort}`
139
+ );
140
+
141
+ return repos.map(repo => ({
142
+ id: String(repo.id),
143
+ name: repo.name,
144
+ fullName: repo.full_name,
145
+ description: repo.description,
146
+ defaultBranch: repo.default_branch,
147
+ private: repo.private,
148
+ cloneUrl: repo.clone_url,
149
+ htmlUrl: repo.html_url
150
+ }));
151
+ }
152
+
153
+ async getRepository(owner, repo) {
154
+ const data = await this.apiRequest(`/repos/${owner}/${repo}`);
155
+
156
+ return {
157
+ id: String(data.id),
158
+ name: data.name,
159
+ fullName: data.full_name,
160
+ description: data.description,
161
+ defaultBranch: data.default_branch,
162
+ private: data.private,
163
+ cloneUrl: data.clone_url,
164
+ htmlUrl: data.html_url
165
+ };
166
+ }
167
+
168
+ async cloneRepository(owner, repo, destPath, options = {}) {
169
+ const { branch, depth = 1 } = options;
170
+
171
+ const cloneUrl = this.getCloneUrl(owner, repo, true);
172
+
173
+ // Inserir token na URL para autenticação
174
+ const authedUrl = cloneUrl.replace(
175
+ 'https://',
176
+ `https://${this.accessToken}@`
177
+ );
178
+
179
+ const args = ['clone'];
180
+
181
+ if (depth) {
182
+ args.push('--depth', String(depth));
183
+ }
184
+
185
+ if (branch) {
186
+ args.push('--branch', branch);
187
+ }
188
+
189
+ args.push(authedUrl, destPath);
190
+
191
+ const result = await run('git', args);
192
+
193
+ return {
194
+ success: result.code === 0,
195
+ path: destPath,
196
+ output: result.stdout + result.stderr
197
+ };
198
+ }
199
+
200
+ // ==========================================
201
+ // BRANCHES
202
+ // ==========================================
203
+
204
+ async listBranches(owner, repo) {
205
+ const branches = await this.apiRequest(`/repos/${owner}/${repo}/branches`);
206
+
207
+ return branches.map(branch => ({
208
+ name: branch.name,
209
+ sha: branch.commit.sha,
210
+ protected: branch.protected
211
+ }));
212
+ }
213
+
214
+ async createBranch(owner, repo, branchName, fromRef) {
215
+ // Obter SHA do ref de origem
216
+ let sha = fromRef;
217
+
218
+ if (!fromRef.match(/^[a-f0-9]{40}$/)) {
219
+ // É um nome de branch, precisamos do SHA
220
+ const branch = await this.getBranch(owner, repo, fromRef);
221
+ sha = branch.sha;
222
+ }
223
+
224
+ // Criar a referência
225
+ const data = await this.apiRequest(`/repos/${owner}/${repo}/git/refs`, {
226
+ method: 'POST',
227
+ body: JSON.stringify({
228
+ ref: `refs/heads/${branchName}`,
229
+ sha
230
+ })
231
+ });
232
+
233
+ return {
234
+ name: branchName,
235
+ sha: data.object.sha,
236
+ protected: false
237
+ };
238
+ }
239
+
240
+ async getBranch(owner, repo, branch) {
241
+ const data = await this.apiRequest(`/repos/${owner}/${repo}/branches/${branch}`);
242
+
243
+ return {
244
+ name: data.name,
245
+ sha: data.commit.sha,
246
+ protected: data.protected
247
+ };
248
+ }
249
+
250
+ // ==========================================
251
+ // COMMITS
252
+ // ==========================================
253
+
254
+ async listCommits(owner, repo, branch, options = {}) {
255
+ const { page = 1, perPage = 30 } = options;
256
+
257
+ const commits = await this.apiRequest(
258
+ `/repos/${owner}/${repo}/commits?sha=${branch}&page=${page}&per_page=${perPage}`
259
+ );
260
+
261
+ return commits.map(commit => ({
262
+ sha: commit.sha,
263
+ message: commit.commit.message,
264
+ authorName: commit.commit.author.name,
265
+ authorEmail: commit.commit.author.email,
266
+ date: new Date(commit.commit.author.date)
267
+ }));
268
+ }
269
+
270
+ async createCommit(owner, repo, branch, message, changes) {
271
+ // 1. Obter referência atual
272
+ const branchData = await this.getBranch(owner, repo, branch);
273
+ const baseSha = branchData.sha;
274
+
275
+ // 2. Obter tree atual
276
+ const baseCommit = await this.apiRequest(
277
+ `/repos/${owner}/${repo}/git/commits/${baseSha}`
278
+ );
279
+ const baseTreeSha = baseCommit.tree.sha;
280
+
281
+ // 3. Criar blobs para cada arquivo modificado
282
+ const treeItems = [];
283
+
284
+ for (const change of changes) {
285
+ if (change.action === 'delete') {
286
+ treeItems.push({
287
+ path: change.path,
288
+ mode: '100644',
289
+ type: 'blob',
290
+ sha: null
291
+ });
292
+ } else {
293
+ // Criar blob
294
+ const blob = await this.apiRequest(`/repos/${owner}/${repo}/git/blobs`, {
295
+ method: 'POST',
296
+ body: JSON.stringify({
297
+ content: change.content,
298
+ encoding: 'utf-8'
299
+ })
300
+ });
301
+
302
+ treeItems.push({
303
+ path: change.path,
304
+ mode: '100644',
305
+ type: 'blob',
306
+ sha: blob.sha
307
+ });
308
+ }
309
+ }
310
+
311
+ // 4. Criar nova tree
312
+ const newTree = await this.apiRequest(`/repos/${owner}/${repo}/git/trees`, {
313
+ method: 'POST',
314
+ body: JSON.stringify({
315
+ base_tree: baseTreeSha,
316
+ tree: treeItems
317
+ })
318
+ });
319
+
320
+ // 5. Criar commit
321
+ const newCommit = await this.apiRequest(`/repos/${owner}/${repo}/git/commits`, {
322
+ method: 'POST',
323
+ body: JSON.stringify({
324
+ message,
325
+ tree: newTree.sha,
326
+ parents: [baseSha]
327
+ })
328
+ });
329
+
330
+ // 6. Atualizar referência da branch
331
+ await this.apiRequest(`/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
332
+ method: 'PATCH',
333
+ body: JSON.stringify({
334
+ sha: newCommit.sha
335
+ })
336
+ });
337
+
338
+ return {
339
+ sha: newCommit.sha,
340
+ message: newCommit.message,
341
+ authorName: newCommit.author.name,
342
+ authorEmail: newCommit.author.email,
343
+ date: new Date(newCommit.author.date)
344
+ };
345
+ }
346
+
347
+ // ==========================================
348
+ // FILES
349
+ // ==========================================
350
+
351
+ async getFileContent(owner, repo, path, ref = 'main') {
352
+ const data = await this.apiRequest(
353
+ `/repos/${owner}/${repo}/contents/${path}?ref=${ref}`
354
+ );
355
+
356
+ return {
357
+ path: data.path,
358
+ content: Buffer.from(data.content, 'base64').toString('utf8'),
359
+ sha: data.sha,
360
+ encoding: data.encoding
361
+ };
362
+ }
363
+
364
+ async createOrUpdateFile(owner, repo, path, content, message, branch, sha = null) {
365
+ const body = {
366
+ message,
367
+ content: Buffer.from(content).toString('base64'),
368
+ branch
369
+ };
370
+
371
+ if (sha) {
372
+ body.sha = sha;
373
+ }
374
+
375
+ const data = await this.apiRequest(`/repos/${owner}/${repo}/contents/${path}`, {
376
+ method: 'PUT',
377
+ body: JSON.stringify(body)
378
+ });
379
+
380
+ return {
381
+ path: data.content.path,
382
+ sha: data.content.sha,
383
+ commit: data.commit
384
+ };
385
+ }
386
+
387
+ // ==========================================
388
+ // PULL REQUESTS
389
+ // ==========================================
390
+
391
+ async listPullRequests(owner, repo, options = {}) {
392
+ const { state = 'open', page = 1, perPage = 30 } = options;
393
+
394
+ const prs = await this.apiRequest(
395
+ `/repos/${owner}/${repo}/pulls?state=${state}&page=${page}&per_page=${perPage}`
396
+ );
397
+
398
+ return prs.map(pr => ({
399
+ number: pr.number,
400
+ title: pr.title,
401
+ body: pr.body,
402
+ state: pr.state,
403
+ head: pr.head.ref,
404
+ base: pr.base.ref,
405
+ htmlUrl: pr.html_url
406
+ }));
407
+ }
408
+
409
+ async createPullRequest(owner, repo, pr) {
410
+ const data = await this.apiRequest(`/repos/${owner}/${repo}/pulls`, {
411
+ method: 'POST',
412
+ body: JSON.stringify({
413
+ title: pr.title,
414
+ body: pr.body,
415
+ head: pr.head,
416
+ base: pr.base
417
+ })
418
+ });
419
+
420
+ return {
421
+ number: data.number,
422
+ title: data.title,
423
+ body: data.body,
424
+ state: data.state,
425
+ head: data.head.ref,
426
+ base: data.base.ref,
427
+ htmlUrl: data.html_url
428
+ };
429
+ }
430
+
431
+ async getPullRequest(owner, repo, prNumber) {
432
+ const data = await this.apiRequest(`/repos/${owner}/${repo}/pulls/${prNumber}`);
433
+
434
+ return {
435
+ number: data.number,
436
+ title: data.title,
437
+ body: data.body,
438
+ state: data.state,
439
+ head: data.head.ref,
440
+ base: data.base.ref,
441
+ htmlUrl: data.html_url
442
+ };
443
+ }
444
+
445
+ // ==========================================
446
+ // WEBHOOKS
447
+ // ==========================================
448
+
449
+ async createWebhook(owner, repo, webhook) {
450
+ const data = await this.apiRequest(`/repos/${owner}/${repo}/hooks`, {
451
+ method: 'POST',
452
+ body: JSON.stringify({
453
+ name: 'web',
454
+ active: true,
455
+ events: webhook.events || ['push', 'pull_request'],
456
+ config: {
457
+ url: webhook.url,
458
+ content_type: 'json',
459
+ secret: webhook.secret
460
+ }
461
+ })
462
+ });
463
+
464
+ return {
465
+ id: String(data.id),
466
+ url: data.config.url,
467
+ events: data.events,
468
+ active: data.active
469
+ };
470
+ }
471
+
472
+ // ==========================================
473
+ // HELPER METHODS
474
+ // ==========================================
475
+
476
+ getCloneUrl(owner, repo, useHttps = true) {
477
+ if (useHttps) {
478
+ return `https://github.com/${owner}/${repo}.git`;
479
+ }
480
+ return `git@github.com:${owner}/${repo}.git`;
481
+ }
482
+
483
+ parseRepositoryUrl(url) {
484
+ // Suporta:
485
+ // - https://github.com/owner/repo
486
+ // - https://github.com/owner/repo.git
487
+ // - git@github.com:owner/repo.git
488
+
489
+ let match = url.match(/github\.com[/:]([\w-]+)\/([\w-]+?)(?:\.git)?$/);
490
+
491
+ if (match) {
492
+ return {
493
+ owner: match[1],
494
+ repo: match[2]
495
+ };
496
+ }
497
+
498
+ throw new Error(`Invalid GitHub repository URL: ${url}`);
499
+ }
500
+ }
501
+
502
+ export default GitHubProvider;