cc-reviewer 5.2.0 → 5.3.0
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/adapters/claude.js +15 -13
- package/dist/adapters/codex.js +20 -20
- package/dist/adapters/gemini.js +17 -12
- package/dist/config.d.ts +222 -0
- package/dist/config.js +174 -0
- package/dist/index.js +12 -0
- package/dist/schema.d.ts +6 -6
- package/dist/tools/feedback.d.ts +4 -4
- package/package.json +1 -1
package/dist/adapters/claude.js
CHANGED
|
@@ -16,12 +16,7 @@ import { registerAdapter, } from './base.js';
|
|
|
16
16
|
import { CliExecutor } from '../executor.js';
|
|
17
17
|
import { ClaudeEventDecoder } from '../decoders/index.js';
|
|
18
18
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
19
|
-
|
|
20
|
-
// CONFIGURATION
|
|
21
|
-
// =============================================================================
|
|
22
|
-
const INACTIVITY_TIMEOUT_MS = 300_000; // 5 min — Opus has long thinking phases
|
|
23
|
-
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
24
|
-
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
19
|
+
import { getConfig } from '../config.js';
|
|
25
20
|
// Write tools explicitly blocked as defense-in-depth
|
|
26
21
|
const DISALLOWED_TOOLS = 'Edit Write NotebookEdit';
|
|
27
22
|
// =============================================================================
|
|
@@ -43,12 +38,18 @@ export class ClaudeAdapter {
|
|
|
43
38
|
}
|
|
44
39
|
async isAvailable() {
|
|
45
40
|
return new Promise((resolve) => {
|
|
41
|
+
let settled = false;
|
|
42
|
+
const done = (result) => { if (!settled) {
|
|
43
|
+
settled = true;
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
resolve(result);
|
|
46
|
+
} };
|
|
46
47
|
const proc = spawn('claude', ['--version'], {
|
|
47
48
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
49
|
});
|
|
49
|
-
proc.on('close', (code) =>
|
|
50
|
-
proc.on('error', () =>
|
|
51
|
-
setTimeout(() => { proc.kill();
|
|
50
|
+
proc.on('close', (code) => done(code === 0));
|
|
51
|
+
proc.on('error', () => done(false));
|
|
52
|
+
const timer = setTimeout(() => { proc.kill(); done(false); }, 5000);
|
|
52
53
|
});
|
|
53
54
|
}
|
|
54
55
|
async runReview(request) {
|
|
@@ -86,9 +87,10 @@ export class ClaudeAdapter {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
async runCli(prompt, workingDir) {
|
|
90
|
+
const cfg = getConfig().claude;
|
|
89
91
|
const args = [
|
|
90
92
|
'-p', // Non-interactive, print and exit
|
|
91
|
-
'--model',
|
|
93
|
+
'--model', cfg.model, // Model from config (default: opus)
|
|
92
94
|
'--setting-sources', '', // Skip hooks, plugins, CLAUDE.md (preserves OAuth auth; --bare kills keychain)
|
|
93
95
|
'--permission-mode', 'plan', // Read-only enforcement (layer 1)
|
|
94
96
|
'--verbose', // Required for stream-json
|
|
@@ -111,9 +113,9 @@ export class ClaudeAdapter {
|
|
|
111
113
|
args,
|
|
112
114
|
cwd: workingDir,
|
|
113
115
|
stdin: prompt,
|
|
114
|
-
inactivityTimeoutMs:
|
|
115
|
-
maxTimeoutMs:
|
|
116
|
-
maxBufferSize:
|
|
116
|
+
inactivityTimeoutMs: cfg.inactivityTimeoutMs,
|
|
117
|
+
maxTimeoutMs: cfg.maxTimeoutMs,
|
|
118
|
+
maxBufferSize: cfg.maxBufferSize,
|
|
117
119
|
onLine: (line) => {
|
|
118
120
|
decoder.processLine(line);
|
|
119
121
|
},
|
package/dist/adapters/codex.js
CHANGED
|
@@ -11,15 +11,7 @@ import { registerAdapter, } from './base.js';
|
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { CodexEventDecoder } from '../decoders/index.js';
|
|
13
13
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
|
-
|
|
15
|
-
// CONFIGURATION
|
|
16
|
-
// =============================================================================
|
|
17
|
-
const INACTIVITY_TIMEOUT_MS = {
|
|
18
|
-
high: 180_000, // 3 min — covers reasoning gaps between tool use bursts
|
|
19
|
-
xhigh: 300_000, // 5 min — xhigh has longer reasoning phases
|
|
20
|
-
};
|
|
21
|
-
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
22
|
-
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
14
|
+
import { getConfig } from '../config.js';
|
|
23
15
|
// =============================================================================
|
|
24
16
|
// CODEX ADAPTER
|
|
25
17
|
// =============================================================================
|
|
@@ -39,12 +31,18 @@ export class CodexAdapter {
|
|
|
39
31
|
}
|
|
40
32
|
async isAvailable() {
|
|
41
33
|
return new Promise((resolve) => {
|
|
34
|
+
let settled = false;
|
|
35
|
+
const done = (result) => { if (!settled) {
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
resolve(result);
|
|
39
|
+
} };
|
|
42
40
|
const proc = spawn('codex', ['--version'], {
|
|
43
41
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
42
|
});
|
|
45
|
-
proc.on('close', (code) =>
|
|
46
|
-
proc.on('error', () =>
|
|
47
|
-
setTimeout(() => { proc.kill();
|
|
43
|
+
proc.on('close', (code) => done(code === 0));
|
|
44
|
+
proc.on('error', () => done(false));
|
|
45
|
+
const timer = setTimeout(() => { proc.kill(); done(false); }, 5000);
|
|
48
46
|
});
|
|
49
47
|
}
|
|
50
48
|
async runReview(request) {
|
|
@@ -62,7 +60,8 @@ export class CodexAdapter {
|
|
|
62
60
|
const prompt = request.reviewMode === 'adversarial'
|
|
63
61
|
? buildAdversarialHandoffPrompt({ handoff })
|
|
64
62
|
: buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
|
|
65
|
-
const
|
|
63
|
+
const cfg = getConfig().codex;
|
|
64
|
+
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort ?? cfg.reasoningEffort, request.serviceTier);
|
|
66
65
|
if (result.exitCode !== 0) {
|
|
67
66
|
const error = this.categorizeError(result.stderr);
|
|
68
67
|
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
@@ -82,10 +81,11 @@ export class CodexAdapter {
|
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
async runCli(prompt, workingDir, reasoningEffort, serviceTier) {
|
|
84
|
+
const cfg = getConfig().codex;
|
|
85
85
|
const args = [
|
|
86
86
|
'exec',
|
|
87
87
|
'--json', // JSONL streaming events
|
|
88
|
-
'-m',
|
|
88
|
+
'-m', cfg.model,
|
|
89
89
|
'-c', `model_reasoning_effort=${reasoningEffort}`,
|
|
90
90
|
'-c', 'model_reasoning_summary_format=experimental',
|
|
91
91
|
'--full-auto',
|
|
@@ -94,9 +94,9 @@ export class CodexAdapter {
|
|
|
94
94
|
'-C', workingDir,
|
|
95
95
|
'-', // Read prompt from stdin
|
|
96
96
|
];
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
const effectiveTier = serviceTier
|
|
97
|
+
// Caller-supplied serviceTier overrides config. Explicit 'default' is an
|
|
98
|
+
// opt-out and emits no flag (uses Codex API default).
|
|
99
|
+
const effectiveTier = serviceTier ?? cfg.serviceTier;
|
|
100
100
|
if (effectiveTier !== 'default') {
|
|
101
101
|
args.push('-c', `service_tier=${effectiveTier}`);
|
|
102
102
|
}
|
|
@@ -114,9 +114,9 @@ export class CodexAdapter {
|
|
|
114
114
|
args,
|
|
115
115
|
cwd: workingDir,
|
|
116
116
|
stdin: prompt,
|
|
117
|
-
inactivityTimeoutMs:
|
|
118
|
-
maxTimeoutMs:
|
|
119
|
-
maxBufferSize:
|
|
117
|
+
inactivityTimeoutMs: cfg.inactivityTimeoutMs[reasoningEffort] ?? cfg.inactivityTimeoutMs.high,
|
|
118
|
+
maxTimeoutMs: cfg.maxTimeoutMs,
|
|
119
|
+
maxBufferSize: cfg.maxBufferSize,
|
|
120
120
|
onLine: (line) => {
|
|
121
121
|
decoder.processLine(line);
|
|
122
122
|
},
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -11,12 +11,7 @@ import { registerAdapter, } from './base.js';
|
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { GeminiEventDecoder } from '../decoders/index.js';
|
|
13
13
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
|
-
|
|
15
|
-
// CONFIGURATION
|
|
16
|
-
// =============================================================================
|
|
17
|
-
const INACTIVITY_TIMEOUT_MS = 300_000; // 5 min — covers reasoning gaps between tool use
|
|
18
|
-
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
19
|
-
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
14
|
+
import { getConfig } from '../config.js';
|
|
20
15
|
// =============================================================================
|
|
21
16
|
// GEMINI ADAPTER
|
|
22
17
|
// =============================================================================
|
|
@@ -36,12 +31,18 @@ export class GeminiAdapter {
|
|
|
36
31
|
}
|
|
37
32
|
async isAvailable() {
|
|
38
33
|
return new Promise((resolve) => {
|
|
34
|
+
let settled = false;
|
|
35
|
+
const done = (result) => { if (!settled) {
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
resolve(result);
|
|
39
|
+
} };
|
|
39
40
|
const proc = spawn('gemini', ['--version'], {
|
|
40
41
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
41
42
|
});
|
|
42
|
-
proc.on('close', (code) =>
|
|
43
|
-
proc.on('error', () =>
|
|
44
|
-
setTimeout(() => { proc.kill();
|
|
43
|
+
proc.on('close', (code) => done(code === 0));
|
|
44
|
+
proc.on('error', () => done(false));
|
|
45
|
+
const timer = setTimeout(() => { proc.kill(); done(false); }, 5000);
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
async runReview(request) {
|
|
@@ -79,6 +80,7 @@ export class GeminiAdapter {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
async runCli(prompt, workingDir) {
|
|
83
|
+
const cfg = getConfig().gemini;
|
|
82
84
|
const args = [
|
|
83
85
|
'--sandbox',
|
|
84
86
|
'--approval-mode', 'plan',
|
|
@@ -86,6 +88,9 @@ export class GeminiAdapter {
|
|
|
86
88
|
'--include-directories', workingDir,
|
|
87
89
|
'-p', '',
|
|
88
90
|
];
|
|
91
|
+
if (cfg.model) {
|
|
92
|
+
args.push('--model', cfg.model);
|
|
93
|
+
}
|
|
89
94
|
const decoder = new GeminiEventDecoder();
|
|
90
95
|
const cliStartTime = Date.now();
|
|
91
96
|
console.error('[gemini] Running...');
|
|
@@ -99,9 +104,9 @@ export class GeminiAdapter {
|
|
|
99
104
|
args,
|
|
100
105
|
cwd: workingDir,
|
|
101
106
|
stdin: prompt,
|
|
102
|
-
inactivityTimeoutMs:
|
|
103
|
-
maxTimeoutMs:
|
|
104
|
-
maxBufferSize:
|
|
107
|
+
inactivityTimeoutMs: cfg.inactivityTimeoutMs,
|
|
108
|
+
maxTimeoutMs: cfg.maxTimeoutMs,
|
|
109
|
+
maxBufferSize: cfg.maxBufferSize,
|
|
105
110
|
onLine: (line) => {
|
|
106
111
|
decoder.processLine(line);
|
|
107
112
|
},
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration for cc-reviewer.
|
|
3
|
+
*
|
|
4
|
+
* Config file: ~/.config/cc-reviewer/config.json
|
|
5
|
+
*
|
|
6
|
+
* Semantics:
|
|
7
|
+
* - Lazy, cached load. `getConfig()` returns the cached config or reads once.
|
|
8
|
+
* - Missing file → defaults in memory (no write). Use `initConfig()` from the
|
|
9
|
+
* server entry point to create the file with defaults on first launch.
|
|
10
|
+
* - Invalid JSON or schema violations → fall back to defaults, warn on stderr.
|
|
11
|
+
* - Partial user configs are deep-merged against defaults via Zod `.default()`.
|
|
12
|
+
* - Tool-call arguments still override config (e.g. `reasoningEffort` on a
|
|
13
|
+
* single `codex_review` call). Config only sets defaults.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
export declare const CodexConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
17
|
+
model: z.ZodDefault<z.ZodString>;
|
|
18
|
+
reasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
19
|
+
serviceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
20
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodObject<{
|
|
21
|
+
high: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
xhigh: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
high: number;
|
|
25
|
+
xhigh: number;
|
|
26
|
+
}, {
|
|
27
|
+
high?: number | undefined;
|
|
28
|
+
xhigh?: number | undefined;
|
|
29
|
+
}>>;
|
|
30
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
32
|
+
}, "strip", z.ZodTypeAny, {
|
|
33
|
+
model: string;
|
|
34
|
+
reasoningEffort: "high" | "xhigh";
|
|
35
|
+
serviceTier: "default" | "fast" | "flex";
|
|
36
|
+
inactivityTimeoutMs: {
|
|
37
|
+
high: number;
|
|
38
|
+
xhigh: number;
|
|
39
|
+
};
|
|
40
|
+
maxTimeoutMs: number;
|
|
41
|
+
maxBufferSize: number;
|
|
42
|
+
}, {
|
|
43
|
+
model?: string | undefined;
|
|
44
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
45
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
46
|
+
inactivityTimeoutMs?: {
|
|
47
|
+
high?: number | undefined;
|
|
48
|
+
xhigh?: number | undefined;
|
|
49
|
+
} | undefined;
|
|
50
|
+
maxTimeoutMs?: number | undefined;
|
|
51
|
+
maxBufferSize?: number | undefined;
|
|
52
|
+
}>>;
|
|
53
|
+
export declare const ClaudeConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
54
|
+
model: z.ZodDefault<z.ZodString>;
|
|
55
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
56
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
57
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
58
|
+
}, "strip", z.ZodTypeAny, {
|
|
59
|
+
model: string;
|
|
60
|
+
inactivityTimeoutMs: number;
|
|
61
|
+
maxTimeoutMs: number;
|
|
62
|
+
maxBufferSize: number;
|
|
63
|
+
}, {
|
|
64
|
+
model?: string | undefined;
|
|
65
|
+
inactivityTimeoutMs?: number | undefined;
|
|
66
|
+
maxTimeoutMs?: number | undefined;
|
|
67
|
+
maxBufferSize?: number | undefined;
|
|
68
|
+
}>>;
|
|
69
|
+
export declare const GeminiConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
70
|
+
model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
71
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
72
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
73
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
74
|
+
}, "strip", z.ZodTypeAny, {
|
|
75
|
+
model: string | null;
|
|
76
|
+
inactivityTimeoutMs: number;
|
|
77
|
+
maxTimeoutMs: number;
|
|
78
|
+
maxBufferSize: number;
|
|
79
|
+
}, {
|
|
80
|
+
model?: string | null | undefined;
|
|
81
|
+
inactivityTimeoutMs?: number | undefined;
|
|
82
|
+
maxTimeoutMs?: number | undefined;
|
|
83
|
+
maxBufferSize?: number | undefined;
|
|
84
|
+
}>>;
|
|
85
|
+
export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
86
|
+
codex: z.ZodDefault<z.ZodObject<{
|
|
87
|
+
model: z.ZodDefault<z.ZodString>;
|
|
88
|
+
reasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
89
|
+
serviceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
90
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodObject<{
|
|
91
|
+
high: z.ZodDefault<z.ZodNumber>;
|
|
92
|
+
xhigh: z.ZodDefault<z.ZodNumber>;
|
|
93
|
+
}, "strip", z.ZodTypeAny, {
|
|
94
|
+
high: number;
|
|
95
|
+
xhigh: number;
|
|
96
|
+
}, {
|
|
97
|
+
high?: number | undefined;
|
|
98
|
+
xhigh?: number | undefined;
|
|
99
|
+
}>>;
|
|
100
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
101
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
102
|
+
}, "strip", z.ZodTypeAny, {
|
|
103
|
+
model: string;
|
|
104
|
+
reasoningEffort: "high" | "xhigh";
|
|
105
|
+
serviceTier: "default" | "fast" | "flex";
|
|
106
|
+
inactivityTimeoutMs: {
|
|
107
|
+
high: number;
|
|
108
|
+
xhigh: number;
|
|
109
|
+
};
|
|
110
|
+
maxTimeoutMs: number;
|
|
111
|
+
maxBufferSize: number;
|
|
112
|
+
}, {
|
|
113
|
+
model?: string | undefined;
|
|
114
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
115
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
116
|
+
inactivityTimeoutMs?: {
|
|
117
|
+
high?: number | undefined;
|
|
118
|
+
xhigh?: number | undefined;
|
|
119
|
+
} | undefined;
|
|
120
|
+
maxTimeoutMs?: number | undefined;
|
|
121
|
+
maxBufferSize?: number | undefined;
|
|
122
|
+
}>>;
|
|
123
|
+
claude: z.ZodDefault<z.ZodObject<{
|
|
124
|
+
model: z.ZodDefault<z.ZodString>;
|
|
125
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
126
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
127
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
128
|
+
}, "strip", z.ZodTypeAny, {
|
|
129
|
+
model: string;
|
|
130
|
+
inactivityTimeoutMs: number;
|
|
131
|
+
maxTimeoutMs: number;
|
|
132
|
+
maxBufferSize: number;
|
|
133
|
+
}, {
|
|
134
|
+
model?: string | undefined;
|
|
135
|
+
inactivityTimeoutMs?: number | undefined;
|
|
136
|
+
maxTimeoutMs?: number | undefined;
|
|
137
|
+
maxBufferSize?: number | undefined;
|
|
138
|
+
}>>;
|
|
139
|
+
gemini: z.ZodDefault<z.ZodObject<{
|
|
140
|
+
model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
141
|
+
inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
142
|
+
maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
143
|
+
maxBufferSize: z.ZodDefault<z.ZodNumber>;
|
|
144
|
+
}, "strip", z.ZodTypeAny, {
|
|
145
|
+
model: string | null;
|
|
146
|
+
inactivityTimeoutMs: number;
|
|
147
|
+
maxTimeoutMs: number;
|
|
148
|
+
maxBufferSize: number;
|
|
149
|
+
}, {
|
|
150
|
+
model?: string | null | undefined;
|
|
151
|
+
inactivityTimeoutMs?: number | undefined;
|
|
152
|
+
maxTimeoutMs?: number | undefined;
|
|
153
|
+
maxBufferSize?: number | undefined;
|
|
154
|
+
}>>;
|
|
155
|
+
}, "strip", z.ZodTypeAny, {
|
|
156
|
+
codex: {
|
|
157
|
+
model: string;
|
|
158
|
+
reasoningEffort: "high" | "xhigh";
|
|
159
|
+
serviceTier: "default" | "fast" | "flex";
|
|
160
|
+
inactivityTimeoutMs: {
|
|
161
|
+
high: number;
|
|
162
|
+
xhigh: number;
|
|
163
|
+
};
|
|
164
|
+
maxTimeoutMs: number;
|
|
165
|
+
maxBufferSize: number;
|
|
166
|
+
};
|
|
167
|
+
claude: {
|
|
168
|
+
model: string;
|
|
169
|
+
inactivityTimeoutMs: number;
|
|
170
|
+
maxTimeoutMs: number;
|
|
171
|
+
maxBufferSize: number;
|
|
172
|
+
};
|
|
173
|
+
gemini: {
|
|
174
|
+
model: string | null;
|
|
175
|
+
inactivityTimeoutMs: number;
|
|
176
|
+
maxTimeoutMs: number;
|
|
177
|
+
maxBufferSize: number;
|
|
178
|
+
};
|
|
179
|
+
}, {
|
|
180
|
+
codex?: {
|
|
181
|
+
model?: string | undefined;
|
|
182
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
183
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
184
|
+
inactivityTimeoutMs?: {
|
|
185
|
+
high?: number | undefined;
|
|
186
|
+
xhigh?: number | undefined;
|
|
187
|
+
} | undefined;
|
|
188
|
+
maxTimeoutMs?: number | undefined;
|
|
189
|
+
maxBufferSize?: number | undefined;
|
|
190
|
+
} | undefined;
|
|
191
|
+
claude?: {
|
|
192
|
+
model?: string | undefined;
|
|
193
|
+
inactivityTimeoutMs?: number | undefined;
|
|
194
|
+
maxTimeoutMs?: number | undefined;
|
|
195
|
+
maxBufferSize?: number | undefined;
|
|
196
|
+
} | undefined;
|
|
197
|
+
gemini?: {
|
|
198
|
+
model?: string | null | undefined;
|
|
199
|
+
inactivityTimeoutMs?: number | undefined;
|
|
200
|
+
maxTimeoutMs?: number | undefined;
|
|
201
|
+
maxBufferSize?: number | undefined;
|
|
202
|
+
} | undefined;
|
|
203
|
+
}>>;
|
|
204
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
205
|
+
export type CodexConfig = z.infer<typeof CodexConfigSchema>;
|
|
206
|
+
export type ClaudeConfig = z.infer<typeof ClaudeConfigSchema>;
|
|
207
|
+
export type GeminiConfig = z.infer<typeof GeminiConfigSchema>;
|
|
208
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
209
|
+
export declare function getConfigPath(): string;
|
|
210
|
+
export declare function getConfig(): Config;
|
|
211
|
+
/**
|
|
212
|
+
* Create the config file with defaults if it does not exist.
|
|
213
|
+
* Uses the exclusive `wx` flag for atomic creation — safe against TOCTOU races
|
|
214
|
+
* when multiple server instances start concurrently.
|
|
215
|
+
* Refreshes the cached config so subsequent `getConfig()` calls see disk state.
|
|
216
|
+
*/
|
|
217
|
+
export declare function initConfig(): {
|
|
218
|
+
path: string;
|
|
219
|
+
created: boolean;
|
|
220
|
+
};
|
|
221
|
+
/** Test-only hook. Redirects the config path and clears the cache. */
|
|
222
|
+
export declare function setConfigPathForTesting(path: string | null): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration for cc-reviewer.
|
|
3
|
+
*
|
|
4
|
+
* Config file: ~/.config/cc-reviewer/config.json
|
|
5
|
+
*
|
|
6
|
+
* Semantics:
|
|
7
|
+
* - Lazy, cached load. `getConfig()` returns the cached config or reads once.
|
|
8
|
+
* - Missing file → defaults in memory (no write). Use `initConfig()` from the
|
|
9
|
+
* server entry point to create the file with defaults on first launch.
|
|
10
|
+
* - Invalid JSON or schema violations → fall back to defaults, warn on stderr.
|
|
11
|
+
* - Partial user configs are deep-merged against defaults via Zod `.default()`.
|
|
12
|
+
* - Tool-call arguments still override config (e.g. `reasoningEffort` on a
|
|
13
|
+
* single `codex_review` call). Config only sets defaults.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'fs';
|
|
17
|
+
import { dirname, join } from 'path';
|
|
18
|
+
import { homedir } from 'os';
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// SCHEMA
|
|
21
|
+
// =============================================================================
|
|
22
|
+
export const CodexConfigSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
model: z.string().default('gpt-5.4'),
|
|
25
|
+
reasoningEffort: z.enum(['high', 'xhigh']).default('high'),
|
|
26
|
+
serviceTier: z.enum(['default', 'fast', 'flex']).default('fast'),
|
|
27
|
+
inactivityTimeoutMs: z
|
|
28
|
+
.object({
|
|
29
|
+
high: z.number().int().positive().default(180_000),
|
|
30
|
+
xhigh: z.number().int().positive().default(300_000),
|
|
31
|
+
})
|
|
32
|
+
.default({}),
|
|
33
|
+
maxTimeoutMs: z.number().int().positive().default(3_600_000),
|
|
34
|
+
maxBufferSize: z.number().int().positive().default(1_048_576),
|
|
35
|
+
})
|
|
36
|
+
.default({});
|
|
37
|
+
export const ClaudeConfigSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
model: z.string().default('opus'),
|
|
40
|
+
inactivityTimeoutMs: z.number().int().positive().default(300_000),
|
|
41
|
+
maxTimeoutMs: z.number().int().positive().default(3_600_000),
|
|
42
|
+
maxBufferSize: z.number().int().positive().default(1_048_576),
|
|
43
|
+
})
|
|
44
|
+
.default({});
|
|
45
|
+
export const GeminiConfigSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
model: z.string().nullable().default('gemini-3.1-pro-preview'),
|
|
48
|
+
inactivityTimeoutMs: z.number().int().positive().default(300_000),
|
|
49
|
+
maxTimeoutMs: z.number().int().positive().default(3_600_000),
|
|
50
|
+
maxBufferSize: z.number().int().positive().default(1_048_576),
|
|
51
|
+
})
|
|
52
|
+
.default({});
|
|
53
|
+
export const ConfigSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
codex: CodexConfigSchema,
|
|
56
|
+
claude: ClaudeConfigSchema,
|
|
57
|
+
gemini: GeminiConfigSchema,
|
|
58
|
+
})
|
|
59
|
+
.default({});
|
|
60
|
+
export const DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// STATE
|
|
63
|
+
// =============================================================================
|
|
64
|
+
const DEFAULT_CONFIG_PATH = join(homedir(), '.config', 'cc-reviewer', 'config.json');
|
|
65
|
+
let _configPath = DEFAULT_CONFIG_PATH;
|
|
66
|
+
let _cached = null;
|
|
67
|
+
let _cachedMtimeMs = 0;
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// PUBLIC API
|
|
70
|
+
// =============================================================================
|
|
71
|
+
export function getConfigPath() {
|
|
72
|
+
return _configPath;
|
|
73
|
+
}
|
|
74
|
+
export function getConfig() {
|
|
75
|
+
// Hot-reload: re-read if the file's mtime has changed since last load.
|
|
76
|
+
if (_cached) {
|
|
77
|
+
try {
|
|
78
|
+
if (existsSync(_configPath)) {
|
|
79
|
+
const mtime = statSync(_configPath).mtimeMs;
|
|
80
|
+
if (mtime !== _cachedMtimeMs) {
|
|
81
|
+
_cached = loadConfigFromDisk(_configPath);
|
|
82
|
+
_cachedMtimeMs = mtime;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// statSync failure is non-fatal — keep using the cached config.
|
|
88
|
+
}
|
|
89
|
+
return _cached;
|
|
90
|
+
}
|
|
91
|
+
_cached = loadConfigFromDisk(_configPath);
|
|
92
|
+
if (existsSync(_configPath)) {
|
|
93
|
+
try {
|
|
94
|
+
_cachedMtimeMs = statSync(_configPath).mtimeMs;
|
|
95
|
+
}
|
|
96
|
+
catch { /* ignore */ }
|
|
97
|
+
}
|
|
98
|
+
return _cached;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create the config file with defaults if it does not exist.
|
|
102
|
+
* Uses the exclusive `wx` flag for atomic creation — safe against TOCTOU races
|
|
103
|
+
* when multiple server instances start concurrently.
|
|
104
|
+
* Refreshes the cached config so subsequent `getConfig()` calls see disk state.
|
|
105
|
+
*/
|
|
106
|
+
export function initConfig() {
|
|
107
|
+
const path = _configPath;
|
|
108
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
109
|
+
try {
|
|
110
|
+
writeFileSync(path, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n', { encoding: 'utf-8', flag: 'wx' });
|
|
111
|
+
_cached = DEFAULT_CONFIG;
|
|
112
|
+
_cachedMtimeMs = statSync(path).mtimeMs;
|
|
113
|
+
return { path, created: true };
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error.code === 'EEXIST') {
|
|
117
|
+
_cached = loadConfigFromDisk(path);
|
|
118
|
+
try {
|
|
119
|
+
_cachedMtimeMs = statSync(path).mtimeMs;
|
|
120
|
+
}
|
|
121
|
+
catch { /* ignore */ }
|
|
122
|
+
return { path, created: false };
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Test-only hook. Redirects the config path and clears the cache. */
|
|
128
|
+
export function setConfigPathForTesting(path) {
|
|
129
|
+
_configPath = path ?? DEFAULT_CONFIG_PATH;
|
|
130
|
+
_cached = null;
|
|
131
|
+
_cachedMtimeMs = 0;
|
|
132
|
+
}
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// INTERNAL
|
|
135
|
+
// =============================================================================
|
|
136
|
+
/**
|
|
137
|
+
* Parse each adapter's config independently so a typo in one section only
|
|
138
|
+
* resets that adapter to defaults — the other adapters' settings survive.
|
|
139
|
+
*/
|
|
140
|
+
function loadConfigFromDisk(path) {
|
|
141
|
+
if (!existsSync(path))
|
|
142
|
+
return DEFAULT_CONFIG;
|
|
143
|
+
let raw;
|
|
144
|
+
try {
|
|
145
|
+
raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
146
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
147
|
+
console.error(`[cc-reviewer] Config at ${path} is not a JSON object — using defaults.`);
|
|
148
|
+
return DEFAULT_CONFIG;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
153
|
+
console.error(`[cc-reviewer] Invalid JSON in ${path} — using defaults. Error: ${msg}`);
|
|
154
|
+
return DEFAULT_CONFIG;
|
|
155
|
+
}
|
|
156
|
+
const adapters = [
|
|
157
|
+
{ key: 'codex', schema: CodexConfigSchema },
|
|
158
|
+
{ key: 'claude', schema: ClaudeConfigSchema },
|
|
159
|
+
{ key: 'gemini', schema: GeminiConfigSchema },
|
|
160
|
+
];
|
|
161
|
+
const result = {};
|
|
162
|
+
for (const { key, schema } of adapters) {
|
|
163
|
+
const section = raw[key];
|
|
164
|
+
try {
|
|
165
|
+
result[key] = schema.parse(section);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
169
|
+
console.error(`[cc-reviewer] Invalid "${key}" config — using ${key} defaults. Error: ${msg}`);
|
|
170
|
+
result[key] = schema.parse(undefined);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
21
21
|
import { handleCodexReview, handleGeminiReview, handleClaudeReview, handleMultiReview, ReviewInputSchema, TOOL_DEFINITIONS } from './tools/feedback.js';
|
|
22
22
|
import { logCliStatus } from './cli/check.js';
|
|
23
23
|
import { installCommands } from './commands.js';
|
|
24
|
+
import { initConfig } from './config.js';
|
|
24
25
|
// Read version from package.json
|
|
25
26
|
import { readFileSync } from 'fs';
|
|
26
27
|
import { join, dirname } from 'path';
|
|
@@ -111,6 +112,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
111
112
|
});
|
|
112
113
|
// Start the server
|
|
113
114
|
async function main() {
|
|
115
|
+
// Initialize config (writes defaults to ~/.config/cc-reviewer/config.json on first run)
|
|
116
|
+
try {
|
|
117
|
+
const cfg = initConfig();
|
|
118
|
+
console.error(cfg.created
|
|
119
|
+
? `[cc-reviewer] Initialized config at ${cfg.path}`
|
|
120
|
+
: `[cc-reviewer] Loaded config from ${cfg.path}`);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
124
|
+
console.error(`[cc-reviewer] Warning: Could not initialize config: ${msg}`);
|
|
125
|
+
}
|
|
114
126
|
// Auto-install slash commands
|
|
115
127
|
const result = installCommands();
|
|
116
128
|
if (result.success) {
|
package/dist/schema.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export declare const ReviewFinding: z.ZodObject<{
|
|
|
63
63
|
owasp_category: z.ZodOptional<z.ZodString>;
|
|
64
64
|
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
65
65
|
}, "strip", z.ZodTypeAny, {
|
|
66
|
-
severity: "
|
|
66
|
+
severity: "high" | "info" | "critical" | "medium" | "low";
|
|
67
67
|
title: string;
|
|
68
68
|
description: string;
|
|
69
69
|
category: "other" | "performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation" | "best-practice";
|
|
@@ -82,7 +82,7 @@ export declare const ReviewFinding: z.ZodObject<{
|
|
|
82
82
|
owasp_category?: string | undefined;
|
|
83
83
|
tags?: string[] | undefined;
|
|
84
84
|
}, {
|
|
85
|
-
severity: "
|
|
85
|
+
severity: "high" | "info" | "critical" | "medium" | "low";
|
|
86
86
|
title: string;
|
|
87
87
|
description: string;
|
|
88
88
|
category: "other" | "performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation" | "best-practice";
|
|
@@ -266,7 +266,7 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
266
266
|
owasp_category: z.ZodOptional<z.ZodString>;
|
|
267
267
|
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
268
268
|
}, "strip", z.ZodTypeAny, {
|
|
269
|
-
severity: "
|
|
269
|
+
severity: "high" | "info" | "critical" | "medium" | "low";
|
|
270
270
|
title: string;
|
|
271
271
|
description: string;
|
|
272
272
|
category: "other" | "performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation" | "best-practice";
|
|
@@ -285,7 +285,7 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
285
285
|
owasp_category?: string | undefined;
|
|
286
286
|
tags?: string[] | undefined;
|
|
287
287
|
}, {
|
|
288
|
-
severity: "
|
|
288
|
+
severity: "high" | "info" | "critical" | "medium" | "low";
|
|
289
289
|
title: string;
|
|
290
290
|
description: string;
|
|
291
291
|
category: "other" | "performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation" | "best-practice";
|
|
@@ -431,7 +431,7 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
431
431
|
execution_notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
432
432
|
}, "strip", z.ZodTypeAny, {
|
|
433
433
|
findings: {
|
|
434
|
-
severity: "
|
|
434
|
+
severity: "high" | "info" | "critical" | "medium" | "low";
|
|
435
435
|
title: string;
|
|
436
436
|
description: string;
|
|
437
437
|
category: "other" | "performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation" | "best-practice";
|
|
@@ -499,7 +499,7 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
499
499
|
execution_notes?: string | null | undefined;
|
|
500
500
|
}, {
|
|
501
501
|
findings: {
|
|
502
|
-
severity: "
|
|
502
|
+
severity: "high" | "info" | "critical" | "medium" | "low";
|
|
503
503
|
title: string;
|
|
504
504
|
description: string;
|
|
505
505
|
category: "other" | "performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation" | "best-practice";
|
package/dist/tools/feedback.d.ts
CHANGED
|
@@ -18,20 +18,20 @@ export declare const ReviewInputSchema: z.ZodObject<{
|
|
|
18
18
|
workingDir: string;
|
|
19
19
|
ccOutput: string;
|
|
20
20
|
outputType: "findings" | "analysis" | "plan" | "proposal";
|
|
21
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
22
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
21
23
|
focusAreas?: ("performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation")[] | undefined;
|
|
22
24
|
analyzedFiles?: string[] | undefined;
|
|
23
25
|
customPrompt?: string | undefined;
|
|
24
|
-
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
25
|
-
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
26
26
|
}, {
|
|
27
27
|
workingDir: string;
|
|
28
28
|
ccOutput: string;
|
|
29
29
|
outputType: "findings" | "analysis" | "plan" | "proposal";
|
|
30
|
+
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
31
|
+
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
30
32
|
focusAreas?: ("performance" | "security" | "testing" | "architecture" | "correctness" | "maintainability" | "scalability" | "documentation")[] | undefined;
|
|
31
33
|
analyzedFiles?: string[] | undefined;
|
|
32
34
|
customPrompt?: string | undefined;
|
|
33
|
-
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
34
|
-
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
35
35
|
}>;
|
|
36
36
|
export type ReviewInput = z.infer<typeof ReviewInputSchema>;
|
|
37
37
|
export declare function handleCodexReview(input: ReviewInput): Promise<{
|