mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 00:42:30 +00:00
Merge pull request #1523 from PR0M3TH3AN/agent-pr-review-8429910653145820727
feat: Automated Agent PR Review System
This commit is contained in:
2 files changed
+210
No files matched your search
@@ -0,0 +1,34 @@
|
||||
name: Automated Agent PR Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
agent-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- name: Run Agent Review
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
GITHUB_BASE_REF: ${{ github.base_ref }}
|
||||
GITHUB_BASE_REPO_URL: ${{ github.event.pull_request.base.repo.clone_url }}
|
||||
run: node scripts/automated-pr-review.mjs
|
||||
@@ -0,0 +1,176 @@
|
||||
import { spawnSync, execSync } from 'node:child_process';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
// Configurations
|
||||
const SENSITIVE_PATHS = [
|
||||
/^js\/nostr\//,
|
||||
/^js\/storage\//,
|
||||
/^js\/dmDecryptor\.js$/
|
||||
];
|
||||
|
||||
function runCommand(command, args = [], options = {}) {
|
||||
console.log(`\n> ${command} ${args.join(' ')}`);
|
||||
return spawnSync(command, args, { stdio: 'pipe', encoding: 'utf-8', ...options });
|
||||
}
|
||||
|
||||
function getChangedFiles() {
|
||||
try {
|
||||
const baseRef = process.env.GITHUB_BASE_REF || 'main';
|
||||
const baseUrl = process.env.GITHUB_BASE_REPO_URL;
|
||||
let remote = 'origin';
|
||||
|
||||
if (baseUrl) {
|
||||
remote = 'upstream';
|
||||
try {
|
||||
execSync(`git remote add upstream ${baseUrl}`, { stdio: 'ignore' });
|
||||
} catch (e) {
|
||||
// Ignore if remote already exists
|
||||
}
|
||||
console.log(`Fetching ${remote}/${baseRef} from ${baseUrl}...`);
|
||||
try {
|
||||
execSync(`git fetch ${remote} ${baseRef} --depth=1`, { stdio: 'ignore' });
|
||||
} catch (e) {
|
||||
console.error(`Failed to fetch ${remote}/${baseRef}:`, e.message);
|
||||
}
|
||||
} else {
|
||||
// Fallback for local testing or if env var missing
|
||||
try {
|
||||
execSync(`git fetch origin ${baseRef} --depth=1`, { stdio: 'ignore' });
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// We want the files changed in this PR.
|
||||
// Use 3-dot diff to compare HEAD against the merge base with upstream
|
||||
const cmd = `git diff --name-only ${remote}/${baseRef}...HEAD`;
|
||||
console.log(`Running diff: ${cmd}`);
|
||||
const output = execSync(cmd, { encoding: 'utf-8' });
|
||||
return output.split('\n').filter(Boolean).map(f => f.trim());
|
||||
} catch (error) {
|
||||
console.error('Error getting changed files:', error.message);
|
||||
// Fallback: try 2-dot diff if 3-dot failed (e.g. shallow clone issues)
|
||||
try {
|
||||
const baseRef = process.env.GITHUB_BASE_REF || 'main';
|
||||
const remote = process.env.GITHUB_BASE_REPO_URL ? 'upstream' : 'origin';
|
||||
const cmd = `git diff --name-only ${remote}/${baseRef} HEAD`;
|
||||
console.log(`Retrying with 2-dot diff: ${cmd}`);
|
||||
const output = execSync(cmd, { encoding: 'utf-8' });
|
||||
return output.split('\n').filter(Boolean).map(f => f.trim());
|
||||
} catch (e) {
|
||||
console.error('Fallback diff failed:', e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Starting Automated PR Review...');
|
||||
let commentBody = '## 🤖 Automated PR Review\n\n';
|
||||
let hasFailures = false;
|
||||
let hasFormatChanges = false;
|
||||
let microFixesStatus = '';
|
||||
|
||||
// 1. Install Dependencies
|
||||
if (process.env.CI) {
|
||||
runCommand('npm', ['ci']);
|
||||
}
|
||||
|
||||
// 2. Format
|
||||
console.log('Checking formatting...');
|
||||
const formatRes = runCommand('npm', ['run', 'format']);
|
||||
try {
|
||||
const diff = execSync('git diff --name-only', { encoding: 'utf-8' });
|
||||
if (diff.trim().length > 0) {
|
||||
hasFormatChanges = true;
|
||||
commentBody += '### 🎨 Formatting\n\n';
|
||||
commentBody += 'Formatting changes detected.\n\n';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error checking git diff for format:', e);
|
||||
}
|
||||
|
||||
// 3. Lint
|
||||
console.log('Running lint...');
|
||||
const lintRes = runCommand('npm', ['run', 'lint']);
|
||||
if (lintRes.status !== 0) {
|
||||
hasFailures = true;
|
||||
commentBody += '### ⚠️ Lint Warnings/Errors\n\n';
|
||||
commentBody += '```\n' + lintRes.stdout + '\n' + lintRes.stderr + '\n```\n\n';
|
||||
}
|
||||
|
||||
// 4. Unit Tests
|
||||
console.log('Running unit tests...');
|
||||
const testRes = runCommand('npm', ['run', 'test:unit']);
|
||||
if (testRes.status !== 0) {
|
||||
hasFailures = true;
|
||||
commentBody += '### ❌ Test Failures\n\n';
|
||||
commentBody += '```\n' + testRes.stdout + '\n' + testRes.stderr + '\n```\n\n';
|
||||
commentBody += '> **Suggestion:** Inspect the stack trace above. Run `npm run test:unit` locally to reproduce.\n\n';
|
||||
} else {
|
||||
commentBody += '### ✅ Tests Passed\n\nAll unit tests passed.\n\n';
|
||||
}
|
||||
|
||||
// 5. Security/Protocol Review Flags
|
||||
const changedFiles = getChangedFiles();
|
||||
const sensitiveChanges = changedFiles.filter(file =>
|
||||
SENSITIVE_PATHS.some(pattern => pattern.test(file))
|
||||
);
|
||||
|
||||
if (sensitiveChanges.length > 0) {
|
||||
commentBody += '### 🛡️ Guardrails\n\n';
|
||||
commentBody += 'This PR modifies sensitive files. **Security and Protocol Review Required.**\n';
|
||||
commentBody += '`requires-security-review` `requires-protocol-review`\n\n';
|
||||
commentBody += '<details><summary>Sensitive Files Touched</summary>\n\n';
|
||||
sensitiveChanges.forEach(f => commentBody += `- ${f}\n`);
|
||||
commentBody += '\n</details>\n\n';
|
||||
}
|
||||
|
||||
// 6. Post Comment (if in CI)
|
||||
if (process.env.GITHUB_TOKEN && process.env.PR_NUMBER) {
|
||||
|
||||
// Attempt Micro-fixes
|
||||
if (hasFormatChanges) {
|
||||
console.log('Attempting to commit formatting fixes...');
|
||||
try {
|
||||
execSync('git config user.name "bitvid-agent"');
|
||||
execSync('git config user.email "[email protected]"');
|
||||
execSync('git add .');
|
||||
execSync('git commit -m "fix(ai): formatting (agent)"');
|
||||
execSync('git push');
|
||||
|
||||
commentBody += '> **✅ Micro-fixes applied:** Formatting fixes have been committed to this branch.\n\n';
|
||||
} catch (e) {
|
||||
console.error('Failed to auto-commit/push:', e.message);
|
||||
commentBody += '> **ℹ️ Micro-fixes available:** Formatting issues were found, but I could not automatically commit the fixes (likely due to permissions on a fork). Please run `npm run format` locally and push.\n\n';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Posting comment to PR #' + process.env.PR_NUMBER);
|
||||
try {
|
||||
const gh = spawnSync('gh', ['pr', 'comment', process.env.PR_NUMBER, '-F', '-'], {
|
||||
input: commentBody,
|
||||
stdio: ['pipe', 'inherit', 'inherit']
|
||||
});
|
||||
|
||||
if (gh.status !== 0) {
|
||||
console.error('Failed to post comment via gh CLI');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error posting comment:', e);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('Not in CI or missing tokens. Printing report to stdout:');
|
||||
console.log('---------------------------------------------------');
|
||||
console.log(commentBody);
|
||||
console.log('---------------------------------------------------');
|
||||
}
|
||||
|
||||
if (hasFailures) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user