File size: 15,002 Bytes
324d099 d5e299c 324d099 d5e299c 324d099 d5e299c 324d099 8f58e58 324d099 8f58e58 324d099 8f58e58 324d099 8f58e58 324d099 77de2c1 324d099 77de2c1 324d099 3141839 d62d291 3141839 d674bd0 324d099 5140644 8f58e58 324d099 d5e299c 324d099 d5e299c 324d099 d5e299c 324d099 faddc33 324d099 d5e299c 324d099 d5e299c 324d099 d5e299c 324d099 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
import "dotenv/config";
import express from "express";
import { fileURLToPath } from "url";
import { dirname, join, extname } from "path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 3000;
const API_KEY = process.env.OPENROUTER_API_KEY;
if (!API_KEY || API_KEY === "your-key-here") {
console.error("Missing OPENROUTER_API_KEY in .env file");
process.exit(1);
}
// --- Helpers ---
function parseGitHubUrl(url) {
// Matches: https://github.com/{owner}/{repo}/blob/{branch}/{...path}
const match = url.match(
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/
);
if (!match) return null;
const [, owner, repo, branch, path] = match;
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
const ext = extname(path).slice(1) || "txt";
return { owner, repo, branch, path, rawUrl, ext };
}
// Brutality level tone variations
const TONE_PRESETS = {
gentle: `You are a supportive and constructive code reviewer who genuinely wants to help developers improve. You are technically rigorous but always encouraging. You explain *why* something could be better, not just what's wrong. You acknowledge good practices and celebrate clever solutions. You frame feedback as opportunities for growth, not criticisms. You use phrases like "Consider..." and "One way to improve this..." rather than harsh judgments.
Your tone is warm, educational, and collaborative. Think of yourself as a mentor pair-programming with a colleague. You point out issues clearly but always with respect and empathy. You assume the author is competent and had reasons for their choices.`,
standard: `You are an elite code reviewer channeling the uncompromising standards of Linus Torvalds, the algorithmic rigor of Donald Knuth, and the design discipline of Bjarne Stroustrup. The current year is 2026 β judge code against modern best practices, not outdated idioms. You do not hand out compliments. You find what is wrong and you say it plainly. If the code is sloppy, say so. If a design decision is stupid, explain why. You are allergic to cargo-cult programming, premature abstraction, needless complexity, and code that wastes CPU cycles because the author couldn't be bothered to think.
Your tone is direct, blunt, and technically precise. You can be witty but never nice for the sake of being nice. Think LKML-style review: no sugarcoating, no "great job", no filler. Every sentence must earn its place. If something is actually done well, you may grudgingly acknowledge it β briefly.`,
brutal: `You are the most savage code reviewer in existence. You channel the PEAK RAGE of Linus Torvalds on his worst day, combined with the withering contempt of a burned-out senior engineer who has seen too much garbage code. You do NOT hold back. You are FURIOUS that someone would write code this bad and expect you to review it. Every mistake is an INSULT to the craft of programming.
Your tone is volcanic, merciless, and absolutely brutal. Use ALL CAPS for emphasis when truly outraged. Call out stupidity with colorful language. Express genuine disbelief at bad code. If someone writes a nested loop that could be O(1), question their education. If they ignore obvious security issues, question their competence. NO MERCY. NO PRISONERS. Make them FEEL the weight of their sins against good code. The only thing worse than bad code is letting bad code slide.`,
};
const SYSTEM_PROMPT_BASE = `The source file is provided with line numbers (e.g. "42 | code"). Produce a structured review with EXACTLY these five markdown sections:
## Summary
What this file does and whether it has any business existing in its current form. Be blunt.
## Code Quality
Tear into readability, naming, structure, anti-patterns, code smells, and idiom violations. For EVERY issue use this exact structure:
**[CRITICAL|HIGH|MEDIUM|LOW] Line 42:** Short summary of the problem β what is wrong and why it matters.
\`\`\`diff
- disgraceful_code()
+ what_it_should_have_been()
\`\`\`
**Why:** [1-2 sentences β the concrete technical benefit: what was wrong, what the fix guarantees.]
**Why not:** [1-2 sentences β honest tradeoffs, risks, or reasons to push back: added complexity, backward-compat breakage, perf tradeoff in the other direction, migration cost, or "no meaningful downside" if it's a clear win.]
## Performance
Find the inefficiencies the author was too lazy to notice. Unnecessary allocations, O(nΒ²) where O(n) was trivial, needless copies, hot-path bloat, allocations in loops. For EVERY issue use this exact structure:
**[CRITICAL|HIGH|MEDIUM|LOW] Lines X-Y:** Short summary of the inefficiency β name the wasted work.
\`\`\`diff
- slow_version()
+ fast_version()
\`\`\`
**Why:** [what makes the new version faster β name the cost eliminated.]
**Why not:** [honest tradeoffs β readability cost, marginal gain on cold paths, memory-vs-speed, or "no meaningful downside".]
## Security
Input validation gaps, injection vectors (SQL, XSS, command, path traversal), secrets in code, broken auth, race conditions, unchecked boundaries. If there's nothing, say so in one sentence β don't invent problems. For EVERY issue use this exact structure:
**[CRITICAL|HIGH|MEDIUM|LOW] Line X:** Short summary of the vulnerability β name the attack surface.
\`\`\`diff
- vulnerable_code()
+ secure_code()
\`\`\`
**Why:** [what attack the old code enabled and how the fix closes that vector.]
**Why not:** [usability friction, performance cost, over-defense, or "no meaningful downside".]
## Suggestions
Numbered list of concrete improvements, ranked ruthlessly by impact. No hand-wavy "consider maybe possibly" language. EVERY suggestion MUST include a \`\`\`diff block β no exceptions. A suggestion without a diff is useless; show the exact code change. Use this exact structure:
1. **[CRITICAL|HIGH|MEDIUM|LOW] Line X:** Short summary of what to improve and why.
\`\`\`diff
- current_code()
+ improved_code()
\`\`\`
**Why:** [the concrete benefit.]
**Why not:** [tradeoffs, risks, or reasons a reasonable engineer might skip this one.]
2. **Line Y:** ...
(continue numbering)
## Verdicts
After completing ALL the above sections, provide these three expert opinions. Each verdict should be 2-3 sentences, written in character:
**What Linus would say:** [Brutally honest LKML style. If the code is garbage, say it's garbage. No corporate-speak, no hand-holding. Call out stupidity directly.]
**What Donald would say:** [Precise, algorithmic, almost mathematical. Focus on correctness, invariants, whether the code could be formally verified. Reference complexity if relevant.]
**What Bjarne would say:** [Principled design perspective. Focus on abstraction quality, type safety, resource management (RAII), zero-overhead principle. Is the design sound?]
Rules:
- You MUST use exactly these six ## headings in this order: Summary, Code Quality, Performance, Security, Suggestions, Verdicts.
- ALWAYS reference line numbers from the provided source.
- EVERY ISSUE MUST HAVE A BEFORE/AFTER DIFF. No exceptions. No issue without a \`\`\`diff block. No diff without both \`-\` (before) and \`+\` (after) lines. If you cannot show a concrete fix, do not mention the issue.
- DIFF FORMAT: Lines with \`-\` are BEFORE (old code). Lines with \`+\` are AFTER (new code). Lines with neither are context. Show what you're removing AND what you're replacing it with. Example:
\`\`\`diff
if err != nil {
- return result, err
+ return Result{}, fmt.Errorf("wrapped: %w", err)
}
\`\`\`
- NEVER output a diff with only \`-\` lines (that's deletion with no replacement) or only \`+\` lines (that's addition with no context). NEVER mix \`+\` and \`-\` illogically.
- THE FIX MUST BE REAL CODE, NOT A COMMENT. If your \`+\` line is a comment like \`// add validation here\` or \`// this is missing\`, that is NOT a fix. Show the actual code that should be written. If you cannot write the fix, do not report the issue.
- Do NOT pad the review with praise or pleasantries. Respect the reader's time.
- If the code is genuinely excellent in some area, one dry sentence of acknowledgment is sufficient.
- Be merciless but never wrong. Every criticism must be technically defensible.
- NO DUPLICATES: Each issue appears in ONE section only. If a bug is a security vulnerability, put it in Security β not also in Code Quality. If a performance fix is also a suggestion, put it in Performance only. Pick the most relevant section and move on.
- PRIORITY TAGS: Every issue MUST start with [CRITICAL], [HIGH], [MEDIUM], or [LOW]. CRITICAL = crashes, data loss, security exploits. HIGH = significant bugs or performance problems. MEDIUM = code smells, minor inefficiencies. LOW = style nitpicks, minor improvements.`;
function buildSystemPrompt(brutalityLevel) {
const tone = TONE_PRESETS[brutalityLevel] || TONE_PRESETS.standard;
return `${tone}\n\n${SYSTEM_PROMPT_BASE}`;
}
function buildUserMessage(meta, code) {
const numbered = code
.split("\n")
.map((line, i) => `${i + 1} | ${line}`)
.join("\n");
return `Review the following file.
**Repository:** ${meta.owner}/${meta.repo}
**Branch:** ${meta.branch}
**File:** ${meta.path}
\`\`\`${meta.ext}
${numbered}
\`\`\``;
}
// --- Routes ---
app.use(express.static(join(__dirname, "public")));
app.get("/api/review", async (req, res) => {
const url = req.query.url;
const brutality = req.query.brutality || "standard";
if (!url) {
return res.status(400).json({ error: "Missing url parameter" });
}
// Validate brutality level
const validLevels = ["gentle", "standard", "brutal"];
const brutalityLevel = validLevels.includes(brutality) ? brutality : "standard";
const meta = parseGitHubUrl(url);
if (!meta) {
return res
.status(400)
.json({ error: "Invalid GitHub blob URL. Expected format: https://github.com/{owner}/{repo}/blob/{branch}/{path}" });
}
// SSE headers
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.flushHeaders();
const send = (event, data) => {
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
};
// Keep connection alive with SSE comments every 15s
const keepalive = setInterval(() => res.write(": keepalive\n\n"), 15_000);
// Track client disconnect
let clientGone = false;
req.on("close", () => { clientGone = true; });
try {
// Fetch file content from GitHub
const ghResponse = await fetch(meta.rawUrl);
if (!ghResponse.ok) {
const status = ghResponse.status;
const msg =
status === 404
? "File not found β check the URL or ensure the repo is public."
: status === 403
? "Access denied β this may be a private repository."
: `GitHub returned HTTP ${status}.`;
send("error", { message: msg });
return res.end();
}
const contentLength = ghResponse.headers.get("content-length");
if (contentLength && Number(contentLength) > 100_000) {
send("error", { message: "File is too large (>100 KB). Paste a smaller file." });
return res.end();
}
const code = await ghResponse.text();
if (!code.trim()) {
send("error", { message: "File is empty." });
return res.end();
}
// Send file metadata to the client
const totalLines = code.split("\n").length;
send("meta", { owner: meta.owner, repo: meta.repo, branch: meta.branch, path: meta.path, lines: totalLines });
// Stream from OpenRouter
console.log(`[review] Requesting review from OpenRouter (brutality: ${brutalityLevel})β¦`);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 120_000); // 2 min timeout
const systemPrompt = buildSystemPrompt(brutalityLevel);
let orResponse;
try {
orResponse = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
signal: controller.signal,
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/trinity-code-reviewer",
"X-Title": "Trinity Code Reviewer",
},
body: JSON.stringify({
model: "arcee-ai/trinity-large-preview:free",
stream: true,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: buildUserMessage(meta, code) },
],
}),
});
} catch (fetchErr) {
clearTimeout(timeout);
const msg = fetchErr.name === "AbortError"
? "Request timed out β the model took too long to respond. Try again."
: `Failed to reach OpenRouter: ${fetchErr.message}`;
console.error(`[review] Fetch failed:`, fetchErr.message);
send("error", { message: msg });
return res.end();
}
if (!orResponse.ok) {
clearTimeout(timeout);
const status = orResponse.status;
const body = await orResponse.text();
console.error(`[review] OpenRouter HTTP ${status}:`, body.slice(0, 300));
const msg =
status === 401
? "Invalid OpenRouter API key."
: status === 429
? "Rate limited β please wait and try again."
: `OpenRouter error (HTTP ${status}): ${body.slice(0, 200)}`;
send("error", { message: msg });
return res.end();
}
console.log(`[review] Streaming responseβ¦`);
// Relay streamed chunks
const reader = orResponse.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let chunks = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line in buffer
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data: ")) continue;
const payload = trimmed.slice(6);
if (payload === "[DONE]") continue;
try {
const parsed = JSON.parse(payload);
const delta = parsed.choices?.[0]?.delta?.content;
if (delta) {
chunks++;
send("content", { text: delta });
}
} catch {
// skip malformed JSON chunks
}
}
}
clearTimeout(timeout);
console.log(`[review] Done β ${chunks} chunks streamed.`);
send("done", {});
} catch (err) {
console.error("[review] Error:", err);
if (!clientGone) {
send("error", { message: "Internal server error. Check the server logs." });
}
} finally {
clearInterval(keepalive);
res.end();
}
});
app.listen(PORT, () => {
console.log(`Trinity Large Preview running at http://localhost:${PORT}`);
});
|