@contextrail/code-review-agent 0.1.1 → 0.1.2-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +48 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/parser.d.ts +3 -0
- package/dist/cli/parser.js +144 -0
- package/dist/cli/types.d.ts +17 -0
- package/dist/cli/types.js +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +16 -0
- package/dist/errors/error-utils.d.ts +2 -0
- package/dist/errors/error-utils.js +37 -0
- package/dist/index.js +40 -578
- package/dist/lifecycle.d.ts +4 -0
- package/dist/lifecycle.js +52 -0
- package/dist/orchestrator/agentic-orchestrator.d.ts +2 -0
- package/dist/orchestrator/agentic-orchestrator.js +9 -13
- package/dist/orchestrator/writer.js +1 -1
- package/dist/output/aggregator.d.ts +2 -1
- package/dist/output/aggregator.js +3 -12
- package/dist/output/schema.d.ts +86 -86
- package/dist/output/summary-logger.d.ts +3 -0
- package/dist/output/summary-logger.js +81 -0
- package/dist/pipeline.d.ts +25 -0
- package/dist/pipeline.js +276 -0
- package/dist/prompts/blocks.d.ts +25 -0
- package/dist/prompts/blocks.js +129 -0
- package/dist/prompts/decision.d.ts +15 -0
- package/dist/prompts/decision.js +30 -0
- package/dist/prompts/index.d.ts +5 -0
- package/dist/prompts/index.js +5 -0
- package/dist/{orchestrator/prompts.d.ts → prompts/orchestrator.d.ts} +2 -3
- package/dist/{orchestrator/prompts.js → prompts/orchestrator.js} +2 -3
- package/dist/prompts/reviewer.d.ts +12 -0
- package/dist/prompts/reviewer.js +107 -0
- package/dist/{output/prompts.d.ts → prompts/synthesis.d.ts} +1 -16
- package/dist/{output/prompts.js → prompts/synthesis.js} +0 -30
- package/dist/review-inputs/filtering.d.ts +14 -0
- package/dist/review-inputs/filtering.js +97 -8
- package/dist/review-inputs/index.js +59 -13
- package/dist/reviewers/executor.d.ts +2 -0
- package/dist/reviewers/executor.js +1 -8
- package/dist/reviewers/prompt.d.ts +2 -28
- package/dist/reviewers/prompt.js +5 -235
- package/package.json +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createLogger, parseLogLevel } from './logging/logger.js';
|
|
2
|
+
import { serializeError, toError } from './errors/error-utils.js';
|
|
3
|
+
let mcpClientInstance = null;
|
|
4
|
+
let isShuttingDown = false;
|
|
5
|
+
export const setMcpClient = (client) => {
|
|
6
|
+
mcpClientInstance = client;
|
|
7
|
+
};
|
|
8
|
+
export const gracefulShutdown = async (signal, error) => {
|
|
9
|
+
const log = createLogger(parseLogLevel(process.env.DEBUG));
|
|
10
|
+
if (isShuttingDown) {
|
|
11
|
+
log.warn({ msg: 'Shutdown already in progress', signal });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
isShuttingDown = true;
|
|
15
|
+
log.warn({ msg: `Received ${signal}. Shutting down...`, signal });
|
|
16
|
+
try {
|
|
17
|
+
if (mcpClientInstance) {
|
|
18
|
+
await mcpClientInstance.close();
|
|
19
|
+
}
|
|
20
|
+
process.exit(error ? 1 : 0);
|
|
21
|
+
}
|
|
22
|
+
catch (shutdownError) {
|
|
23
|
+
const error = toError(shutdownError);
|
|
24
|
+
log.error({ msg: 'Error during shutdown', error: serializeError(error) });
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export const setupLifecycleHandlers = () => {
|
|
29
|
+
process.on('uncaughtException', async (error) => {
|
|
30
|
+
const log = createLogger(parseLogLevel(process.env.DEBUG));
|
|
31
|
+
log.error({
|
|
32
|
+
msg: 'Uncaught exception',
|
|
33
|
+
error: serializeError(error),
|
|
34
|
+
});
|
|
35
|
+
await gracefulShutdown('SIGTERM', error);
|
|
36
|
+
});
|
|
37
|
+
process.on('unhandledRejection', async (reason) => {
|
|
38
|
+
const log = createLogger(parseLogLevel(process.env.DEBUG));
|
|
39
|
+
const error = toError(reason);
|
|
40
|
+
log.error({
|
|
41
|
+
msg: 'Unhandled rejection',
|
|
42
|
+
error: serializeError(error),
|
|
43
|
+
});
|
|
44
|
+
await gracefulShutdown('SIGTERM', error);
|
|
45
|
+
});
|
|
46
|
+
process.on('SIGTERM', async () => {
|
|
47
|
+
await gracefulShutdown('SIGTERM');
|
|
48
|
+
});
|
|
49
|
+
process.on('SIGINT', async () => {
|
|
50
|
+
await gracefulShutdown('SIGINT');
|
|
51
|
+
});
|
|
52
|
+
};
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { ReviewInputs } from '../review-inputs/index.js';
|
|
3
3
|
import type { McpClient } from '../mcp/client.js';
|
|
4
4
|
import type { Logger } from '../logging/logger.js';
|
|
5
|
+
import type { LlmService } from '../llm/service.js';
|
|
5
6
|
declare const orchestratorOutputSchema: z.ZodObject<{
|
|
6
7
|
understanding: z.ZodString;
|
|
7
8
|
reviewers: z.ZodArray<z.ZodString, "many">;
|
|
@@ -22,6 +23,7 @@ export type AgenticOrchestratorConfig = {
|
|
|
22
23
|
};
|
|
23
24
|
export type AgenticOrchestratorDeps = {
|
|
24
25
|
mcpClient: McpClient;
|
|
26
|
+
llmService: LlmService;
|
|
25
27
|
config: AgenticOrchestratorConfig;
|
|
26
28
|
logger?: Logger;
|
|
27
29
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { DEFAULT_MAX_STEPS } from '../config/defaults.js';
|
|
3
|
-
import { generateUserMessagePrompt } from '
|
|
3
|
+
import { generateUserMessagePrompt } from '../prompts/index.js';
|
|
4
4
|
import { writeOrchestratorOutputs } from './writer.js';
|
|
5
5
|
import { filterValidReviewers, formatReviewerSelectionError } from './validation.js';
|
|
6
6
|
import { extractFilePatterns } from '../reviewers/executor.js';
|
|
7
7
|
import { fileMatchesPatterns } from '../review-inputs/file-patterns.js';
|
|
8
|
-
import { createLlmService } from '../llm/factory.js';
|
|
9
8
|
const extractSystemPrompt = (text) => {
|
|
10
9
|
const start = text.indexOf('<system_prompt>');
|
|
11
10
|
const end = text.indexOf('</system_prompt>');
|
|
@@ -20,7 +19,7 @@ const orchestratorOutputSchema = z.object({
|
|
|
20
19
|
reviewers: z.array(z.string()).min(1).describe('Selected reviewer names from available list'),
|
|
21
20
|
});
|
|
22
21
|
export const runOrchestrator = async (inputs, outputDir, deps) => {
|
|
23
|
-
const { mcpClient, config, logger } = deps;
|
|
22
|
+
const { mcpClient, llmService, config, logger } = deps;
|
|
24
23
|
// Fetch review-orchestration prompt from MCP
|
|
25
24
|
const promptResult = await mcpClient.getPrompt({ name: 'review-orchestration' });
|
|
26
25
|
const promptMessages = [];
|
|
@@ -34,10 +33,13 @@ export const runOrchestrator = async (inputs, outputDir, deps) => {
|
|
|
34
33
|
const role = message.role === 'user' ? 'user' : message.role === 'assistant' ? 'assistant' : 'system';
|
|
35
34
|
promptMessages.push({ role, content: raw });
|
|
36
35
|
}
|
|
37
|
-
// Extract reviewer catalog from prompt
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// Extract reviewer catalog from prompt metadata
|
|
37
|
+
const promptMeta = promptResult.metadata;
|
|
38
|
+
if (!Array.isArray(promptMeta?.availableReviewers) || promptMeta.availableReviewers.length === 0) {
|
|
39
|
+
throw new Error('[ORCH] Orchestration prompt is missing metadata.availableReviewers or it is empty. ' +
|
|
40
|
+
'Please update review-orchestration.yaml to include a metadata.availableReviewers array.');
|
|
41
|
+
}
|
|
42
|
+
const availableReviewers = promptMeta.availableReviewers;
|
|
41
43
|
// Pre-fetch reviewer file patterns before orchestrator runs
|
|
42
44
|
logger?.debug(`Pre-fetching file patterns for ${availableReviewers.length} reviewers...`);
|
|
43
45
|
const reviewerFilePatterns = new Map();
|
|
@@ -58,12 +60,6 @@ export const runOrchestrator = async (inputs, outputDir, deps) => {
|
|
|
58
60
|
logger?.debug(`Pre-fetched file patterns for ${reviewerFilePatterns.size} reviewers`);
|
|
59
61
|
// Build user message with change summary and reviewer file patterns
|
|
60
62
|
const userMessage = generateUserMessagePrompt(inputs, availableReviewers, config.prDescription, reviewerFilePatterns, config.reviewDomains);
|
|
61
|
-
// Create LLM service with MCP tools
|
|
62
|
-
const { service: llmService } = createLlmService({
|
|
63
|
-
openRouterApiKey: config.openRouterApiKey,
|
|
64
|
-
mcpClient,
|
|
65
|
-
logger,
|
|
66
|
-
});
|
|
67
63
|
// Prepare messages
|
|
68
64
|
const messages = [
|
|
69
65
|
...promptMessages,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { generateActivityContentPrompt } from '
|
|
3
|
+
import { generateActivityContentPrompt } from '../prompts/index.js';
|
|
4
4
|
export const writeOrchestratorOutputs = async (outputDir, understanding, reviewers, toolCalls, contextIds) => {
|
|
5
5
|
const orchestratorDir = path.join(outputDir, 'orchestrator');
|
|
6
6
|
await mkdir(orchestratorDir, { recursive: true });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReviewerResult, ReviewDecision, AggregationTokenUsage, SynthesisResult } from './schema.js';
|
|
2
|
+
import type { LlmService } from '../llm/service.js';
|
|
2
3
|
import type { Logger } from '../logging/logger.js';
|
|
3
4
|
export type AggregationConfig = {
|
|
4
|
-
|
|
5
|
+
llmService: LlmService;
|
|
5
6
|
model: string;
|
|
6
7
|
maxSteps?: number;
|
|
7
8
|
logger?: Logger;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { reviewDecisionSchema, synthesisResultSchema, cleanFinding } from './schema.js';
|
|
2
|
-
import { buildReviewDecisionPrompt, buildSynthesisPrompt } from '
|
|
3
|
-
import { createLlmService } from '../llm/factory.js';
|
|
2
|
+
import { buildReviewDecisionPrompt, buildSynthesisPrompt } from '../prompts/index.js';
|
|
4
3
|
const DEFAULT_AGGREGATION_STEPS = 5;
|
|
5
4
|
const MIN_AGGREGATION_STEPS = 1;
|
|
6
5
|
const MAX_AGGREGATION_STEPS = 20;
|
|
@@ -20,11 +19,7 @@ export const validateAggregationMaxSteps = (value) => {
|
|
|
20
19
|
* @returns Synthesis result with deduplicated findings, contradictions, and compound risks
|
|
21
20
|
*/
|
|
22
21
|
export const synthesizeFindings = async (reviewerResults, config) => {
|
|
23
|
-
const
|
|
24
|
-
openRouterApiKey: config.openRouterApiKey,
|
|
25
|
-
logger: config.logger,
|
|
26
|
-
enableMetrics: true,
|
|
27
|
-
});
|
|
22
|
+
const llmService = config.llmService;
|
|
28
23
|
const maxSteps = validateAggregationMaxSteps(config.maxSteps);
|
|
29
24
|
const totalInputFindings = reviewerResults.reduce((sum, rr) => sum + rr.findings.length, 0);
|
|
30
25
|
config.logger?.debug(`Synthesis evidence input: reviewers=${reviewerResults.length}, totalFindings=${totalInputFindings}, byReviewer=${JSON.stringify(reviewerResults.map((rr) => ({
|
|
@@ -75,11 +70,7 @@ export const synthesizeFindings = async (reviewerResults, config) => {
|
|
|
75
70
|
return { synthesis: cleanedSynthesis, usage };
|
|
76
71
|
};
|
|
77
72
|
export const generateReviewDecision = async (understanding, synthesisResult, config) => {
|
|
78
|
-
const
|
|
79
|
-
openRouterApiKey: config.openRouterApiKey,
|
|
80
|
-
logger: config.logger,
|
|
81
|
-
enableMetrics: true,
|
|
82
|
-
});
|
|
73
|
+
const llmService = config.llmService;
|
|
83
74
|
const maxSteps = validateAggregationMaxSteps(config.maxSteps);
|
|
84
75
|
const blockingFindings = synthesisResult.findings.filter((finding) => finding.severity === 'critical' || finding.severity === 'major');
|
|
85
76
|
config.logger?.debug(`Decision evidence input: findings=${synthesisResult.findings.length}, blockingFindings=${blockingFindings.length}, contradictions=${synthesisResult.contradictions.length}, compoundRisks=${synthesisResult.compoundRisks.length}`);
|