Skip to content

Commit bf76464

Browse files
committed
feat: Enhance GitHub and GitLab services with PR/MR merge handling and analysis updates
- Added target branch name and current commit hash to AI client services for GitHub and GitLab. - Implemented findPullRequestForCommit method in GitHubOperationsService to retrieve PRs associated with commits. - Implemented findPullRequestForCommit method in GitLabOperationsService to retrieve MRs associated with commits. - Created GitHubPrMergeWebhookHandler to handle PR merge events and trigger branch reconciliation. - Created GitLabMrMergeWebhookHandler to handle MR merge events and trigger branch reconciliation. - Updated AnalysisIssueController and ProjectAnalyticsController to support new issue resolution context (resolvedByPr and resolvedCommitHash). - Enhanced IssueStatusUpdateRequest to include resolvedByPr and resolvedCommitHash fields. - Updated AnalysisService to handle new resolution metadata when updating issue status. - Improved prompt builder documentation for resolved issues. - Removed outdated IMPLEMENTATION_SUMMARY.md file.
1 parent bb8b822 commit bf76464

26 files changed

Lines changed: 753 additions & 430 deletions

File tree

java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/BranchAnalysisProcessor.java

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,37 @@ public Map<String, Object> process(BranchProcessRequest request, Consumer<Map<St
153153
EVcsProvider provider = getVcsProvider(project);
154154
VcsOperationsService operationsService = vcsServiceFactory.getOperationsService(provider);
155155

156+
// If sourcePrNumber is not set, try to look it up from the commit
157+
// This handles cases where branch analysis is triggered by push events
158+
Long prNumber = request.getSourcePrNumber();
159+
if (prNumber == null && request.getCommitHash() != null) {
160+
try {
161+
prNumber = operationsService.findPullRequestForCommit(
162+
client,
163+
vcsInfo.workspace(),
164+
vcsInfo.repoSlug(),
165+
request.getCommitHash()
166+
);
167+
if (prNumber != null) {
168+
log.info("Found PR #{} for commit {} via API lookup", prNumber, request.getCommitHash());
169+
request.sourcePrNumber = prNumber; // Update request for later use
170+
}
171+
} catch (Exception e) {
172+
log.debug("Could not look up PR for commit {}: {}", request.getCommitHash(), e.getMessage());
173+
}
174+
}
175+
156176
String rawDiff;
157-
// Use PR diff if sourcePrNumber is available (from pullrequest:fulfilled events)
177+
// Use PR diff if sourcePrNumber is available (from pullrequest:fulfilled events or API lookup)
158178
// This ensures we get ALL files from the PR, not just the merge commit changes
159-
if (request.getSourcePrNumber() != null) {
179+
if (prNumber != null) {
160180
rawDiff = operationsService.getPullRequestDiff(
161181
client,
162182
vcsInfo.workspace(),
163183
vcsInfo.repoSlug(),
164-
String.valueOf(request.getSourcePrNumber())
184+
String.valueOf(prNumber)
165185
);
166-
log.info("Fetched PR #{} diff for branch analysis (contains all PR files)", request.getSourcePrNumber());
186+
log.info("Fetched PR #{} diff for branch analysis (contains all PR files)", prNumber);
167187
} else {
168188
rawDiff = operationsService.getCommitDiff(
169189
client,
@@ -423,7 +443,7 @@ private void reanalyzeCandidateIssues(Set<String> changedFiles, Branch branch, P
423443
if (item instanceof Map) {
424444
@SuppressWarnings("unchecked")
425445
Map<String, Object> issueData = (Map<String, Object>) item;
426-
processReconciledIssue(issueData, branch, request.getCommitHash());
446+
processReconciledIssue(issueData, branch, request.getCommitHash(), request.getSourcePrNumber());
427447
}
428448
}
429449
}
@@ -435,7 +455,7 @@ else if (issuesObj instanceof Map) {
435455
if (val instanceof Map) {
436456
@SuppressWarnings("unchecked")
437457
Map<String, Object> issueData = (Map<String, Object>) val;
438-
processReconciledIssue(issueData, branch, request.getCommitHash());
458+
processReconciledIssue(issueData, branch, request.getCommitHash(), request.getSourcePrNumber());
439459
}
440460
}
441461
} else if (issuesObj != null) {
@@ -464,8 +484,12 @@ else if (issuesObj instanceof Map) {
464484
}
465485
}
466486

467-
private void processReconciledIssue(Map<String, Object> issueData, Branch branch, String commitHash) {
468-
Object issueIdFromAi = issueData.get("id");
487+
private void processReconciledIssue(Map<String, Object> issueData, Branch branch, String commitHash, Long sourcePrNumber) {
488+
// Try both "issueId" (as instructed in prompt) and "id" (fallback) for the issue identifier
489+
Object issueIdFromAi = issueData.get("issueId");
490+
if (issueIdFromAi == null) {
491+
issueIdFromAi = issueData.get("id");
492+
}
469493
Long actualIssueId = null;
470494

471495
if (issueIdFromAi != null) {
@@ -485,26 +509,47 @@ private void processReconciledIssue(Map<String, Object> issueData, Branch branch
485509
resolved = true;
486510
}
487511

512+
// Extract AI's resolution reason/description if provided
513+
String resolvedDescription = null;
514+
if (issueData.get("reason") != null) {
515+
resolvedDescription = String.valueOf(issueData.get("reason"));
516+
}
517+
488518
if (resolved && actualIssueId != null) {
489519
Optional<BranchIssue> branchIssueOpt = branchIssueRepository
490520
.findByBranchIdAndCodeAnalysisIssueId(branch.getId(), actualIssueId);
491521
if (branchIssueOpt.isPresent()) {
492522
BranchIssue bi = branchIssueOpt.get();
493523
if (!bi.isResolved()) {
524+
java.time.OffsetDateTime now = java.time.OffsetDateTime.now();
525+
526+
// Update BranchIssue with resolution info
494527
bi.setResolved(true);
495-
bi.setResolvedInPrNumber(null);
528+
bi.setResolvedInPrNumber(sourcePrNumber);
496529
bi.setResolvedInCommitHash(commitHash);
530+
bi.setResolvedDescription(resolvedDescription);
531+
bi.setResolvedAt(now);
532+
bi.setResolvedBy("AI-reconciliation");
497533
branchIssueRepository.save(bi);
498534

535+
// Update CodeAnalysisIssue with resolution info (preserving original issue data)
499536
Optional<CodeAnalysisIssue> caiOpt = codeAnalysisIssueRepository.findById(actualIssueId);
500537
if (caiOpt.isPresent()) {
501538
CodeAnalysisIssue cai = caiOpt.get();
502539
cai.setResolved(true);
540+
cai.setResolvedDescription(resolvedDescription);
541+
cai.setResolvedByPr(sourcePrNumber);
542+
cai.setResolvedCommitHash(commitHash);
543+
cai.setResolvedAt(now);
544+
cai.setResolvedBy("AI-reconciliation");
545+
// Note: original issue fields (reason, suggestedFixDescription, suggestedFixDiff, etc.) are preserved
503546
codeAnalysisIssueRepository.save(cai);
504547
}
505-
log.info("Marked branch issue {} as resolved (commit: {})",
548+
log.info("Marked branch issue {} as resolved (commit: {}, PR: {}, description: {})",
506549
actualIssueId,
507-
commitHash);
550+
commitHash,
551+
sourcePrNumber,
552+
resolvedDescription != null ? resolvedDescription.substring(0, Math.min(100, resolvedDescription.length())) : "none");
508553
}
509554
}
510555
}

java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/vcs/VcsOperationsService.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,17 @@ public interface VcsOperationsService {
6868
* @throws IOException on network errors
6969
*/
7070
boolean checkFileExistsInBranch(OkHttpClient client, String workspace, String repoSlug, String branchName, String filePath) throws IOException;
71+
72+
/**
73+
* Finds the pull request number that introduced a specific commit to the repository.
74+
* This is useful for branch reconciliation when we need to track which PR resolved an issue.
75+
*
76+
* @param client authorized HTTP client
77+
* @param workspace workspace or team/organization slug
78+
* @param repoSlug repository slug
79+
* @param commitHash the commit hash to look up
80+
* @return the PR/MR number that introduced this commit, or null if not found or commit wasn't from a PR
81+
* @throws IOException on network errors
82+
*/
83+
Long findPullRequestForCommit(OkHttpClient client, String workspace, String repoSlug, String commitHash) throws IOException;
7184
}

java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/analysis/issue/IssueDTO.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ public record IssueDTO (
2525
Long analysisId,
2626
Long prNumber,
2727
String commitHash,
28-
OffsetDateTime detectedAt
28+
OffsetDateTime detectedAt,
29+
// Resolution info - populated when issue is resolved
30+
String resolvedDescription,
31+
Long resolvedByPr,
32+
String resolvedCommitHash,
33+
Long resolvedAnalysisId,
34+
OffsetDateTime resolvedAt,
35+
String resolvedBy
2936
) {
3037
public static IssueDTO fromEntity(CodeAnalysisIssue issue) {
3138
String categoryStr = issue.getIssueCategory() != null
@@ -53,7 +60,14 @@ public static IssueDTO fromEntity(CodeAnalysisIssue issue) {
5360
analysis != null ? analysis.getId() : null,
5461
analysis != null ? analysis.getPrNumber() : null,
5562
analysis != null ? analysis.getCommitHash() : null,
56-
issue.getCreatedAt()
63+
issue.getCreatedAt(),
64+
// Resolution info
65+
issue.getResolvedDescription(),
66+
issue.getResolvedByPr(),
67+
issue.getResolvedCommitHash(),
68+
issue.getResolvedAnalysisId(),
69+
issue.getResolvedAt(),
70+
issue.getResolvedBy()
5771
);
5872
}
5973
}

java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/branch/BranchIssue.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ public class BranchIssue {
4141
@Column(name = "resolved_in_commit_hash", length = 40)
4242
private String resolvedInCommitHash;
4343

44+
@Column(name = "resolved_description", columnDefinition = "TEXT")
45+
private String resolvedDescription;
46+
47+
@Column(name = "resolved_at")
48+
private OffsetDateTime resolvedAt;
49+
50+
@Column(name = "resolved_by", length = 100)
51+
private String resolvedBy;
52+
4453
@Column(name = "created_at", nullable = false, updatable = false)
4554
private OffsetDateTime createdAt = OffsetDateTime.now();
4655

@@ -75,6 +84,15 @@ public void onUpdate() {
7584
public String getResolvedInCommitHash() { return resolvedInCommitHash; }
7685
public void setResolvedInCommitHash(String resolvedInCommitHash) { this.resolvedInCommitHash = resolvedInCommitHash; }
7786

87+
public String getResolvedDescription() { return resolvedDescription; }
88+
public void setResolvedDescription(String resolvedDescription) { this.resolvedDescription = resolvedDescription; }
89+
90+
public OffsetDateTime getResolvedAt() { return resolvedAt; }
91+
public void setResolvedAt(OffsetDateTime resolvedAt) { this.resolvedAt = resolvedAt; }
92+
93+
public String getResolvedBy() { return resolvedBy; }
94+
public void setResolvedBy(String resolvedBy) { this.resolvedBy = resolvedBy; }
95+
7896
public OffsetDateTime getCreatedAt() { return createdAt; }
7997
public OffsetDateTime getUpdatedAt() { return updatedAt; }
8098
}

java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/codeanalysis/CodeAnalysisIssue.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ public class CodeAnalysisIssue {
4343
@Column(name = "is_resolved", nullable = false)
4444
private boolean resolved;
4545

46+
// Resolution tracking fields - populated when issue is marked as resolved
47+
@Column(name = "resolved_description", columnDefinition = "TEXT")
48+
private String resolvedDescription;
49+
50+
@Column(name = "resolved_by_pr")
51+
private Long resolvedByPr;
52+
53+
@Column(name = "resolved_commit_hash", length = 40)
54+
private String resolvedCommitHash;
55+
56+
@Column(name = "resolved_analysis_id")
57+
private Long resolvedAnalysisId;
58+
59+
@Column(name = "resolved_at")
60+
private OffsetDateTime resolvedAt;
61+
62+
@Column(name = "resolved_by", length = 100)
63+
private String resolvedBy;
64+
4665
@Column(name = "created_at", nullable = false, updatable = false)
4766
private OffsetDateTime createdAt = OffsetDateTime.now();
4867

@@ -75,5 +94,23 @@ public class CodeAnalysisIssue {
7594
public boolean isResolved() { return resolved; }
7695
public void setResolved(boolean resolved) { this.resolved = resolved; }
7796

97+
public String getResolvedDescription() { return resolvedDescription; }
98+
public void setResolvedDescription(String resolvedDescription) { this.resolvedDescription = resolvedDescription; }
99+
100+
public Long getResolvedByPr() { return resolvedByPr; }
101+
public void setResolvedByPr(Long resolvedByPr) { this.resolvedByPr = resolvedByPr; }
102+
103+
public String getResolvedCommitHash() { return resolvedCommitHash; }
104+
public void setResolvedCommitHash(String resolvedCommitHash) { this.resolvedCommitHash = resolvedCommitHash; }
105+
106+
public Long getResolvedAnalysisId() { return resolvedAnalysisId; }
107+
public void setResolvedAnalysisId(Long resolvedAnalysisId) { this.resolvedAnalysisId = resolvedAnalysisId; }
108+
109+
public OffsetDateTime getResolvedAt() { return resolvedAt; }
110+
public void setResolvedAt(OffsetDateTime resolvedAt) { this.resolvedAt = resolvedAt; }
111+
112+
public String getResolvedBy() { return resolvedBy; }
113+
public void setResolvedBy(String resolvedBy) { this.resolvedBy = resolvedBy; }
114+
78115
public OffsetDateTime getCreatedAt() { return createdAt; }
79116
}

java-ecosystem/libs/core/src/main/resources/db/migration/0.2.0/V0.2.0__add_allowed_command_users_table.sql renamed to java-ecosystem/libs/core/src/main/resources/db/migration/1.0.0/V1.0.0__add_allowed_command_users_table.sql

File renamed without changes.

java-ecosystem/libs/core/src/main/resources/db/migration/0.2.0/V0.2.0__add_gitlab_provider_type.sql renamed to java-ecosystem/libs/core/src/main/resources/db/migration/1.0.0/V1.0.0__add_gitlab_provider_type.sql

File renamed without changes.

java-ecosystem/libs/core/src/main/resources/db/migration/0.2.0/V0.2.0__add_google_provider_key.sql renamed to java-ecosystem/libs/core/src/main/resources/db/migration/1.0.0/V1.0.0__add_google_provider_key.sql

File renamed without changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- Add resolution tracking fields to code_analysis_issue table
2+
-- These fields preserve the original issue data while tracking resolution metadata separately
3+
4+
ALTER TABLE code_analysis_issue
5+
ADD COLUMN IF NOT EXISTS resolved_description TEXT,
6+
ADD COLUMN IF NOT EXISTS resolved_by_pr BIGINT,
7+
ADD COLUMN IF NOT EXISTS resolved_commit_hash VARCHAR(40),
8+
ADD COLUMN IF NOT EXISTS resolved_analysis_id BIGINT,
9+
ADD COLUMN IF NOT EXISTS resolved_at TIMESTAMP WITH TIME ZONE,
10+
ADD COLUMN IF NOT EXISTS resolved_by VARCHAR(100);
11+
12+
-- Add resolution tracking fields to branch_issue table
13+
ALTER TABLE branch_issue
14+
ADD COLUMN IF NOT EXISTS resolved_description TEXT,
15+
ADD COLUMN IF NOT EXISTS resolved_at TIMESTAMP WITH TIME ZONE,
16+
ADD COLUMN IF NOT EXISTS resolved_by VARCHAR(100);
17+
18+
-- Add comments explaining the purpose of these fields
19+
COMMENT ON COLUMN code_analysis_issue.resolved_description IS 'AI or user explanation of how/why the issue was resolved';
20+
COMMENT ON COLUMN code_analysis_issue.resolved_by_pr IS 'PR number that resolved this issue (if applicable)';
21+
COMMENT ON COLUMN code_analysis_issue.resolved_commit_hash IS 'Commit hash that resolved this issue';
22+
COMMENT ON COLUMN code_analysis_issue.resolved_analysis_id IS 'Analysis ID during which this issue was resolved';
23+
COMMENT ON COLUMN code_analysis_issue.resolved_at IS 'Timestamp when the issue was marked as resolved';
24+
COMMENT ON COLUMN code_analysis_issue.resolved_by IS 'Actor who resolved the issue (user, AI-reconciliation, manual)';
25+
26+
COMMENT ON COLUMN branch_issue.resolved_description IS 'AI or user explanation of how/why the issue was resolved on this branch';
27+
COMMENT ON COLUMN branch_issue.resolved_at IS 'Timestamp when the issue was marked as resolved on this branch';
28+
COMMENT ON COLUMN branch_issue.resolved_by IS 'Actor who resolved the issue on this branch (user, AI-reconciliation, manual)';

0 commit comments

Comments
 (0)