Skip to content

Commit dfbdf77

Browse files
Setting up a new Github Action Application for AI Support (#3)
* chore(application): fixed * chore(action): updated node v * chore(action): node latest * chore(node): updated node * chore(updated): moved libs to services * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * chore(action): fixed issue with var * updated index.ts * chore(project): updating project to support a new readme * chore(action): fixed issue with var * chore(actions): fixed typo * chore(actions): fixed typo * chore(issue): added a issue manager
1 parent d38220f commit dfbdf77

File tree

8 files changed

+618
-43
lines changed

8 files changed

+618
-43
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: "[BUG] PR Magic bug report"
5+
labels: bug
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**Expected behavior**
14+
A clear and concise description of what you expected to happen.
15+
16+
**Screenshots Of Error**
17+
If applicable, add screenshots to help explain your problem.
18+
19+
**Versions**
20+
- Tagged Release Version
21+
22+
**Additional context**
23+
Add any other context about the problem here.

.github/workflows/action.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ jobs:
1010
steps:
1111
- name: Checkout code
1212
uses: actions/checkout@v2
13-
- name: Set up Node
14-
uses: actions/setup-node@v2
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: 18
16+
- name: Install dependencies
17+
run: npm install
18+
- name: Build Code
19+
run: npm run build
1520
- name: Run Custom Action
1621
uses: ./
1722
with:
18-
github-token: ${{ secrets.GITHUB_TOKEN }}
19-
pr-number: ${{ github.event.number }}
20-
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
21-
openai_model: 'gpt-4'
23+
github_token: ${{ secrets.GITHUB_TOKEN }}
24+
openai_api_key: ${{ secrets.OPEN_AI_KEY }}
25+
openai_model: "gpt-4o"
2226
review_code: true
27+
excluded_files: "node_modules, package.json, package-lock.json"

action.yml

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1-
name: 'Github PR Magic'
2-
description: 'Automatically reviewing and approving PRs'
1+
name: 'Pull Request Magic'
2+
description: 'Pull Request Magic enables you to streamline your workflow by automating Pull Request Reviews, crafting Pull Request Descriptions, and even auto-approving Pull Requests—all seamlessly integrated with Github Actions. '
33
author: 'Darrell Richards'
44
inputs:
5-
github-token:
6-
description: 'Github token'
7-
required: true
8-
pr-number:
9-
description: 'PR number'
5+
github_token:
6+
description: 'Github Token for PR approval and code review.'
107
required: true
8+
excluded_files:
9+
description: 'A comma separated list of files to exclude from code review.'
10+
required: false
11+
default: "node_modules, package-lock.json, yarn.lock"
1112
openai_api_key:
12-
description: 'OpenAI API key'
13+
description: 'OpenAI API Key for code review and approval.'
1314
required: true
1415
openai_model:
15-
description: 'OpenAI model to use (e.g., gpt-4, davinci)'
16+
description: 'OpenAI model to use (e.g., gpt-4, davinci).'
1617
required: false
1718
default: 'gpt-4'
1819
review_code:
19-
description: 'Review code'
20+
description: 'Provides code review as a comment line by line.'
2021
required: false
2122
default: true
23+
generate_summary:
24+
description: 'Generates Pull Request summary based on git diff and code changes'
25+
required: false
26+
default: false
27+
overall_code_review:
28+
description: 'Provides overall code review as a comment (Overview, Updates and Enhancements, Recommendations).'
29+
required: false
30+
default: false
2231
runs:
2332
using: 'node20'
2433
main: 'lib/index.js'

package-lock.json

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
"dependencies": {
1313
"@actions/core": "^1.10.1",
1414
"@octokit/rest": "^20.1.1",
15-
"@tandil/diffparse": "^0.2.0",
15+
"@octokit/types": "^13.5.0",
1616
"minimatch": "^9.0.4",
17-
"openai": "^4.47.3"
17+
"openai": "^4.47.3",
18+
"parse-diff": "^0.11.1"
1819
}
1920
}

src/index.ts

Lines changed: 191 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,213 @@
1-
const parser = require('@tandil/diffparse');
21
import { readFileSync } from "fs"
3-
import OpenAI from "openai"
4-
import core from "@actions/core";
5-
import { PRDetails } from "./lib/github";
2+
import parseDiff, { File } from "parse-diff"
3+
import * as core from "@actions/core"
4+
5+
import { commentOnPullRequest, compareCommits, createReviewComment, getPullRequestDiff, PRDetails, updateBody } from "./services/github";
66
import { minimatch } from "minimatch";
7+
import { obtainFeedback, prSummaryCreation, summaryAllMessages, summaryOfAllFeedback, validateCodeViaAI } from "./services/ai";
8+
9+
10+
const excludedFiles = core.getInput("excluded_files").split(",").map((s: string) => s.trim());
11+
const createPullRequestSummary = core.getInput("generate_summary");
12+
const reviewCode = core.getInput("review_code")
13+
const overallReview = core.getInput("overall_code_review");
14+
15+
16+
export interface Details {
17+
title: string;
18+
description: string;
19+
}
20+
21+
async function validatePullRequest(diff: File[], details: Details) {
22+
const foundSummary = [];
23+
for (const file of diff) {
24+
for (const chunk of file.chunks) {
25+
const message = await prSummaryCreation(file, chunk, details);
26+
if (message) {
27+
const mappedResults = message.flatMap((result: any) => {
28+
if (!result.changes) {
29+
return [];
30+
}
31+
32+
if (!result.typeChanges) {
33+
return [];
34+
}
35+
36+
if (!result.checklist) {
37+
return [];
38+
}
39+
40+
41+
return {
42+
changes: result.changes,
43+
typeChanges: result.typeChanges,
44+
checklist: result.checklist,
45+
};
46+
});
47+
48+
foundSummary.push(...mappedResults);
49+
}
50+
}
51+
}
52+
53+
54+
if (foundSummary && foundSummary.length > 0) {
55+
const compiledSummary = await summaryAllMessages(foundSummary);
56+
return compiledSummary;
57+
}
58+
59+
return '';
60+
}
61+
62+
async function validateCode(diff: File[], details: Details) {
63+
const neededComments = [];
64+
for (const file of diff) {
65+
for (const chunk of file.chunks) {
66+
const results = await validateCodeViaAI(file, chunk, details);
67+
68+
if (results) {
69+
const mappedResults = results.flatMap((result: any) => {
70+
if (!file.to) {
71+
return [];
72+
}
73+
74+
if (!result.lineNumber) {
75+
return [];
76+
}
77+
78+
if (!result.review) {
79+
return [];
80+
}
81+
82+
return {
83+
body: result.review,
84+
path: file.to,
85+
line: parseInt(result.lineNumber, 10)
86+
};
87+
});
88+
89+
if (mappedResults) {
90+
neededComments.push(...mappedResults);
91+
}
92+
}
93+
}
94+
}
95+
96+
return neededComments;
97+
}
98+
99+
async function validateOverallCodeReview(diff: File[], details: Details) {
100+
const detailedFeedback = [];
101+
for (const file of diff) {
102+
for (const chunk of file.chunks) {
103+
const results = await obtainFeedback(file, chunk, details);
104+
if (results) {
105+
const mappedResults = results.flatMap((result: any) => {
106+
if (!file.to) {
107+
return [];
108+
}
109+
110+
return {
111+
changesOverview: result.changesOverview,
112+
feedback: result.feedback,
113+
improvements: result.improvements,
114+
conclusion: result.conclusion,
115+
};
116+
});
117+
118+
if (mappedResults) {
119+
detailedFeedback.push(...mappedResults);
120+
}
121+
}
122+
}
123+
}
124+
125+
return detailedFeedback;
126+
}
7127

8-
const openai = new OpenAI()
9-
const excludedFiles = core.getInput("exclude").split(",").map((s: string) => s.trim());
10128

11129
async function main() {
12130
let dif: string | null = null;
13-
const { action, repository, number } = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH || "", "utf-8"))
131+
const { action, repository, number, before, after } = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH || "", "utf-8"))
14132
const { title, description } = await PRDetails(repository, number);
15-
16-
if (action === "opened") {
17-
// Generate a summary of the PR since it's a new PR
18-
133+
if (action === "opened" || action === "reopened") {
134+
const data = await getPullRequestDiff(repository.owner.login, repository.name, number);
135+
dif = data as unknown as string;
136+
} else if (action === "synchronize") {
137+
const newBaseSha = before;
138+
const newHeadSha = after;
139+
140+
const data = await compareCommits({
141+
owner: repository.owner.login,
142+
repo: repository.name,
143+
before: newBaseSha,
144+
after: newHeadSha,
145+
number
146+
})
147+
148+
dif = String(data);
149+
} else {
150+
console.log('Unknown action', process.env.GITHUB_EVENT_NAME);
151+
return;
19152
}
20153

21154
if (!dif) {
22-
// Well shit.
155+
console.log('No diff found, exiting')
23156
return;
24157
}
25158

26-
const diff = parser.parseDiffString(dif);
27-
const filteredDiff = diff.filter((file: { to: any; }) => {
159+
const diff = parseDiff(dif);
160+
const filteredDiff = diff.filter((file) => {
28161
return !excludedFiles.some((pattern) =>
29162
minimatch(file.to ?? "", pattern)
30163
);
31164
});
32165

33-
console.log(filteredDiff);
166+
if (action === "opened" || action === "reopened") {
167+
if (createPullRequestSummary) {
168+
console.log('Generating summary for new PR');
169+
const summary = await validatePullRequest(diff, {
170+
title,
171+
description
172+
});
34173

35-
// Validate Some Code Yo!
174+
if (summary) {
175+
await updateBody(repository.owner.login, repository.name, number, summary);
176+
}
177+
}
36178

37-
// Post some comments
179+
if (overallReview) {
180+
const detailedFeedback = await validateOverallCodeReview(filteredDiff, {
181+
title,
182+
description
183+
});
184+
185+
if (detailedFeedback && detailedFeedback.length > 0) {
186+
const resultsFullFeedback = await summaryOfAllFeedback(detailedFeedback);
187+
if (resultsFullFeedback) {
188+
await commentOnPullRequest({
189+
owner: repository.owner.login,
190+
repo: repository.name,
191+
number
192+
}, resultsFullFeedback);
193+
}
194+
}
195+
}
196+
}
197+
198+
if (reviewCode) {
199+
const neededComments = await validateCode(filteredDiff, {
200+
title,
201+
description
202+
});
203+
204+
if (neededComments) {
205+
await createReviewComment(repository.owner.login, repository.name, number, neededComments);
206+
}
207+
}
38208
}
39209

40-
main();
210+
main().catch((error) => {
211+
console.error(error);
212+
process.exit(1);
213+
});

0 commit comments

Comments
 (0)