@contextrail/code-review-agent 0.1.1 → 0.1.2-alpha.1

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.
@@ -0,0 +1,2 @@
1
+ import type { createLogger } from '../logging/logger.js';
2
+ export declare const printHelp: (log: ReturnType<typeof createLogger>) => void;
@@ -0,0 +1,48 @@
1
+ export const printHelp = (log) => {
2
+ log.info(`code-review-agent
3
+
4
+ Usage:
5
+ code-review-agent review --repo <path> --from <sha> --to <sha> [--output <dir>]
6
+ code-review-agent review --repo <path> --files <file1,file2> [--output <dir>]
7
+ code-review-agent review --repo <path> --file <file> [--file <file> ...] [--output <dir>]
8
+
9
+ Commands:
10
+ review Run code review on git diff
11
+
12
+ Options:
13
+ --repo <path> Repository path (default: current directory)
14
+ --from <sha> Base commit SHA (required)
15
+ --to <sha> Head commit SHA (required)
16
+ --files <list> Comma-separated list of files to review
17
+ --file <path> File to review (repeatable)
18
+ --output <dir> Output directory (default: ./review)
19
+ --log-level <lv> Log level: debug|info|warn|error|silent (overrides DEBUG)
20
+ --max-steps <n> Maximum LLM steps per call (default: 10)
21
+ --max-iterations <n> Maximum reviewer validation iterations (default: 2)
22
+ --aggregation-max-steps <n> Maximum steps for aggregation (default: 5)
23
+ --max-tokens-per-file <n> Token budget for surrounding context per file (default: 20000)
24
+ --context-lines <n> Lines of context around changes (default: 10)
25
+ --pr-description <text> PR description to include as context (optional)
26
+ --domains <list> Comma-separated review focus domains (optional)
27
+ -h, --help Show this help message
28
+
29
+ Environment Variables:
30
+ CONTEXTRAIL_MCP_SERVER_URL ContextRail MCP server URL (required)
31
+ CONTEXTRAIL_MCP_JWT_TOKEN ContextRail MCP authentication token (required)
32
+ OPENROUTER_API_KEY OpenRouter API key (required)
33
+ LLM_MODEL_ORCHESTRATOR Model for orchestrator (default: anthropic/claude-haiku-4.5)
34
+ LLM_MODEL_REVIEWER Model for reviewers (default: anthropic/claude-haiku-4.5)
35
+ MAX_STEPS Maximum LLM steps per call (default: 10)
36
+ MAX_ITERATIONS Maximum reviewer validation iterations (default: 2)
37
+ AGGREGATION_MAX_STEPS Maximum steps for aggregation (default: 5)
38
+ MAX_TOKENS_PER_FILE Token budget for surrounding context per file (default: 20000)
39
+ CONTEXT_LINES Lines of context around changes (default: 10)
40
+ REVIEW_DOMAINS Comma-separated review focus domains (optional)
41
+
42
+ Examples:
43
+ code-review-agent review --repo . --from HEAD^ --to HEAD
44
+ code-review-agent review --repo . --files src/a.ts,src/b.ts
45
+ code-review-agent review --repo . --file src/a.ts --file src/b.ts
46
+ code-review-agent review --repo /path/to/repo --from abc123 --to def456 --output ./review-results
47
+ `);
48
+ };
@@ -0,0 +1,3 @@
1
+ export type { CliArgs } from './types.js';
2
+ export { parseArgs, validateArgs } from './parser.js';
3
+ export { printHelp } from './help.js';
@@ -0,0 +1,2 @@
1
+ export { parseArgs, validateArgs } from './parser.js';
2
+ export { printHelp } from './help.js';
@@ -0,0 +1,3 @@
1
+ import type { CliArgs } from './types.js';
2
+ export declare const parseArgs: (args: string[]) => CliArgs;
3
+ export declare const validateArgs: (args: CliArgs) => void;
@@ -0,0 +1,144 @@
1
+ const ALLOWED_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error', 'silent']);
2
+ const isPositiveInteger = (value) => Number.isInteger(value) && value > 0;
3
+ const isNonNegativeInteger = (value) => Number.isInteger(value) && value >= 0;
4
+ export const parseArgs = (args) => {
5
+ const parsed = {};
6
+ let i = 0;
7
+ while (i < args.length) {
8
+ const arg = args[i];
9
+ if (!arg) {
10
+ i += 1;
11
+ continue;
12
+ }
13
+ if (arg === '-h' || arg === '--help') {
14
+ parsed.help = true;
15
+ i += 1;
16
+ }
17
+ else if (arg === '--repo' && i + 1 < args.length) {
18
+ parsed.repo = args[i + 1];
19
+ i += 2;
20
+ }
21
+ else if (arg === '--from' && i + 1 < args.length) {
22
+ parsed.from = args[i + 1];
23
+ i += 2;
24
+ }
25
+ else if (arg === '--to' && i + 1 < args.length) {
26
+ parsed.to = args[i + 1];
27
+ i += 2;
28
+ }
29
+ else if (arg === '--output' && i + 1 < args.length) {
30
+ parsed.output = args[i + 1];
31
+ i += 2;
32
+ }
33
+ else if (arg === '--files' && i + 1 < args.length) {
34
+ const raw = args[i + 1] ?? '';
35
+ const files = raw
36
+ .split(',')
37
+ .map((file) => file.trim())
38
+ .filter(Boolean);
39
+ if (!parsed.files) {
40
+ parsed.files = [];
41
+ }
42
+ parsed.files.push(...files);
43
+ i += 2;
44
+ }
45
+ else if (arg === '--file' && i + 1 < args.length) {
46
+ const file = args[i + 1];
47
+ if (file) {
48
+ if (!parsed.files) {
49
+ parsed.files = [];
50
+ }
51
+ parsed.files.push(file);
52
+ }
53
+ i += 2;
54
+ }
55
+ else if (arg === '--log-level' && i + 1 < args.length) {
56
+ parsed.logLevel = args[i + 1];
57
+ i += 2;
58
+ }
59
+ else if (arg === '--pr-description' && i + 1 < args.length) {
60
+ parsed.prDescription = args[i + 1];
61
+ i += 2;
62
+ }
63
+ else if (arg === '--domains' && i + 1 < args.length) {
64
+ const raw = args[i + 1] ?? '';
65
+ const domains = raw
66
+ .split(',')
67
+ .map((domain) => domain.trim())
68
+ .filter(Boolean);
69
+ if (!parsed.domains) {
70
+ parsed.domains = [];
71
+ }
72
+ parsed.domains.push(...domains);
73
+ i += 2;
74
+ }
75
+ else if (arg === '--max-steps' && i + 1 < args.length) {
76
+ const value = parseInt(args[i + 1] ?? '10', 10);
77
+ parsed.maxSteps = value;
78
+ i += 2;
79
+ }
80
+ else if (arg === '--max-iterations' && i + 1 < args.length) {
81
+ const value = parseInt(args[i + 1] ?? '2', 10);
82
+ parsed.maxIterations = value;
83
+ i += 2;
84
+ }
85
+ else if (arg === '--aggregation-max-steps' && i + 1 < args.length) {
86
+ const value = parseInt(args[i + 1] ?? '5', 10);
87
+ parsed.aggregationMaxSteps = value;
88
+ i += 2;
89
+ }
90
+ else if (arg === '--max-tokens-per-file' && i + 1 < args.length) {
91
+ const value = parseInt(args[i + 1] ?? '20000', 10);
92
+ parsed.maxTokensPerFile = value;
93
+ i += 2;
94
+ }
95
+ else if (arg === '--context-lines' && i + 1 < args.length) {
96
+ const value = parseInt(args[i + 1] ?? '10', 10);
97
+ parsed.contextLines = value;
98
+ i += 2;
99
+ }
100
+ else if (!parsed.command && !arg.startsWith('-')) {
101
+ parsed.command = arg;
102
+ i += 1;
103
+ }
104
+ else {
105
+ i += 1;
106
+ }
107
+ }
108
+ return parsed;
109
+ };
110
+ export const validateArgs = (args) => {
111
+ if (args.help) {
112
+ return;
113
+ }
114
+ if (args.command !== 'review') {
115
+ throw new Error(`Unknown command: ${args.command ?? 'none'}. Use 'review' or see --help.`);
116
+ }
117
+ if (args.logLevel !== undefined && !ALLOWED_LOG_LEVELS.has(args.logLevel)) {
118
+ throw new Error(`Invalid log level: ${args.logLevel}. Must be one of: ${Array.from(ALLOWED_LOG_LEVELS).join(', ')}`);
119
+ }
120
+ const hasFileList = Array.isArray(args.files) && args.files.length > 0;
121
+ if (!hasFileList) {
122
+ if (!args.from) {
123
+ throw new Error('Missing required argument: --from');
124
+ }
125
+ if (!args.to) {
126
+ throw new Error('Missing required argument: --to');
127
+ }
128
+ }
129
+ if (args.maxSteps !== undefined && !isPositiveInteger(args.maxSteps)) {
130
+ throw new Error(`Invalid max-steps: ${args.maxSteps}. Must be a positive integer.`);
131
+ }
132
+ if (args.maxIterations !== undefined && !isPositiveInteger(args.maxIterations)) {
133
+ throw new Error(`Invalid max-iterations: ${args.maxIterations}. Must be a positive integer.`);
134
+ }
135
+ if (args.aggregationMaxSteps !== undefined && !isPositiveInteger(args.aggregationMaxSteps)) {
136
+ throw new Error(`Invalid aggregation-max-steps: ${args.aggregationMaxSteps}. Must be a positive integer.`);
137
+ }
138
+ if (args.maxTokensPerFile !== undefined && !isPositiveInteger(args.maxTokensPerFile)) {
139
+ throw new Error(`Invalid max-tokens-per-file: ${args.maxTokensPerFile}. Must be a positive integer.`);
140
+ }
141
+ if (args.contextLines !== undefined && !isNonNegativeInteger(args.contextLines)) {
142
+ throw new Error(`Invalid context-lines: ${args.contextLines}. Must be a non-negative integer.`);
143
+ }
144
+ };
@@ -0,0 +1,17 @@
1
+ export interface CliArgs {
2
+ command?: string;
3
+ repo?: string;
4
+ from?: string;
5
+ to?: string;
6
+ output?: string;
7
+ logLevel?: string;
8
+ files?: string[];
9
+ help?: boolean;
10
+ maxSteps?: number;
11
+ maxIterations?: number;
12
+ aggregationMaxSteps?: number;
13
+ maxTokensPerFile?: number;
14
+ contextLines?: number;
15
+ prDescription?: string;
16
+ domains?: string[];
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { type LogLevel } from '../logging/logger.js';
2
+ import type { CliArgs } from '../cli/types.js';
2
3
  export type ReviewAgentConfig = {
3
4
  mcpServerUrl?: string;
4
5
  mcpAuthToken?: string;
@@ -31,4 +32,5 @@ export type ValidatedReviewAgentConfig = {
31
32
  reviewDomains?: string[];
32
33
  };
33
34
  export declare const loadConfig: () => ReviewAgentConfig;
35
+ export declare const applyCliArgs: (config: ReviewAgentConfig, args: CliArgs) => void;
34
36
  export declare const validateConfig: (config: ReviewAgentConfig) => ValidatedReviewAgentConfig;
@@ -63,6 +63,22 @@ export const loadConfig = () => {
63
63
  }
64
64
  return config;
65
65
  };
66
+ export const applyCliArgs = (config, args) => {
67
+ if (args.maxSteps !== undefined)
68
+ config.maxSteps = args.maxSteps;
69
+ if (args.maxIterations !== undefined)
70
+ config.maxIterations = args.maxIterations;
71
+ if (args.aggregationMaxSteps !== undefined)
72
+ config.aggregationMaxSteps = args.aggregationMaxSteps;
73
+ if (args.maxTokensPerFile !== undefined)
74
+ config.maxTokensPerFile = args.maxTokensPerFile;
75
+ if (args.contextLines !== undefined)
76
+ config.contextLines = args.contextLines;
77
+ if (args.prDescription !== undefined)
78
+ config.prDescription = args.prDescription;
79
+ if (args.domains && args.domains.length > 0)
80
+ config.reviewDomains = args.domains;
81
+ };
66
82
  export const validateConfig = (config) => {
67
83
  const mcpServerUrl = config.mcpServerUrl;
68
84
  const mcpAuthToken = config.mcpAuthToken;
@@ -0,0 +1,2 @@
1
+ export declare const toError: (error: unknown) => Error;
2
+ export declare const serializeError: (error: Error) => Record<string, unknown>;
@@ -0,0 +1,37 @@
1
+ export const toError = (error) => {
2
+ if (error instanceof Error) {
3
+ return error;
4
+ }
5
+ if (typeof error === 'string') {
6
+ return new Error(error);
7
+ }
8
+ if (error && typeof error === 'object') {
9
+ const kind = Object.prototype.toString.call(error);
10
+ try {
11
+ return new Error(`Non-Error thrown (${kind}): ${JSON.stringify(error)}`);
12
+ }
13
+ catch {
14
+ return new Error(`Non-Error thrown (${kind})`);
15
+ }
16
+ }
17
+ return new Error(`Non-Error thrown: ${String(error)}`);
18
+ };
19
+ const serializeCause = (cause) => {
20
+ if (cause instanceof Error) {
21
+ return {
22
+ name: cause.name,
23
+ message: cause.message,
24
+ stack: cause.stack,
25
+ };
26
+ }
27
+ if (cause === undefined || cause === null) {
28
+ return cause;
29
+ }
30
+ return String(cause);
31
+ };
32
+ export const serializeError = (error) => ({
33
+ name: error.name,
34
+ message: error.message,
35
+ stack: error.stack,
36
+ ...('cause' in error ? { cause: serializeCause(error.cause) } : {}),
37
+ });