From 4973c90ca6de0e9376d3c69fa692acfd7d24b7d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Sep 2025 22:41:31 +0000 Subject: [PATCH 1/4] Initial plan From cc6fc865f06b772a72d66a220239172aa147656f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Sep 2025 22:52:36 +0000 Subject: [PATCH 2/4] Implement issue linkage tracking in PR analysis Co-authored-by: ChrisTimperley <523560+ChrisTimperley@users.noreply.github.com> --- README.md | 2 ++ src/services/github.ts | 2 ++ src/types/index.ts | 1 + src/utils/helpers.ts | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/README.md b/README.md index 5cf84ab..dc38241 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A simple GitHub repo scanner that assesses for SWE best practices and good teamw ## Features - 📊 **PR Analysis**: Analyze pull requests for lines changed, files modified, commit count, and review activity +- 🔗 **Issue Linking Detection**: Track whether PRs are linked to issues via closing keywords - 🔍 **Date Range Filtering**: Scan PRs within specific date ranges - 📈 **CI/CD Monitoring**: Track continuous integration success rates - 📝 **YAML Reports**: Generate comprehensive reports in YAML format @@ -138,6 +139,7 @@ pullRequests: hasReviews: true hasComments: true ciStatus: success + linkedIssue: 5678 url: https://github.com/microsoft/vscode/pull/1234 summary: averageLinesChanged: 175 diff --git a/src/services/github.ts b/src/services/github.ts index bed6e85..75d9856 100644 --- a/src/services/github.ts +++ b/src/services/github.ts @@ -1,5 +1,6 @@ import { Octokit } from '@octokit/rest'; import { PullRequestAnalysis } from '../types'; +import { extractLinkedIssue } from '../utils/helpers'; export class GitHubService { private octokit: Octokit; @@ -168,6 +169,7 @@ export class GitHubService { hasReviews: reviews.data.length > 0, hasComments: comments.data.length > 0, ciStatus, + linkedIssue: extractLinkedIssue(pr.body), url: pr.html_url, }; } diff --git a/src/types/index.ts b/src/types/index.ts index 976bde1..16b01ce 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -18,6 +18,7 @@ export interface PullRequestAnalysis { hasReviews: boolean; hasComments: boolean; ciStatus?: 'success' | 'failure' | 'pending' | 'unknown'; + linkedIssue: number | null; url: string; } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 7bdd7d8..998bd6a 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -60,3 +60,37 @@ export function formatDuration(start: Date, end: Date): string { return `${hours}h ${minutes}m`; } } + +/** + * Extracts the first linked issue number from a PR body. + * Supports various formats like "closes #123", "fixes #456", "resolves #789", + * "closes https://github.com/owner/repo/issues/123", etc. + * + * @param body The PR body text + * @returns The issue number if found, null otherwise + */ +export function extractLinkedIssue(body: string | null): number | null { + if (!body) { + return null; + } + + // Pattern to match various issue linking formats: + // - "closes #123", "fixes #456", "resolves #789" (case insensitive) + // - "closes https://github.com/owner/repo/issues/123" + // - "close #123", "fix #456", "resolve #789" (singular forms) + const patterns = [ + // Match "closes/fixes/resolves #number" format + /(?:closes?|fixes?|resolves?)\s+#(\d+)/i, + // Match "closes/fixes/resolves https://github.com/.../issues/number" format + /(?:closes?|fixes?|resolves?)\s+https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/i + ]; + + for (const pattern of patterns) { + const match = body.match(pattern); + if (match && match[1]) { + return parseInt(match[1], 10); + } + } + + return null; +} From 694b979bec7edc6657193ac83ac28844567ce583 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Sep 2025 23:19:33 +0000 Subject: [PATCH 3/4] Replace PR body parsing with GitHub GraphQL API for issue linking Co-authored-by: ChrisTimperley <523560+ChrisTimperley@users.noreply.github.com> --- src/services/github.ts | 40 +++++++++++++++++++++++++++++++++++++--- src/utils/helpers.ts | 34 ---------------------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/services/github.ts b/src/services/github.ts index 75d9856..ff57263 100644 --- a/src/services/github.ts +++ b/src/services/github.ts @@ -1,6 +1,5 @@ import { Octokit } from '@octokit/rest'; import { PullRequestAnalysis } from '../types'; -import { extractLinkedIssue } from '../utils/helpers'; export class GitHubService { private octokit: Octokit; @@ -99,7 +98,7 @@ export class GitHubService { ): Promise { console.log(` 📊 Fetching detailed data for PR #${prNumber}...`); - const [prData, files, commits, reviews, comments, checkRuns] = + const [prData, files, commits, reviews, comments, checkRuns, linkedIssue] = await Promise.all([ this.octokit.pulls.get({ owner, repo, pull_number: prNumber }), this.octokit.pulls.listFiles({ owner, repo, pull_number: prNumber }), @@ -111,6 +110,7 @@ export class GitHubService { issue_number: prNumber, }), this.getCheckRuns(owner, repo, prNumber), + this.getLinkedIssues(owner, repo, prNumber), ]); console.log(` ✅ Completed analysis for PR #${prNumber}`); @@ -169,7 +169,7 @@ export class GitHubService { hasReviews: reviews.data.length > 0, hasComments: comments.data.length > 0, ciStatus, - linkedIssue: extractLinkedIssue(pr.body), + linkedIssue, url: pr.html_url, }; } @@ -199,6 +199,40 @@ export class GitHubService { } } + private async getLinkedIssues( + owner: string, + repo: string, + prNumber: number + ): Promise { + try { + const query = ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + closingIssuesReferences(first: 1) { + nodes { + number + } + } + } + } + } + `; + + const result: any = await this.octokit.graphql(query, { + owner, + repo, + number: prNumber, + }); + + const closingIssues = result.repository.pullRequest.closingIssuesReferences.nodes; + return closingIssues.length > 0 ? closingIssues[0].number : null; + } catch (error) { + console.warn(`Failed to get linked issues for PR ${prNumber}:`, error); + return null; + } + } + async validateRepository(owner: string, repo: string): Promise { try { await this.octokit.repos.get({ owner, repo }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 998bd6a..7bdd7d8 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -60,37 +60,3 @@ export function formatDuration(start: Date, end: Date): string { return `${hours}h ${minutes}m`; } } - -/** - * Extracts the first linked issue number from a PR body. - * Supports various formats like "closes #123", "fixes #456", "resolves #789", - * "closes https://github.com/owner/repo/issues/123", etc. - * - * @param body The PR body text - * @returns The issue number if found, null otherwise - */ -export function extractLinkedIssue(body: string | null): number | null { - if (!body) { - return null; - } - - // Pattern to match various issue linking formats: - // - "closes #123", "fixes #456", "resolves #789" (case insensitive) - // - "closes https://github.com/owner/repo/issues/123" - // - "close #123", "fix #456", "resolve #789" (singular forms) - const patterns = [ - // Match "closes/fixes/resolves #number" format - /(?:closes?|fixes?|resolves?)\s+#(\d+)/i, - // Match "closes/fixes/resolves https://github.com/.../issues/number" format - /(?:closes?|fixes?|resolves?)\s+https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/i - ]; - - for (const pattern of patterns) { - const match = body.match(pattern); - if (match && match[1]) { - return parseInt(match[1], 10); - } - } - - return null; -} From 01bf503181a5ed200b809fec575a78dfec76bec7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Sep 2025 23:28:55 +0000 Subject: [PATCH 4/4] Fix code formatting with Prettier Co-authored-by: ChrisTimperley <523560+ChrisTimperley@users.noreply.github.com> --- src/services/github.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/github.ts b/src/services/github.ts index ff57263..3dec017 100644 --- a/src/services/github.ts +++ b/src/services/github.ts @@ -225,7 +225,8 @@ export class GitHubService { number: prNumber, }); - const closingIssues = result.repository.pullRequest.closingIssuesReferences.nodes; + const closingIssues = + result.repository.pullRequest.closingIssuesReferences.nodes; return closingIssues.length > 0 ? closingIssues[0].number : null; } catch (error) { console.warn(`Failed to get linked issues for PR ${prNumber}:`, error);