initial commit
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ic08e7c4b5b4f4072de9e2f9a701e977b6a6a6964
This commit is contained in:
commit
f8db097ba9
21 changed files with 4924 additions and 0 deletions
167
src/index.ts
Normal file
167
src/index.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { loadConfig } from './config.js';
|
||||
import { initLogger, getLogger } from './logger.js';
|
||||
import {
|
||||
initGitHub,
|
||||
fetchPR,
|
||||
hasExistingComment,
|
||||
postComment,
|
||||
updateComment,
|
||||
formatComment,
|
||||
} from './github.js';
|
||||
import { createApp } from './server.js';
|
||||
import { createEngine } from './engine/index.js';
|
||||
import type { WebhookEvent } from './types.js';
|
||||
|
||||
async function analyzeOne(target: string) {
|
||||
const match = target.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
||||
if (!match) {
|
||||
console.error('Usage: troutbot analyze <owner/repo#number>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [, owner, repo, numStr] = match;
|
||||
const prNumber = parseInt(numStr, 10);
|
||||
|
||||
const config = loadConfig();
|
||||
initLogger(config.logging);
|
||||
const logger = getLogger();
|
||||
|
||||
initGitHub(process.env.GITHUB_TOKEN);
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
logger.error('GITHUB_TOKEN is required for analyze mode');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prData = await fetchPR(owner, repo, prNumber);
|
||||
if (!prData) {
|
||||
logger.error(`Could not fetch PR ${owner}/${repo}#${prNumber}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const event: WebhookEvent = {
|
||||
action: 'analyze',
|
||||
type: 'pull_request',
|
||||
number: prNumber,
|
||||
title: prData.title,
|
||||
body: prData.body,
|
||||
owner,
|
||||
repo,
|
||||
author: prData.author,
|
||||
labels: prData.labels,
|
||||
branch: prData.branch,
|
||||
sha: prData.sha,
|
||||
};
|
||||
|
||||
const engine = createEngine(config.engine);
|
||||
const analysis = await engine.analyze(event);
|
||||
logger.info(
|
||||
`Analyzed ${owner}/${repo}#${prNumber}: impact=${analysis.impact}, confidence=${analysis.confidence.toFixed(2)}`
|
||||
);
|
||||
logger.info(`Reasoning: ${analysis.reasoning}`);
|
||||
|
||||
const { commentMarker, allowUpdates } = config.response;
|
||||
const existing = await hasExistingComment(owner, repo, prNumber, commentMarker);
|
||||
|
||||
if (existing.exists && !allowUpdates) {
|
||||
logger.info(`Already commented on ${owner}/${repo}#${prNumber}, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = formatComment(
|
||||
config.response,
|
||||
event.type,
|
||||
analysis.impact,
|
||||
analysis.confidence,
|
||||
analysis.reasoning
|
||||
);
|
||||
|
||||
if (existing.exists && allowUpdates && existing.commentId) {
|
||||
await updateComment(owner, repo, existing.commentId, body);
|
||||
} else {
|
||||
await postComment(owner, repo, prNumber, body);
|
||||
}
|
||||
}
|
||||
|
||||
function serve() {
|
||||
const config = loadConfig();
|
||||
initLogger(config.logging);
|
||||
const logger = getLogger();
|
||||
|
||||
initGitHub(process.env.GITHUB_TOKEN);
|
||||
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
logger.warn(
|
||||
'No GITHUB_TOKEN - running in dry-run mode (checks and diff backends will be inactive)'
|
||||
);
|
||||
}
|
||||
if (!process.env.WEBHOOK_SECRET) {
|
||||
logger.warn('No WEBHOOK_SECRET - webhook signature verification is disabled');
|
||||
}
|
||||
|
||||
const app = createApp(config);
|
||||
const port = config.server.port;
|
||||
|
||||
const enabledBackends = Object.entries(config.engine.backends)
|
||||
.filter(([, v]) => v.enabled)
|
||||
.map(([k]) => k);
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
logger.info(`Troutbot listening on port ${port}`);
|
||||
logger.info(`Enabled backends: ${enabledBackends.join(', ')}`);
|
||||
|
||||
// Watched repos
|
||||
if (config.repositories.length > 0) {
|
||||
const repos = config.repositories.map((r) => `${r.owner}/${r.repo}`).join(', ');
|
||||
logger.info(`Watched repos: ${repos}`);
|
||||
} else {
|
||||
logger.info('Watched repos: all (no repository filter)');
|
||||
}
|
||||
|
||||
// Active filters (only log non-empty ones)
|
||||
const { filters } = config;
|
||||
if (filters.labels.include.length > 0)
|
||||
logger.info(`Label include filter: ${filters.labels.include.join(', ')}`);
|
||||
if (filters.labels.exclude.length > 0)
|
||||
logger.info(`Label exclude filter: ${filters.labels.exclude.join(', ')}`);
|
||||
if (filters.authors.exclude.length > 0)
|
||||
logger.info(`Excluded authors: ${filters.authors.exclude.join(', ')}`);
|
||||
if (filters.branches.include.length > 0)
|
||||
logger.info(`Branch filter: ${filters.branches.include.join(', ')}`);
|
||||
|
||||
// Engine weights and confidence threshold
|
||||
const { weights, confidenceThreshold } = config.engine;
|
||||
logger.info(
|
||||
`Engine weights: checks=${weights.checks}, diff=${weights.diff}, quality=${weights.quality} | threshold=${confidenceThreshold}`
|
||||
);
|
||||
|
||||
// Comment update mode
|
||||
logger.info(`Comment updates: ${config.response.allowUpdates ? 'enabled' : 'disabled'}`);
|
||||
|
||||
logger.info(`Dashboard available at http://localhost:${port}/dashboard`);
|
||||
});
|
||||
|
||||
function shutdown(signal: string) {
|
||||
logger.info(`Received ${signal}, shutting down gracefully...`);
|
||||
server.close(() => {
|
||||
logger.info('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
setTimeout(() => {
|
||||
logger.warn('Graceful shutdown timed out, forcing exit');
|
||||
process.exit(1);
|
||||
}, 10_000).unref();
|
||||
}
|
||||
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args[0] === 'analyze' && args[1]) {
|
||||
analyzeOne(args[1]).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
serve();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue