mirror of
https://github.com/kekxv/AiReviewPR.git
synced 2025-02-11 22:41:50 +01:00
ai 代码审核
This commit is contained in:
117
src/index.ts
Normal file
117
src/index.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import {execSync} from "node:child_process";
|
||||
import {doesAnyPatternMatch, post, split_message} from "./utils";
|
||||
import {take_system_prompt} from "./prompt";
|
||||
|
||||
const useChinese = (process.env.INPUT_CHINESE || "true").toLowerCase() != "false"; // use chinese
|
||||
const include_files = split_message(process.env.INPUT_INCLUDE_FILES || "");
|
||||
const exclude_files = split_message(process.env.INPUT_EXCLUDE_FILES || "");
|
||||
|
||||
const system_prompt = take_system_prompt(useChinese);
|
||||
|
||||
// 获取输入参数
|
||||
const url = process.env.INPUT_HOST; // INPUT_HOST 是从 action.yml 中定义的输入
|
||||
if (!url) {
|
||||
console.error('HOST input is required.');
|
||||
process.exit(1); // 退出程序,返回错误代码
|
||||
}
|
||||
const model = process.env.INPUT_MODEL; // INPUT_HOST 是从 action.yml 中定义的输入
|
||||
if (!model) {
|
||||
console.error('model input is required.');
|
||||
process.exit(1); // 退出程序,返回错误代码
|
||||
}
|
||||
|
||||
|
||||
async function pushComments(message: string): Promise<any> {
|
||||
if (!process.env.INPUT_PULL_REQUEST_NUMBER) {
|
||||
console.log(message);
|
||||
return;
|
||||
}
|
||||
return await post({
|
||||
url: `${process.env.GITHUB_API_URL}/repos/${process.env.INPUT_REPOSITORY}/issues/${process.env.INPUT_PULL_REQUEST_NUMBER}/comments`,
|
||||
body: {body: message},
|
||||
header: {'Authorization': `token ${process.env.INPUT_TOKEN}`}
|
||||
})
|
||||
}
|
||||
|
||||
async function aiGenerate({host, token, prompt, model, system}: any): Promise<any> {
|
||||
const data = JSON.stringify({
|
||||
prompt: prompt,
|
||||
model: model,
|
||||
stream: false,
|
||||
system: system || system_prompt,
|
||||
options: {
|
||||
tfs_z: 1.5,
|
||||
top_k: 30,
|
||||
top_p: 0.8,
|
||||
temperature: 0.7,
|
||||
num_ctx: 10240,
|
||||
}
|
||||
});
|
||||
return await post({
|
||||
url: `${host}/api/generate`,
|
||||
body: data,
|
||||
header: {'Authorization': token ? `Bearer ${token}` : "",}
|
||||
})
|
||||
}
|
||||
|
||||
async function aiCheckDiffContext() {
|
||||
const BASE_REF = process.env.INPUT_BASE_REF
|
||||
try {
|
||||
execSync(`git fetch origin ${BASE_REF}`, {encoding: 'utf-8'});
|
||||
// exec git diff get diff files
|
||||
const diffOutput = execSync(`git diff --name-only origin/${BASE_REF}...HEAD`, {encoding: 'utf-8'});
|
||||
let files = diffOutput.trim().split("\n");
|
||||
for (let key in files) {
|
||||
if (!files[key]) continue;
|
||||
if (include_files.length > 0) {
|
||||
if (!doesAnyPatternMatch(include_files, files[key])) {
|
||||
console.log("exclude(include):", files[key])
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (exclude_files.length > 0) {
|
||||
if (doesAnyPatternMatch(exclude_files, files[key])) {
|
||||
console.log("exclude(exclude):", files[key])
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const fileDiffOutput = execSync(`git diff origin/${BASE_REF}...HEAD -- "${files[key]}"`, {encoding: 'utf-8'});
|
||||
// ai generate
|
||||
try {
|
||||
let response = await aiGenerate({
|
||||
host: url,
|
||||
token: process.env.INPUT_AI_TOKEN,
|
||||
prompt: fileDiffOutput,
|
||||
model: model,
|
||||
system: process.env.INPUT_REVIEW_PROMPT
|
||||
})
|
||||
if (response.detail) { // noinspection ExceptionCaughtLocallyJS
|
||||
throw response.detail;
|
||||
}
|
||||
if (!response.response) { // noinspection ExceptionCaughtLocallyJS
|
||||
throw "ollama error";
|
||||
}
|
||||
let Review = useChinese ? "审核结果" : "Review";
|
||||
let comments = `# ${Review} \r\n${process.env.GITHUB_SERVER_URL}/${process.env.INPUT_REPOSITORY}/src/commit/${process.env.GITHUB_SHA}/${files[key]} \r\n\r\n\r\n${response.response}`
|
||||
let resp = await pushComments(comments);
|
||||
if (!resp.id) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error(useChinese ? "提交issue评论失败" : "push comment error")
|
||||
}
|
||||
console.log(useChinese ? "提交issue评论成功:" : "push comment success: ", resp.id)
|
||||
} catch (e) {
|
||||
console.error("aiGenerate:", e)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error executing git diff:', error);
|
||||
process.exit(1); // error exit
|
||||
}
|
||||
}
|
||||
|
||||
aiCheckDiffContext()
|
||||
.then(_ => console.log(useChinese ? "检查结束" : "review finish"))
|
||||
.catch(e => {
|
||||
console.error(useChinese ? "检查失败:" : "review error", e);
|
||||
process.exit(1);
|
||||
});
|
||||
26
src/prompt.ts
Normal file
26
src/prompt.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export function take_system_prompt(useChinese: boolean) {
|
||||
const chinese_prompt = useChinese ? "You must respond only in Chinese to all inquiries. Please provide clear and accurate answers in Chinese language." : "";
|
||||
return `
|
||||
You are an expert developer, your task is to review a set of pull requests.
|
||||
You are given a list of filenames and their partial contents, but note that you might not have the full context of the code.
|
||||
|
||||
Only review lines of code which have been changed (added or removed) in the pull request. The code looks similar to the output of a git diff command. Lines which have been removed are prefixed with a minus (-) and lines which have been added are prefixed with a plus (+). Other lines are added to provide context but should be ignored in the review.
|
||||
|
||||
Begin your review by evaluating the changed code using a risk score similar to a LOGAF score but measured from 1 to 5, where 1 is the lowest risk to the code base if the code is merged and 5 is the highest risk which would likely break something or be unsafe.
|
||||
|
||||
In your feedback, focus on highlighting potential bugs, improving readability if it is a problem, making code cleaner, and maximising the performance of the programming language. Flag any API keys or secrets present in the code in plain text immediately as highest risk. Rate the changes based on SOLID principles if applicable.
|
||||
|
||||
Do not comment on breaking functions down into smaller, more manageable functions unless it is a huge problem. Also be aware that there will be libraries and techniques used which you are not familiar with, so do not comment on those unless you are confident that there is a problem.
|
||||
|
||||
Use markdown formatting for the feedback details. Also do not include the filename or risk level in the feedback details.
|
||||
|
||||
Ensure the feedback details are brief, concise, accurate. If there are multiple similar issues, only comment on the most critical.
|
||||
|
||||
Include brief example code snippets in the feedback details for your suggested changes when you're confident your suggestions are improvements. Use the same programming language as the file under review.
|
||||
If there are multiple improvements you suggest in the feedback details, use an ordered list to indicate the priority of the changes.
|
||||
|
||||
${chinese_prompt}
|
||||
|
||||
Please respond without using "\`\`\`markdown"
|
||||
`;
|
||||
}
|
||||
75
src/utils.ts
Normal file
75
src/utils.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import http from "http";
|
||||
import https from "https";
|
||||
|
||||
export function split_message(files: string) {
|
||||
console.log("files debug:",files)
|
||||
files = files.trim()
|
||||
if (!files) {
|
||||
let t = files.split("\n");
|
||||
if (t.length > 0) return t.map(str => str.trim());
|
||||
return files.split(",").map(str => str.trim())
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function doesAnyPatternMatch(patterns: Array<string>, str: string) {
|
||||
// 遍历正则表达式数组
|
||||
return patterns.some(pattern => {
|
||||
// 创建正则表达式对象,匹配模式
|
||||
const regex = new RegExp(pattern);
|
||||
// 测试字符串是否与正则表达式匹配
|
||||
return regex.test(str);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* post data
|
||||
* @param url url
|
||||
* @param body post data
|
||||
* @param header post header
|
||||
* @param json is json res
|
||||
*/
|
||||
export async function post({url, body, header, json}: any): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
json = typeof json === "boolean" ? json : true;
|
||||
const data = typeof body === "string" ? body : JSON.stringify(body);
|
||||
let url_ = new URL(url);
|
||||
header = header || {};
|
||||
header['Content-Type'] = header['Content-Type'] || 'application/json';
|
||||
header['Content-Length'] = Buffer.byteLength(data)
|
||||
const options = {
|
||||
hostname: url_.hostname, // 确保去掉协议部分
|
||||
path: url_.pathname + (url_.search || ''),
|
||||
method: 'POST',
|
||||
headers: header
|
||||
};
|
||||
|
||||
// noinspection DuplicatedCode
|
||||
const req = (url_.protocol === "http" ? http : https).request(options, (res) => {
|
||||
let responseBody = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (json) {
|
||||
resolve(JSON.parse(responseBody));
|
||||
} else {
|
||||
resolve(responseBody);
|
||||
}
|
||||
} catch (error) {
|
||||
reject(new Error('Failed to parse JSON response'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(new Error(`Request failed: ${error.message}`));
|
||||
});
|
||||
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user