@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.
- 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/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 +267 -0
- 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/package.json +1 -1
package/dist/cli/help.js
ADDED
|
@@ -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,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 {};
|
package/dist/config/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/config/index.js
CHANGED
|
@@ -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,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
|
+
});
|