diff --git a/frontend b/frontend index df0b8e52..62359446 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit df0b8e52b495612a1ab99c086de1cbbf8f7168d3 +Subproject commit 62359446f44e5554753f257064ccada85aad24c0 diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/analysis/issue/IssueDTO.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/analysis/issue/IssueDTO.java index 037abc87..c2733281 100644 --- a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/analysis/issue/IssueDTO.java +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/analysis/issue/IssueDTO.java @@ -20,12 +20,19 @@ public record IssueDTO ( String pullRequestId, String status, // open|resolved|ignored OffsetDateTime createdAt, - String issueCategory + String issueCategory, + // Detection info - where was this issue first found + Long analysisId, + Long prNumber, + String commitHash, + OffsetDateTime detectedAt ) { public static IssueDTO fromEntity(CodeAnalysisIssue issue) { String categoryStr = issue.getIssueCategory() != null ? issue.getIssueCategory().name() : IssueCategory.CODE_QUALITY.name(); + + var analysis = issue.getAnalysis(); return new IssueDTO( String.valueOf(issue.getId()), categoryStr, @@ -37,11 +44,16 @@ public static IssueDTO fromEntity(CodeAnalysisIssue issue) { issue.getLineNumber(), null, null, - issue.getAnalysis() == null ? null : issue.getAnalysis().getBranchName(), - issue.getAnalysis() == null || issue.getAnalysis().getPrNumber() == null ? null : String.valueOf(issue.getAnalysis().getPrNumber()), + analysis == null ? null : analysis.getBranchName(), + analysis == null || analysis.getPrNumber() == null ? null : String.valueOf(analysis.getPrNumber()), issue.isResolved() ? "resolved" : "open", issue.getCreatedAt(), - categoryStr + categoryStr, + // Detection info + analysis != null ? analysis.getId() : null, + analysis != null ? analysis.getPrNumber() : null, + analysis != null ? analysis.getCommitHash() : null, + issue.getCreatedAt() ); } } diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/persistence/repository/branch/BranchIssueRepository.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/persistence/repository/branch/BranchIssueRepository.java index 1f935352..50475879 100644 --- a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/persistence/repository/branch/BranchIssueRepository.java +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/persistence/repository/branch/BranchIssueRepository.java @@ -94,4 +94,12 @@ Page findAllByBranchIdPaged( @Modifying @Query("DELETE FROM BranchIssue bi WHERE bi.branch.id IN (SELECT b.id FROM Branch b WHERE b.project.id = :projectId)") void deleteByProjectId(@Param("projectId") Long projectId); + + // Base query for filtered branch issues - filtering is done in service layer + @Query(value = "SELECT bi FROM BranchIssue bi " + + "JOIN FETCH bi.codeAnalysisIssue cai " + + "WHERE bi.branch.id = :branchId " + + "ORDER BY cai.id DESC", + countQuery = "SELECT COUNT(bi) FROM BranchIssue bi WHERE bi.branch.id = :branchId") + List findAllByBranchIdWithIssues(@Param("branchId") Long branchId); } diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/service/CodeAnalysisService.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/service/CodeAnalysisService.java index d7fdacf5..2f6dfa6d 100644 --- a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/service/CodeAnalysisService.java +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/service/CodeAnalysisService.java @@ -176,6 +176,10 @@ public int getMaxAnalysisPrVersion(Long projectId, Long prNumber) { return codeAnalysisRepository.findMaxPrVersion(projectId, prNumber).orElse(0); } + public Optional findAnalysisByProjectAndPrNumberAndVersion(Long projectId, Long prNumber, int prVersion) { + return codeAnalysisRepository.findByProjectIdAndPrNumberAndPrVersion(projectId, prNumber, prVersion); + } + private CodeAnalysisIssue createIssueFromData(Map issueData, String issueKey) { try { CodeAnalysisIssue issue = new CodeAnalysisIssue(); diff --git a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/AnalysisIssueController.java b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/AnalysisIssueController.java index 66ff4cac..7a161542 100644 --- a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/AnalysisIssueController.java +++ b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/AnalysisIssueController.java @@ -67,6 +67,15 @@ public ResponseEntity listIssues( if(pullRequestId != null) { maxVersion = codeAnalysisService.getMaxAnalysisPrVersion(project.getId(), Long.parseLong(pullRequestId)); resp.setMaxVersion(maxVersion); + + // Fetch the analysis comment/summary for the specific version + int versionToFetch = prVersion > 0 ? prVersion : maxVersion; + var analysisOpt = codeAnalysisService.findAnalysisByProjectAndPrNumberAndVersion( + project.getId(), + Long.parseLong(pullRequestId), + versionToFetch + ); + analysisOpt.ifPresent(analysis -> resp.setAnalysisSummary(analysis.getComment())); } List issues = analysisService.findIssues(project.getId(), branch, pullRequestId, severity, type, (prVersion > 0 ? prVersion : maxVersion)); List issueDTOs = issues.stream() diff --git a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/PullRequestController.java b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/PullRequestController.java index fdab9071..00cccb46 100644 --- a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/PullRequestController.java +++ b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/controller/PullRequestController.java @@ -98,6 +98,11 @@ public ResponseEntity> listBranchIssues( @PathVariable String projectNamespace, @PathVariable String branchName, @RequestParam(required = false, defaultValue = "open") String status, + @RequestParam(required = false) String severity, + @RequestParam(required = false) String category, + @RequestParam(required = false) String filePath, + @RequestParam(required = false) String dateFrom, + @RequestParam(required = false) String dateTo, @RequestParam(required = false, defaultValue = "1") int page, @RequestParam(required = false, defaultValue = "50") int pageSize ) { @@ -114,26 +119,96 @@ public ResponseEntity> listBranchIssues( } Branch branch = branchOpt.get(); - // Use paginated queries from repository - org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(page - 1, pageSize); - org.springframework.data.domain.Page branchIssuePage; - long total; + // Normalize filter values + String normalizedStatus = (status == null || status.isBlank()) ? "open" : status.toLowerCase(); + String normalizedSeverity = (severity == null || severity.isBlank() || "ALL".equalsIgnoreCase(severity)) ? null : severity.toUpperCase(); + String normalizedCategory = (category == null || category.isBlank() || "ALL".equalsIgnoreCase(category)) ? null : category.toUpperCase(); + String normalizedFilePath = (filePath == null || filePath.isBlank()) ? null : filePath.toLowerCase(); - if ("all".equalsIgnoreCase(status)) { - branchIssuePage = branchIssueRepository.findAllByBranchIdPaged(branch.getId(), pageable); - total = branchIssueRepository.countAllByBranchId(branch.getId()); - } else if ("resolved".equalsIgnoreCase(status)) { - branchIssuePage = branchIssueRepository.findResolvedByBranchIdPaged(branch.getId(), pageable); - total = branchIssueRepository.countResolvedByBranchId(branch.getId()); - } else { - // Default to "open" (unresolved) - branchIssuePage = branchIssueRepository.findUnresolvedByBranchIdPaged(branch.getId(), pageable); - total = branchIssueRepository.countUnresolvedByBranchId(branch.getId()); + // Parse date filters + java.time.OffsetDateTime parsedDateFrom = null; + java.time.OffsetDateTime parsedDateTo = null; + if (dateFrom != null && !dateFrom.isBlank()) { + try { + parsedDateFrom = java.time.OffsetDateTime.parse(dateFrom); + } catch (Exception e) { + try { + parsedDateFrom = java.time.LocalDate.parse(dateFrom).atStartOfDay().atOffset(java.time.ZoneOffset.UTC); + } catch (Exception ignored) {} + } + } + if (dateTo != null && !dateTo.isBlank()) { + try { + parsedDateTo = java.time.OffsetDateTime.parse(dateTo); + } catch (Exception e) { + try { + parsedDateTo = java.time.LocalDate.parse(dateTo).atTime(23, 59, 59).atOffset(java.time.ZoneOffset.UTC); + } catch (Exception ignored) {} + } } + + //TODO: use SQL instead.... + // Fetch all issues for the branch and filter in Java (avoids complex SQL with nullable params) + List allBranchIssues = branchIssueRepository.findAllByBranchIdWithIssues(branch.getId()); + + // Apply filters in Java + final java.time.OffsetDateTime finalDateFrom = parsedDateFrom; + final java.time.OffsetDateTime finalDateTo = parsedDateTo; - List pagedIssues = branchIssuePage.getContent().stream() + List filteredIssues = allBranchIssues.stream() + .filter(bi -> { + // Status filter + if ("open".equals(normalizedStatus) && bi.isResolved()) return false; + if ("resolved".equals(normalizedStatus) && !bi.isResolved()) return false; + // "all" passes everything + + var issue = bi.getCodeAnalysisIssue(); + if (issue == null) return false; + + // Severity filter + if (normalizedSeverity != null) { + if (issue.getSeverity() == null) return false; + if (!normalizedSeverity.equals(issue.getSeverity().name())) return false; + } + + // Category filter + if (normalizedCategory != null) { + if (issue.getIssueCategory() == null) return false; + if (!normalizedCategory.equals(issue.getIssueCategory().name())) return false; + } + + // File path filter (partial match, case insensitive) + if (normalizedFilePath != null) { + if (issue.getFilePath() == null) return false; + if (!issue.getFilePath().toLowerCase().contains(normalizedFilePath)) return false; + } + + // Date from filter + if (finalDateFrom != null && issue.getCreatedAt() != null) { + if (issue.getCreatedAt().isBefore(finalDateFrom)) return false; + } + + // Date to filter + if (finalDateTo != null && issue.getCreatedAt() != null) { + if (issue.getCreatedAt().isAfter(finalDateTo)) return false; + } + + return true; + }) + .sorted((a, b) -> Long.compare(b.getCodeAnalysisIssue().getId(), a.getCodeAnalysisIssue().getId())) + .toList(); + + long total = filteredIssues.size(); + + // Apply pagination + int startIndex = (page - 1) * pageSize; + int endIndex = Math.min(startIndex + pageSize, filteredIssues.size()); + + List pagedIssues = (startIndex < filteredIssues.size()) + ? filteredIssues.subList(startIndex, endIndex).stream() .map(bi -> IssueDTO.fromEntity(bi.getCodeAnalysisIssue())) - .toList(); + .toList() + : List.of(); Map response = new HashMap<>(); response.put("issues", pagedIssues); diff --git a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/dto/response/AnalysisIssueResponse.java b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/dto/response/AnalysisIssueResponse.java index 37f28300..e375afa9 100644 --- a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/dto/response/AnalysisIssueResponse.java +++ b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/analysis/dto/response/AnalysisIssueResponse.java @@ -10,6 +10,7 @@ public class AnalysisIssueResponse { private List issues = new ArrayList<>(); private IssuesSummaryDTO summary = new IssuesSummaryDTO(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); private int maxVersion; + private String analysisSummary; // The comment/summary from the CodeAnalysis public AnalysisIssueResponse() { } @@ -34,4 +35,11 @@ public int getMaxVersion() { public void setMaxVersion(int maxVersion) { this.maxVersion = maxVersion; } + + public String getAnalysisSummary() { + return analysisSummary; + } + public void setAnalysisSummary(String analysisSummary) { + this.analysisSummary = analysisSummary; + } } diff --git a/python-ecosystem/mcp-client/.gitignore b/python-ecosystem/mcp-client/.gitignore index 10ce76db..5fa887fe 100644 --- a/python-ecosystem/mcp-client/.gitignore +++ b/python-ecosystem/mcp-client/.gitignore @@ -13,4 +13,5 @@ target/ server.log *.jar -logs/** \ No newline at end of file +logs/** +**/__pycache__/ \ No newline at end of file diff --git a/python-ecosystem/mcp-client/__pycache__/main.cpython-313.pyc b/python-ecosystem/mcp-client/__pycache__/main.cpython-313.pyc deleted file mode 100644 index 69b21a3f..00000000 Binary files a/python-ecosystem/mcp-client/__pycache__/main.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/model/__pycache__/models.cpython-313.pyc b/python-ecosystem/mcp-client/model/__pycache__/models.cpython-313.pyc deleted file mode 100644 index 67272203..00000000 Binary files a/python-ecosystem/mcp-client/model/__pycache__/models.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/server/__pycache__/stdin_handler.cpython-313.pyc b/python-ecosystem/mcp-client/server/__pycache__/stdin_handler.cpython-313.pyc deleted file mode 100644 index f182f559..00000000 Binary files a/python-ecosystem/mcp-client/server/__pycache__/stdin_handler.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/server/__pycache__/web_server.cpython-313.pyc b/python-ecosystem/mcp-client/server/__pycache__/web_server.cpython-313.pyc deleted file mode 100644 index ae3e6052..00000000 Binary files a/python-ecosystem/mcp-client/server/__pycache__/web_server.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/service/__pycache__/review_service.cpython-313.pyc b/python-ecosystem/mcp-client/service/__pycache__/review_service.cpython-313.pyc deleted file mode 100644 index 7ada6f89..00000000 Binary files a/python-ecosystem/mcp-client/service/__pycache__/review_service.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/utils/__pycache__/prompt_builder.cpython-313.pyc b/python-ecosystem/mcp-client/utils/__pycache__/prompt_builder.cpython-313.pyc deleted file mode 100644 index e519924d..00000000 Binary files a/python-ecosystem/mcp-client/utils/__pycache__/prompt_builder.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/utils/__pycache__/prompt_logger.cpython-313.pyc b/python-ecosystem/mcp-client/utils/__pycache__/prompt_logger.cpython-313.pyc deleted file mode 100644 index 572e0361..00000000 Binary files a/python-ecosystem/mcp-client/utils/__pycache__/prompt_logger.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/utils/__pycache__/response_parser.cpython-313.pyc b/python-ecosystem/mcp-client/utils/__pycache__/response_parser.cpython-313.pyc deleted file mode 100644 index ccb13759..00000000 Binary files a/python-ecosystem/mcp-client/utils/__pycache__/response_parser.cpython-313.pyc and /dev/null differ diff --git a/python-ecosystem/mcp-client/utils/prompt_builder.py b/python-ecosystem/mcp-client/utils/prompt_builder.py index fb7be5e0..724829eb 100644 --- a/python-ecosystem/mcp-client/utils/prompt_builder.py +++ b/python-ecosystem/mcp-client/utils/prompt_builder.py @@ -228,7 +228,7 @@ def build_first_review_prompt( - Do NOT include any "id" field in issues - it will be assigned by the system - Each issue MUST have: severity, category, file, line, reason, isResolved - REQUIRED FOR ALL ISSUES: Include "suggestedFixDescription" AND "suggestedFixDiff" with actual code fix in unified diff format -- The suggestedFixDiff must show the exact code change to fix the issue - this is MANDATORY, not optional +- The suggestedFixDiff must show the exact code change to fix the issue If no issues are found, return: {{ @@ -351,7 +351,7 @@ def build_review_prompt_with_previous_analysis_data( - Do NOT include any "id" field in issues - it will be assigned by the system - Each issue MUST have: severity, category, file, line, reason, isResolved - REQUIRED FOR ALL ISSUES: Include "suggestedFixDescription" AND "suggestedFixDiff" with actual code fix in unified diff format -- The suggestedFixDiff must show the exact code change to fix the issue - this is MANDATORY, not optional +- The suggestedFixDiff must show the exact code change to fix the issue If no issues are found, return: {{ diff --git a/python-ecosystem/rag-pipeline/.gitignore b/python-ecosystem/rag-pipeline/.gitignore index d8ba1cca..292e697b 100644 --- a/python-ecosystem/rag-pipeline/.gitignore +++ b/python-ecosystem/rag-pipeline/.gitignore @@ -19,4 +19,5 @@ build/ .vscode/ logs/** +**/__pycache__/