diff --git a/scripts/agent/automated-pr-review.mjs b/scripts/agent/automated-pr-review.mjs
new file mode 100644
index 00000000..19ed4dfd
--- /dev/null
+++ b/scripts/agent/automated-pr-review.mjs
@@ -0,0 +1,127 @@
+import { spawnSync, execSync } from 'child_process';
+import fs from 'fs';
+import path from 'path';
+
+const REPORT_FILE = 'PR_REVIEW.md';
+const APPLY_FIXES = process.argv.includes('--apply-fixes');
+
+function run(command, args, opts = {}) {
+ console.log(`> ${command} ${args.join(' ')}`);
+ return spawnSync(command, args, { encoding: 'utf-8', stdio: 'pipe', ...opts });
+}
+
+function getDiffFiles() {
+ try {
+ // Try to compare against origin/main, fallback to HEAD~1 or just check current changes if no upstream
+ // Since this runs on a checkout, we want to see what this branch touches compared to main.
+ // If origin/main is not available, we might fail.
+ const hasMain = run('git', ['rev-parse', '--verify', 'origin/main']).status === 0;
+ const base = hasMain ? 'origin/main' : 'HEAD';
+
+ // Check if we are on main? No, we are on a PR branch usually.
+ // We want the diff of the *current* code vs main.
+ const res = run('git', ['diff', '--name-only', base, 'HEAD']);
+ if (res.error || res.status !== 0) return [];
+ return res.stdout.trim().split('\n').filter(Boolean);
+ } catch (e) {
+ console.error("Error getting diff:", e);
+ return [];
+ }
+}
+
+function main() {
+ const report = [];
+ report.push('# Automated PR Review');
+ report.push(`Date: ${new Date().toISOString()}`);
+ report.push('');
+
+ // 1. npm ci
+ console.log('Running npm ci...');
+ const ci = run('npm', ['ci']);
+ if (ci.status !== 0) {
+ report.push('## 🚨 Dependency Install Failed');
+ report.push('```');
+ report.push(ci.stderr || ci.stdout);
+ report.push('```');
+ // If ci fails, we probably can't run other scripts, but we try.
+ } else {
+ report.push('## ✅ Dependencies Installed');
+ }
+
+ // 2. Format
+ console.log('Running npm run format...');
+ run('npm', ['run', 'format']); // This modifies files
+
+ const status = run('git', ['status', '--porcelain']).stdout;
+ const formattedFiles = status.split('\n').filter(l => l.trim()).map(l => l.slice(3));
+
+ if (formattedFiles.length > 0) {
+ report.push('## ⚠️ Formatting Changes');
+ report.push('The following files were reformatted:');
+ report.push(formattedFiles.map(f => `- ${f}`).join('\n'));
+
+ if (APPLY_FIXES) {
+ console.log('Applying formatting fixes...');
+ run('git', ['add', '.']);
+ run('git', ['commit', '-m', 'fix(ai): automated formatting (agent)']);
+ report.push('\n**Changes have been automatically committed.**');
+ } else {
+ report.push('\n**Run `npm run format` to fix these issues.**');
+ }
+ } else {
+ report.push('## ✅ Formatting Passed');
+ }
+
+ // 3. Lint
+ console.log('Running npm run lint...');
+ const lint = run('npm', ['run', 'lint']);
+ if (lint.status !== 0) {
+ report.push('## ⚠️ Lint Warnings/Errors');
+ report.push('Show Lint Output
');
+ report.push('');
+ report.push('```');
+ report.push(lint.stdout + '\n' + lint.stderr);
+ report.push('```');
+ report.push(' ');
+ } else {
+ report.push('## ✅ Lint Passed');
+ }
+
+ // 4. Unit Tests
+ console.log('Running npm run test:unit...');
+ const test = run('npm', ['run', 'test:unit']);
+ if (test.status !== 0) {
+ report.push('## ❌ Unit Tests Failed');
+ report.push('Show Test Output
');
+ report.push('');
+ report.push('```');
+ report.push(test.stdout + '\n' + test.stderr);
+ report.push('```');
+ report.push(' ');
+ } else {
+ report.push('## ✅ Unit Tests Passed');
+ }
+
+ // 5. Guardrails
+ const diffFiles = getDiffFiles();
+ const sensitivePatterns = [
+ /^js\/nostr\//,
+ /^js\/services\/storageService/,
+ /^js\/services\/r2Service/,
+ /crypto/i
+ ];
+
+ const sensitiveChanges = diffFiles.filter(f => sensitivePatterns.some(p => p.test(f)));
+
+ if (sensitiveChanges.length > 0) {
+ report.push('## 🛡️ Security & Protocol Review Required');
+ report.push('The following sensitive files were modified:');
+ report.push(sensitiveChanges.map(f => `- ${f}`).join('\n'));
+ report.push('\n**Flags:** `requires-security-review`, `requires-protocol-review`');
+ }
+
+ fs.writeFileSync(REPORT_FILE, report.join('\n'));
+ console.log(`Report generated at ${REPORT_FILE}`);
+}
+
+main();