deepdebug-local-agent 0.3.1 → 0.3.2
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/bin/install.js +419 -0
- package/package.json +1 -1
- package/src/git/base-git-provider.js +169 -18
- package/src/git/bitbucket-provider.js +723 -0
- package/src/git/git-provider-registry.js +12 -11
- package/src/git/github-provider.js +299 -6
- package/src/server.js +587 -42
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GitHubProvider } from './github-provider.js';
|
|
2
|
+
import { BitbucketProvider } from './bitbucket-provider.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* GitProviderRegistry
|
|
@@ -15,10 +16,10 @@ export class GitProviderRegistry {
|
|
|
15
16
|
registerDefaults() {
|
|
16
17
|
// Registrar providers disponíveis
|
|
17
18
|
this.register('github', GitHubProvider);
|
|
19
|
+
this.register('bitbucket', BitbucketProvider);
|
|
18
20
|
|
|
19
|
-
// GitLab
|
|
21
|
+
// GitLab será implementado depois
|
|
20
22
|
// this.register('gitlab', GitLabProvider);
|
|
21
|
-
// this.register('bitbucket', BitbucketProvider);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -28,13 +29,13 @@ export class GitProviderRegistry {
|
|
|
28
29
|
*/
|
|
29
30
|
register(id, ProviderClass) {
|
|
30
31
|
this.providers.set(id, ProviderClass);
|
|
31
|
-
console.log(
|
|
32
|
+
console.log(`📦 Registered git provider: ${id}`);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Cria
|
|
36
|
+
* Cria instância de um provider
|
|
36
37
|
* @param {string} id - ID do provider
|
|
37
|
-
* @param {object} config -
|
|
38
|
+
* @param {object} config - Configuração do provider
|
|
38
39
|
* @returns {BaseGitProvider}
|
|
39
40
|
*/
|
|
40
41
|
create(id, config = {}) {
|
|
@@ -48,7 +49,7 @@ export class GitProviderRegistry {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
|
-
* Lista IDs dos providers
|
|
52
|
+
* Lista IDs dos providers disponÃveis
|
|
52
53
|
* @returns {string[]}
|
|
53
54
|
*/
|
|
54
55
|
listProviders() {
|
|
@@ -57,7 +58,7 @@ export class GitProviderRegistry {
|
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* Detecta provider a partir da URL
|
|
60
|
-
* @param {string} url - URL do
|
|
61
|
+
* @param {string} url - URL do repositório
|
|
61
62
|
* @returns {string|null} - ID do provider
|
|
62
63
|
*/
|
|
63
64
|
detectProviderFromUrl(url) {
|
|
@@ -74,9 +75,9 @@ export class GitProviderRegistry {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
* Cria provider a partir da URL do
|
|
78
|
-
* @param {string} url - URL do
|
|
79
|
-
* @param {object} config -
|
|
78
|
+
* Cria provider a partir da URL do repositório
|
|
79
|
+
* @param {string} url - URL do repositório
|
|
80
|
+
* @param {object} config - Configuração
|
|
80
81
|
* @returns {BaseGitProvider}
|
|
81
82
|
*/
|
|
82
83
|
createFromUrl(url, config = {}) {
|
|
@@ -97,7 +98,7 @@ export class GitProviderRegistry {
|
|
|
97
98
|
let instance = null;
|
|
98
99
|
|
|
99
100
|
/**
|
|
100
|
-
*
|
|
101
|
+
* Obtém instância singleton do GitProviderRegistry
|
|
101
102
|
* @returns {GitProviderRegistry}
|
|
102
103
|
*/
|
|
103
104
|
export function getGitProviderRegistry() {
|
|
@@ -4,7 +4,7 @@ import { run } from '../utils/exec-utils.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* GitHubProvider
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Implementação do Git provider para GitHub.
|
|
8
8
|
*/
|
|
9
9
|
export class GitHubProvider extends BaseGitProvider {
|
|
10
10
|
|
|
@@ -170,7 +170,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
170
170
|
|
|
171
171
|
const cloneUrl = this.getCloneUrl(owner, repo, true);
|
|
172
172
|
|
|
173
|
-
// Inserir token na URL para
|
|
173
|
+
// Inserir token na URL para autenticação
|
|
174
174
|
const authedUrl = cloneUrl.replace(
|
|
175
175
|
'https://',
|
|
176
176
|
`https://${this.accessToken}@`
|
|
@@ -216,12 +216,12 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
216
216
|
let sha = fromRef;
|
|
217
217
|
|
|
218
218
|
if (!fromRef.match(/^[a-f0-9]{40}$/)) {
|
|
219
|
-
//
|
|
219
|
+
// É um nome de branch, precisamos do SHA
|
|
220
220
|
const branch = await this.getBranch(owner, repo, fromRef);
|
|
221
221
|
sha = branch.sha;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// Criar a
|
|
224
|
+
// Criar a referência
|
|
225
225
|
const data = await this.apiRequest(`/repos/${owner}/${repo}/git/refs`, {
|
|
226
226
|
method: 'POST',
|
|
227
227
|
body: JSON.stringify({
|
|
@@ -268,7 +268,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
async createCommit(owner, repo, branch, message, changes) {
|
|
271
|
-
// 1. Obter
|
|
271
|
+
// 1. Obter referência atual
|
|
272
272
|
const branchData = await this.getBranch(owner, repo, branch);
|
|
273
273
|
const baseSha = branchData.sha;
|
|
274
274
|
|
|
@@ -327,7 +327,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
327
327
|
})
|
|
328
328
|
});
|
|
329
329
|
|
|
330
|
-
// 6. Atualizar
|
|
330
|
+
// 6. Atualizar referência da branch
|
|
331
331
|
await this.apiRequest(`/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
332
332
|
method: 'PATCH',
|
|
333
333
|
body: JSON.stringify({
|
|
@@ -442,6 +442,299 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
// ==========================================
|
|
446
|
+
// PULL REQUEST COMMENTS / REVIEWS
|
|
447
|
+
// ==========================================
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* List general (issue-level) comments on a PR.
|
|
451
|
+
* GitHub treats PRs as issues, so general comments use the issues API.
|
|
452
|
+
*/
|
|
453
|
+
async listPRComments(owner, repo, prNumber, options = {}) {
|
|
454
|
+
const { page = 1, perPage = 100 } = options;
|
|
455
|
+
|
|
456
|
+
const comments = await this.apiRequest(
|
|
457
|
+
`/repos/${owner}/${repo}/issues/${prNumber}/comments?page=${page}&per_page=${perPage}`
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
return comments.map(c => ({
|
|
461
|
+
id: c.id,
|
|
462
|
+
body: c.body,
|
|
463
|
+
authorUsername: c.user?.login,
|
|
464
|
+
authorName: c.user?.login,
|
|
465
|
+
createdAt: c.created_at,
|
|
466
|
+
updatedAt: c.updated_at,
|
|
467
|
+
htmlUrl: c.html_url,
|
|
468
|
+
resolved: undefined, // General comments don't have resolved state in GitHub
|
|
469
|
+
type: 'general'
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* List review comments (inline code comments) on a PR.
|
|
475
|
+
* These are comments attached to specific lines in the diff.
|
|
476
|
+
*/
|
|
477
|
+
async listPRReviewComments(owner, repo, prNumber, options = {}) {
|
|
478
|
+
const { page = 1, perPage = 100 } = options;
|
|
479
|
+
|
|
480
|
+
const comments = await this.apiRequest(
|
|
481
|
+
`/repos/${owner}/${repo}/pulls/${prNumber}/comments?page=${page}&per_page=${perPage}`
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
return comments.map(c => ({
|
|
485
|
+
id: c.id,
|
|
486
|
+
body: c.body,
|
|
487
|
+
path: c.path,
|
|
488
|
+
line: c.line || c.original_line,
|
|
489
|
+
side: c.side,
|
|
490
|
+
commitId: c.commit_id,
|
|
491
|
+
authorUsername: c.user?.login,
|
|
492
|
+
authorName: c.user?.login,
|
|
493
|
+
createdAt: c.created_at,
|
|
494
|
+
updatedAt: c.updated_at,
|
|
495
|
+
htmlUrl: c.html_url,
|
|
496
|
+
resolved: c.resolved !== undefined ? c.resolved : undefined,
|
|
497
|
+
threadId: c.pull_request_review_id,
|
|
498
|
+
inReplyToId: c.in_reply_to_id || null,
|
|
499
|
+
type: 'review'
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Get a specific review comment by ID
|
|
505
|
+
*/
|
|
506
|
+
async getPRComment(owner, repo, prNumber, commentId) {
|
|
507
|
+
// Try review comment first, then general
|
|
508
|
+
try {
|
|
509
|
+
const c = await this.apiRequest(`/repos/${owner}/${repo}/pulls/comments/${commentId}`);
|
|
510
|
+
return {
|
|
511
|
+
id: c.id,
|
|
512
|
+
body: c.body,
|
|
513
|
+
path: c.path,
|
|
514
|
+
line: c.line || c.original_line,
|
|
515
|
+
side: c.side,
|
|
516
|
+
commitId: c.commit_id,
|
|
517
|
+
authorUsername: c.user?.login,
|
|
518
|
+
authorName: c.user?.login,
|
|
519
|
+
createdAt: c.created_at,
|
|
520
|
+
updatedAt: c.updated_at,
|
|
521
|
+
htmlUrl: c.html_url,
|
|
522
|
+
resolved: c.resolved !== undefined ? c.resolved : undefined,
|
|
523
|
+
threadId: c.pull_request_review_id,
|
|
524
|
+
inReplyToId: c.in_reply_to_id || null,
|
|
525
|
+
type: 'review'
|
|
526
|
+
};
|
|
527
|
+
} catch {
|
|
528
|
+
// Fall back to general comment (issues API)
|
|
529
|
+
const c = await this.apiRequest(`/repos/${owner}/${repo}/issues/comments/${commentId}`);
|
|
530
|
+
return {
|
|
531
|
+
id: c.id,
|
|
532
|
+
body: c.body,
|
|
533
|
+
authorUsername: c.user?.login,
|
|
534
|
+
authorName: c.user?.login,
|
|
535
|
+
createdAt: c.created_at,
|
|
536
|
+
updatedAt: c.updated_at,
|
|
537
|
+
htmlUrl: c.html_url,
|
|
538
|
+
resolved: undefined,
|
|
539
|
+
type: 'general'
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Add a general comment to a PR
|
|
546
|
+
*/
|
|
547
|
+
async addPRComment(owner, repo, prNumber, body) {
|
|
548
|
+
const data = await this.apiRequest(`/repos/${owner}/${repo}/issues/${prNumber}/comments`, {
|
|
549
|
+
method: 'POST',
|
|
550
|
+
body: JSON.stringify({ body })
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
id: data.id,
|
|
555
|
+
body: data.body,
|
|
556
|
+
authorUsername: data.user?.login,
|
|
557
|
+
authorName: data.user?.login,
|
|
558
|
+
createdAt: data.created_at,
|
|
559
|
+
updatedAt: data.updated_at,
|
|
560
|
+
htmlUrl: data.html_url,
|
|
561
|
+
type: 'general'
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Add an inline review comment on a specific line of code.
|
|
567
|
+
*
|
|
568
|
+
* @param {object} comment - { body, path, line, side, commitId }
|
|
569
|
+
* - path: file path relative to repo root
|
|
570
|
+
* - line: line number in the diff
|
|
571
|
+
* - side: 'LEFT' (old) or 'RIGHT' (new) - default 'RIGHT'
|
|
572
|
+
* - commitId: the HEAD commit SHA of the PR
|
|
573
|
+
*/
|
|
574
|
+
async addPRReviewComment(owner, repo, prNumber, comment) {
|
|
575
|
+
const requestBody = {
|
|
576
|
+
body: comment.body,
|
|
577
|
+
path: comment.path,
|
|
578
|
+
commit_id: comment.commitId,
|
|
579
|
+
side: comment.side || 'RIGHT'
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// GitHub uses 'line' for single-line or 'start_line' + 'line' for multi-line
|
|
583
|
+
if (comment.line) {
|
|
584
|
+
requestBody.line = comment.line;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// If replying to an existing thread, use in_reply_to
|
|
588
|
+
if (comment.inReplyToId) {
|
|
589
|
+
requestBody.in_reply_to = comment.inReplyToId;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const data = await this.apiRequest(`/repos/${owner}/${repo}/pulls/${prNumber}/comments`, {
|
|
593
|
+
method: 'POST',
|
|
594
|
+
body: JSON.stringify(requestBody)
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
id: data.id,
|
|
599
|
+
body: data.body,
|
|
600
|
+
path: data.path,
|
|
601
|
+
line: data.line || data.original_line,
|
|
602
|
+
side: data.side,
|
|
603
|
+
commitId: data.commit_id,
|
|
604
|
+
authorUsername: data.user?.login,
|
|
605
|
+
authorName: data.user?.login,
|
|
606
|
+
createdAt: data.created_at,
|
|
607
|
+
updatedAt: data.updated_at,
|
|
608
|
+
htmlUrl: data.html_url,
|
|
609
|
+
threadId: data.pull_request_review_id,
|
|
610
|
+
inReplyToId: data.in_reply_to_id || null,
|
|
611
|
+
type: 'review'
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Reply to an existing review comment thread.
|
|
617
|
+
* GitHub uses the pulls/comments endpoint with in_reply_to.
|
|
618
|
+
*/
|
|
619
|
+
async replyToPRComment(owner, repo, prNumber, commentId, body) {
|
|
620
|
+
// For review comments, use the reply endpoint
|
|
621
|
+
const data = await this.apiRequest(
|
|
622
|
+
`/repos/${owner}/${repo}/pulls/${prNumber}/comments/${commentId}/replies`, {
|
|
623
|
+
method: 'POST',
|
|
624
|
+
body: JSON.stringify({ body })
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
return {
|
|
629
|
+
id: data.id,
|
|
630
|
+
body: data.body,
|
|
631
|
+
path: data.path,
|
|
632
|
+
line: data.line || data.original_line,
|
|
633
|
+
authorUsername: data.user?.login,
|
|
634
|
+
authorName: data.user?.login,
|
|
635
|
+
createdAt: data.created_at,
|
|
636
|
+
updatedAt: data.updated_at,
|
|
637
|
+
htmlUrl: data.html_url,
|
|
638
|
+
inReplyToId: commentId,
|
|
639
|
+
type: 'review'
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Resolve a review comment thread in GitHub.
|
|
645
|
+
*
|
|
646
|
+
* GitHub doesn't have a direct "resolve comment" API.
|
|
647
|
+
* Resolution is done by submitting a review that resolves the thread.
|
|
648
|
+
* We use the GraphQL API for thread resolution.
|
|
649
|
+
*
|
|
650
|
+
* Fallback: If GraphQL is not available, we reply with a "Resolved" message.
|
|
651
|
+
*/
|
|
652
|
+
async resolvePRComment(owner, repo, prNumber, commentId) {
|
|
653
|
+
try {
|
|
654
|
+
// Try GraphQL approach (thread resolution)
|
|
655
|
+
const query = `
|
|
656
|
+
mutation($threadId: ID!) {
|
|
657
|
+
resolveReviewThread(input: { threadId: $threadId }) {
|
|
658
|
+
thread { isResolved }
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
`;
|
|
662
|
+
|
|
663
|
+
await fetch('https://api.github.com/graphql', {
|
|
664
|
+
method: 'POST',
|
|
665
|
+
headers: {
|
|
666
|
+
'Authorization': `Bearer ${this.accessToken}`,
|
|
667
|
+
'Content-Type': 'application/json'
|
|
668
|
+
},
|
|
669
|
+
body: JSON.stringify({
|
|
670
|
+
query,
|
|
671
|
+
variables: { threadId: String(commentId) }
|
|
672
|
+
})
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
return { resolved: true };
|
|
676
|
+
} catch {
|
|
677
|
+
// Fallback: add a reply indicating resolution
|
|
678
|
+
await this.replyToPRComment(owner, repo, prNumber, commentId,
|
|
679
|
+
'✅ Resolved by InspTech AI — fix has been applied.');
|
|
680
|
+
return { resolved: true };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Unresolve a review comment thread in GitHub via GraphQL.
|
|
686
|
+
*/
|
|
687
|
+
async unresolvePRComment(owner, repo, prNumber, commentId) {
|
|
688
|
+
try {
|
|
689
|
+
const query = `
|
|
690
|
+
mutation($threadId: ID!) {
|
|
691
|
+
unresolveReviewThread(input: { threadId: $threadId }) {
|
|
692
|
+
thread { isResolved }
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
`;
|
|
696
|
+
|
|
697
|
+
await fetch('https://api.github.com/graphql', {
|
|
698
|
+
method: 'POST',
|
|
699
|
+
headers: {
|
|
700
|
+
'Authorization': `Bearer ${this.accessToken}`,
|
|
701
|
+
'Content-Type': 'application/json'
|
|
702
|
+
},
|
|
703
|
+
body: JSON.stringify({
|
|
704
|
+
query,
|
|
705
|
+
variables: { threadId: String(commentId) }
|
|
706
|
+
})
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
return { resolved: false };
|
|
710
|
+
} catch {
|
|
711
|
+
return { resolved: false };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* List only unresolved review comments.
|
|
717
|
+
* Uses the reviews API to find pending threads.
|
|
718
|
+
*/
|
|
719
|
+
async listUnresolvedPRComments(owner, repo, prNumber) {
|
|
720
|
+
const reviewComments = await this.listPRReviewComments(owner, repo, prNumber);
|
|
721
|
+
|
|
722
|
+
// Group by thread (in_reply_to chain) and filter unresolved
|
|
723
|
+
const threads = new Map();
|
|
724
|
+
for (const c of reviewComments) {
|
|
725
|
+
const threadKey = c.inReplyToId || c.id;
|
|
726
|
+
if (!threads.has(threadKey)) {
|
|
727
|
+
threads.set(threadKey, []);
|
|
728
|
+
}
|
|
729
|
+
threads.get(threadKey).push(c);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Return root comments of unresolved threads
|
|
733
|
+
return reviewComments.filter(c =>
|
|
734
|
+
!c.inReplyToId && (c.resolved === false || c.resolved === undefined)
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
445
738
|
// ==========================================
|
|
446
739
|
// WEBHOOKS
|
|
447
740
|
// ==========================================
|