cc-reviewer 1.1.2 → 1.1.4
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/package.json
CHANGED
package/README.md
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# CC Reviewer - AI Code Review for Claude Code
|
|
2
|
-
|
|
3
|
-
Get second-opinion feedback from OpenAI Codex and Google Gemini CLIs on Claude Code's work, then synthesize and incorporate.
|
|
4
|
-
|
|
5
|
-
## Quick Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
claude mcp add -s user cc-reviewer -- npx -y cc-reviewer
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
That's it! Restart Claude Code and the tools are available.
|
|
12
|
-
|
|
13
|
-
Verify with:
|
|
14
|
-
```bash
|
|
15
|
-
claude mcp list
|
|
16
|
-
# cc-reviewer: npx -y cc-reviewer - ✓ Connected
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### Alternative: Manual Install
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
git clone https://github.com/SimonRen/cc-reviewer.git
|
|
23
|
-
cd cc-reviewer/mcp-server
|
|
24
|
-
npm install && npm run build
|
|
25
|
-
claude mcp add -s user cc-reviewer -- node /path/to/cc-reviewer/mcp-server/dist/index.js
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Prerequisites
|
|
29
|
-
|
|
30
|
-
Install at least one AI CLI:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# OpenAI Codex CLI
|
|
34
|
-
npm install -g @openai/codex-cli
|
|
35
|
-
codex login
|
|
36
|
-
|
|
37
|
-
# Google Gemini CLI
|
|
38
|
-
npm install -g @google/gemini-cli
|
|
39
|
-
gemini # follow auth prompts
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Slash Commands (Optional)
|
|
43
|
-
|
|
44
|
-
Copy the slash commands to your global commands folder:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# Clone repo (if not already)
|
|
48
|
-
git clone https://github.com/SimonRen/cc-reviewer.git
|
|
49
|
-
|
|
50
|
-
# Copy commands
|
|
51
|
-
cp cc-reviewer/commands/*.md ~/.claude/commands/
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Then use:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
/codex-review # Review with Codex
|
|
58
|
-
/codex-review security # Focus on security
|
|
59
|
-
|
|
60
|
-
/gemini-review # Review with Gemini
|
|
61
|
-
/gemini-review architecture # Focus on architecture
|
|
62
|
-
|
|
63
|
-
/multi-review # Both models in parallel
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## How It Works
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
CC does work → User: /codex-review → External CLI reviews → CC synthesizes → Updated output
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Key Principles:**
|
|
73
|
-
- **CC is primary**: Claude Code does all the work; external models only review
|
|
74
|
-
- **Working directory strategy**: Pass `cwd` + small CC output; external CLIs read files directly
|
|
75
|
-
- **Synthesis, not passthrough**: CC always judges external feedback before incorporating
|
|
76
|
-
|
|
77
|
-
## Focus Areas
|
|
78
|
-
|
|
79
|
-
| Area | Description |
|
|
80
|
-
|------|-------------|
|
|
81
|
-
| `security` | Vulnerabilities, auth, input validation |
|
|
82
|
-
| `performance` | Speed, memory, efficiency |
|
|
83
|
-
| `architecture` | Design patterns, structure, coupling |
|
|
84
|
-
| `correctness` | Logic errors, edge cases, bugs |
|
|
85
|
-
| `maintainability` | Code clarity, documentation, complexity |
|
|
86
|
-
| `scalability` | Load handling, bottlenecks |
|
|
87
|
-
| `testing` | Test coverage, test quality |
|
|
88
|
-
| `documentation` | Comments, docs, API docs |
|
|
89
|
-
|
|
90
|
-
## MCP Tools
|
|
91
|
-
|
|
92
|
-
The plugin exposes four MCP tools:
|
|
93
|
-
|
|
94
|
-
| Tool | Description |
|
|
95
|
-
|------|-------------|
|
|
96
|
-
| `codex_feedback` | Get Codex review (correctness, edge cases, performance) |
|
|
97
|
-
| `gemini_feedback` | Get Gemini review (design patterns, scalability, tech debt) |
|
|
98
|
-
| `multi_feedback` | Parallel review from both models |
|
|
99
|
-
| `council_feedback` | Multi-model consensus with verification pipeline |
|
|
100
|
-
|
|
101
|
-
## Output Format
|
|
102
|
-
|
|
103
|
-
External CLIs return structured JSON feedback with:
|
|
104
|
-
- **Findings**: Issues with severity, confidence, location, and suggestions
|
|
105
|
-
- **Agreements**: Validations of CC's correct assessments
|
|
106
|
-
- **Disagreements**: Challenges to CC's claims with corrections
|
|
107
|
-
- **Alternatives**: Different approaches with tradeoffs
|
|
108
|
-
- **Risk Assessment**: Overall risk level with top concerns
|
|
109
|
-
|
|
110
|
-
## Development
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
cd mcp-server
|
|
114
|
-
npm install
|
|
115
|
-
npm run build # Build once
|
|
116
|
-
npm run dev # Watch mode
|
|
117
|
-
npm test # Run tests
|
|
118
|
-
npm run test:watch # Watch mode tests
|
|
119
|
-
npm start # Run server
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Publishing
|
|
123
|
-
|
|
124
|
-
Uses npm Trusted Publishing (OIDC, no tokens):
|
|
125
|
-
```bash
|
|
126
|
-
gh workflow run publish.yml -f version=patch # or minor/major
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## License
|
|
130
|
-
|
|
131
|
-
MIT
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for pipeline.ts - verification and security features
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
import { tmpdir } from 'os';
|
|
8
|
-
import { FileCache, verifyFinding } from '../pipeline.js';
|
|
9
|
-
// =============================================================================
|
|
10
|
-
// TEST SETUP
|
|
11
|
-
// =============================================================================
|
|
12
|
-
const TEST_DIR = join(tmpdir(), 'pipeline-test-' + Date.now());
|
|
13
|
-
function createTestFile(relativePath, content) {
|
|
14
|
-
const fullPath = join(TEST_DIR, relativePath);
|
|
15
|
-
const dir = fullPath.substring(0, fullPath.lastIndexOf('/'));
|
|
16
|
-
if (dir && !existsSync(dir)) {
|
|
17
|
-
mkdirSync(dir, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
writeFileSync(fullPath, content);
|
|
20
|
-
}
|
|
21
|
-
function createTestFinding(overrides = {}) {
|
|
22
|
-
return {
|
|
23
|
-
id: 'test-1',
|
|
24
|
-
category: 'correctness',
|
|
25
|
-
severity: 'medium',
|
|
26
|
-
confidence: 0.8,
|
|
27
|
-
title: 'Test finding',
|
|
28
|
-
description: 'Test description',
|
|
29
|
-
...overrides,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
// =============================================================================
|
|
33
|
-
// FILE CACHE TESTS
|
|
34
|
-
// =============================================================================
|
|
35
|
-
describe('FileCache', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
38
|
-
createTestFile('existing.ts', 'line 1\nline 2\nline 3\n');
|
|
39
|
-
createTestFile('subdir/nested.ts', 'nested content');
|
|
40
|
-
});
|
|
41
|
-
afterEach(() => {
|
|
42
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
43
|
-
});
|
|
44
|
-
it('should return true for existing files', () => {
|
|
45
|
-
const cache = new FileCache(TEST_DIR);
|
|
46
|
-
expect(cache.exists('existing.ts')).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
it('should return false for non-existing files', () => {
|
|
49
|
-
const cache = new FileCache(TEST_DIR);
|
|
50
|
-
expect(cache.exists('nonexistent.ts')).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
it('should cache file existence checks', () => {
|
|
53
|
-
const cache = new FileCache(TEST_DIR);
|
|
54
|
-
// First call
|
|
55
|
-
cache.exists('existing.ts');
|
|
56
|
-
cache.exists('nonexistent.ts');
|
|
57
|
-
// Stats should show 1 file checked (non-existent cached as null)
|
|
58
|
-
const stats = cache.getStats();
|
|
59
|
-
expect(stats.filesChecked).toBe(1); // Only non-existent is cached on exists()
|
|
60
|
-
});
|
|
61
|
-
it('should return file content', () => {
|
|
62
|
-
const cache = new FileCache(TEST_DIR);
|
|
63
|
-
const content = cache.getContent('existing.ts');
|
|
64
|
-
expect(content).toBe('line 1\nline 2\nline 3\n');
|
|
65
|
-
});
|
|
66
|
-
it('should return null for non-existing file content', () => {
|
|
67
|
-
const cache = new FileCache(TEST_DIR);
|
|
68
|
-
const content = cache.getContent('nonexistent.ts');
|
|
69
|
-
expect(content).toBeNull();
|
|
70
|
-
});
|
|
71
|
-
it('should cache file content', () => {
|
|
72
|
-
const cache = new FileCache(TEST_DIR);
|
|
73
|
-
// Read twice
|
|
74
|
-
cache.getContent('existing.ts');
|
|
75
|
-
cache.getContent('existing.ts');
|
|
76
|
-
const stats = cache.getStats();
|
|
77
|
-
expect(stats.filesLoaded).toBe(1);
|
|
78
|
-
});
|
|
79
|
-
it('should return lines array', () => {
|
|
80
|
-
const cache = new FileCache(TEST_DIR);
|
|
81
|
-
const lines = cache.getLines('existing.ts');
|
|
82
|
-
expect(lines).toEqual(['line 1', 'line 2', 'line 3', '']);
|
|
83
|
-
});
|
|
84
|
-
it('should return correct line count', () => {
|
|
85
|
-
const cache = new FileCache(TEST_DIR);
|
|
86
|
-
const count = cache.getLineCount('existing.ts');
|
|
87
|
-
expect(count).toBe(4); // 3 lines + empty line from trailing newline
|
|
88
|
-
});
|
|
89
|
-
it('should handle nested paths', () => {
|
|
90
|
-
const cache = new FileCache(TEST_DIR);
|
|
91
|
-
expect(cache.exists('subdir/nested.ts')).toBe(true);
|
|
92
|
-
expect(cache.getContent('subdir/nested.ts')).toBe('nested content');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
// =============================================================================
|
|
96
|
-
// PATH TRAVERSAL TESTS
|
|
97
|
-
// =============================================================================
|
|
98
|
-
describe('Path Traversal Protection', () => {
|
|
99
|
-
beforeEach(() => {
|
|
100
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
101
|
-
createTestFile('safe.ts', 'safe content\nline 2\nline 3');
|
|
102
|
-
});
|
|
103
|
-
afterEach(() => {
|
|
104
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
105
|
-
});
|
|
106
|
-
it('should block ../../../etc/passwd traversal', async () => {
|
|
107
|
-
const finding = createTestFinding({
|
|
108
|
-
location: { file: '../../../etc/passwd', line_start: 1 },
|
|
109
|
-
});
|
|
110
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
111
|
-
expect(result.verification.fileExists).toBe(false);
|
|
112
|
-
expect(result.verification.verificationNotes).toContain('Path traversal blocked');
|
|
113
|
-
expect(result.adjustedConfidence).toBeLessThan(0.1);
|
|
114
|
-
});
|
|
115
|
-
it('should block absolute path /etc/passwd', async () => {
|
|
116
|
-
const finding = createTestFinding({
|
|
117
|
-
location: { file: '/etc/passwd', line_start: 1 },
|
|
118
|
-
});
|
|
119
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
120
|
-
expect(result.verification.fileExists).toBe(false);
|
|
121
|
-
expect(result.verification.verificationNotes).toContain('Path traversal blocked');
|
|
122
|
-
});
|
|
123
|
-
it('should block ../ at start of path', async () => {
|
|
124
|
-
const finding = createTestFinding({
|
|
125
|
-
location: { file: '../sibling/file.ts', line_start: 1 },
|
|
126
|
-
});
|
|
127
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
128
|
-
expect(result.verification.fileExists).toBe(false);
|
|
129
|
-
expect(result.verification.verificationNotes).toContain('Path traversal blocked');
|
|
130
|
-
});
|
|
131
|
-
it('should allow valid relative paths', async () => {
|
|
132
|
-
const finding = createTestFinding({
|
|
133
|
-
location: { file: 'safe.ts', line_start: 1 },
|
|
134
|
-
});
|
|
135
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
136
|
-
expect(result.verification.fileExists).toBe(true);
|
|
137
|
-
expect(result.verification.lineValid).toBe(true);
|
|
138
|
-
});
|
|
139
|
-
it('should allow paths with ./ prefix', async () => {
|
|
140
|
-
const finding = createTestFinding({
|
|
141
|
-
location: { file: './safe.ts', line_start: 1 },
|
|
142
|
-
});
|
|
143
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
144
|
-
expect(result.verification.fileExists).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
// =============================================================================
|
|
148
|
-
// VERIFY FINDING TESTS
|
|
149
|
-
// =============================================================================
|
|
150
|
-
describe('verifyFinding', () => {
|
|
151
|
-
beforeEach(() => {
|
|
152
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
153
|
-
createTestFile('test.ts', 'const x = 1;\nconst y = 2;\nconst z = 3;');
|
|
154
|
-
});
|
|
155
|
-
afterEach(() => {
|
|
156
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
157
|
-
});
|
|
158
|
-
it('should verify existing file without location', async () => {
|
|
159
|
-
const finding = createTestFinding(); // No location
|
|
160
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
161
|
-
expect(result.verification.fileExists).toBe(true);
|
|
162
|
-
expect(result.verification.lineValid).toBe(true);
|
|
163
|
-
});
|
|
164
|
-
it('should verify existing file with valid line', async () => {
|
|
165
|
-
const finding = createTestFinding({
|
|
166
|
-
location: { file: 'test.ts', line_start: 2 },
|
|
167
|
-
});
|
|
168
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
169
|
-
expect(result.verification.fileExists).toBe(true);
|
|
170
|
-
expect(result.verification.lineValid).toBe(true);
|
|
171
|
-
});
|
|
172
|
-
it('should detect non-existing file', async () => {
|
|
173
|
-
const finding = createTestFinding({
|
|
174
|
-
location: { file: 'nonexistent.ts', line_start: 1 },
|
|
175
|
-
});
|
|
176
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
177
|
-
expect(result.verification.fileExists).toBe(false);
|
|
178
|
-
expect(result.adjustedConfidence).toBeLessThan(finding.confidence);
|
|
179
|
-
});
|
|
180
|
-
it('should detect invalid line number', async () => {
|
|
181
|
-
const finding = createTestFinding({
|
|
182
|
-
location: { file: 'test.ts', line_start: 999 },
|
|
183
|
-
});
|
|
184
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
185
|
-
expect(result.verification.fileExists).toBe(true);
|
|
186
|
-
expect(result.verification.lineValid).toBe(false);
|
|
187
|
-
expect(result.verification.verificationNotes).toContain('exceeds file length');
|
|
188
|
-
});
|
|
189
|
-
it('should verify matching evidence', async () => {
|
|
190
|
-
const finding = createTestFinding({
|
|
191
|
-
location: { file: 'test.ts', line_start: 2 },
|
|
192
|
-
evidence: 'const y = 2',
|
|
193
|
-
});
|
|
194
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
195
|
-
expect(result.verification.codeSnippetMatches).toBe(true);
|
|
196
|
-
expect(result.adjustedConfidence).toBeGreaterThanOrEqual(finding.confidence);
|
|
197
|
-
});
|
|
198
|
-
it('should detect non-matching evidence', async () => {
|
|
199
|
-
const finding = createTestFinding({
|
|
200
|
-
location: { file: 'test.ts', line_start: 2 },
|
|
201
|
-
evidence: 'completely different code',
|
|
202
|
-
});
|
|
203
|
-
const result = await verifyFinding(finding, TEST_DIR);
|
|
204
|
-
expect(result.verification.codeSnippetMatches).toBe(false);
|
|
205
|
-
expect(result.adjustedConfidence).toBeLessThan(finding.confidence);
|
|
206
|
-
});
|
|
207
|
-
it('should use cache when provided', async () => {
|
|
208
|
-
const cache = new FileCache(TEST_DIR);
|
|
209
|
-
const finding = createTestFinding({
|
|
210
|
-
location: { file: 'test.ts', line_start: 1 },
|
|
211
|
-
});
|
|
212
|
-
// Verify multiple times with same cache
|
|
213
|
-
await verifyFinding(finding, TEST_DIR, cache);
|
|
214
|
-
await verifyFinding(finding, TEST_DIR, cache);
|
|
215
|
-
await verifyFinding(finding, TEST_DIR, cache);
|
|
216
|
-
const stats = cache.getStats();
|
|
217
|
-
expect(stats.filesLoaded).toBe(1);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for schema.ts - JSON Schema and Zod validation consistency
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import { ReviewFinding, CodeLocation, getReviewOutputJsonSchema, parseReviewOutput, } from '../schema.js';
|
|
6
|
-
// =============================================================================
|
|
7
|
-
// ZOD SCHEMA TESTS
|
|
8
|
-
// =============================================================================
|
|
9
|
-
describe('CodeLocation Schema', () => {
|
|
10
|
-
it('should require file field', () => {
|
|
11
|
-
const result = CodeLocation.safeParse({});
|
|
12
|
-
expect(result.success).toBe(false);
|
|
13
|
-
});
|
|
14
|
-
it('should accept file-only location', () => {
|
|
15
|
-
const result = CodeLocation.safeParse({ file: 'test.ts' });
|
|
16
|
-
expect(result.success).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
it('should accept full location', () => {
|
|
19
|
-
const result = CodeLocation.safeParse({
|
|
20
|
-
file: 'test.ts',
|
|
21
|
-
line_start: 10,
|
|
22
|
-
line_end: 20,
|
|
23
|
-
column_start: 0,
|
|
24
|
-
column_end: 50,
|
|
25
|
-
});
|
|
26
|
-
expect(result.success).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
it('should reject negative line numbers', () => {
|
|
29
|
-
const result = CodeLocation.safeParse({
|
|
30
|
-
file: 'test.ts',
|
|
31
|
-
line_start: -1,
|
|
32
|
-
});
|
|
33
|
-
expect(result.success).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
describe('ReviewFinding Schema', () => {
|
|
37
|
-
const validFinding = {
|
|
38
|
-
id: 'find-1',
|
|
39
|
-
category: 'security',
|
|
40
|
-
severity: 'high',
|
|
41
|
-
confidence: 0.9,
|
|
42
|
-
title: 'SQL Injection',
|
|
43
|
-
description: 'User input is not sanitized',
|
|
44
|
-
};
|
|
45
|
-
it('should accept valid finding', () => {
|
|
46
|
-
const result = ReviewFinding.safeParse(validFinding);
|
|
47
|
-
expect(result.success).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
it('should accept finding with location', () => {
|
|
50
|
-
const result = ReviewFinding.safeParse({
|
|
51
|
-
...validFinding,
|
|
52
|
-
location: { file: 'db.ts', line_start: 42 },
|
|
53
|
-
});
|
|
54
|
-
expect(result.success).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
it('should reject invalid severity', () => {
|
|
57
|
-
const result = ReviewFinding.safeParse({
|
|
58
|
-
...validFinding,
|
|
59
|
-
severity: 'extreme', // invalid
|
|
60
|
-
});
|
|
61
|
-
expect(result.success).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
it('should reject confidence > 1', () => {
|
|
64
|
-
const result = ReviewFinding.safeParse({
|
|
65
|
-
...validFinding,
|
|
66
|
-
confidence: 1.5,
|
|
67
|
-
});
|
|
68
|
-
expect(result.success).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
it('should reject confidence < 0', () => {
|
|
71
|
-
const result = ReviewFinding.safeParse({
|
|
72
|
-
...validFinding,
|
|
73
|
-
confidence: -0.1,
|
|
74
|
-
});
|
|
75
|
-
expect(result.success).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
it('should validate CWE ID format', () => {
|
|
78
|
-
const validCwe = ReviewFinding.safeParse({
|
|
79
|
-
...validFinding,
|
|
80
|
-
cwe_id: 'CWE-89',
|
|
81
|
-
});
|
|
82
|
-
expect(validCwe.success).toBe(true);
|
|
83
|
-
const invalidCwe = ReviewFinding.safeParse({
|
|
84
|
-
...validFinding,
|
|
85
|
-
cwe_id: 'CWE89', // missing dash
|
|
86
|
-
});
|
|
87
|
-
expect(invalidCwe.success).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
// =============================================================================
|
|
91
|
-
// JSON SCHEMA CONSISTENCY TESTS
|
|
92
|
-
// =============================================================================
|
|
93
|
-
describe('JSON Schema Consistency', () => {
|
|
94
|
-
it('should have file as required in location', () => {
|
|
95
|
-
const schema = getReviewOutputJsonSchema();
|
|
96
|
-
const findingSchema = schema.properties.findings.items;
|
|
97
|
-
const locationSchema = findingSchema.properties.location;
|
|
98
|
-
expect(locationSchema.required).toContain('file');
|
|
99
|
-
});
|
|
100
|
-
it('should include column fields in location', () => {
|
|
101
|
-
const schema = getReviewOutputJsonSchema();
|
|
102
|
-
const findingSchema = schema.properties.findings.items;
|
|
103
|
-
const locationSchema = findingSchema.properties.location;
|
|
104
|
-
expect(locationSchema.properties).toHaveProperty('column_start');
|
|
105
|
-
expect(locationSchema.properties).toHaveProperty('column_end');
|
|
106
|
-
});
|
|
107
|
-
it('should have all severity levels', () => {
|
|
108
|
-
const schema = getReviewOutputJsonSchema();
|
|
109
|
-
const severityEnum = schema.properties.findings.items.properties.severity.enum;
|
|
110
|
-
expect(severityEnum).toContain('critical');
|
|
111
|
-
expect(severityEnum).toContain('high');
|
|
112
|
-
expect(severityEnum).toContain('medium');
|
|
113
|
-
expect(severityEnum).toContain('low');
|
|
114
|
-
expect(severityEnum).toContain('info');
|
|
115
|
-
});
|
|
116
|
-
it('should have confidence constraints', () => {
|
|
117
|
-
const schema = getReviewOutputJsonSchema();
|
|
118
|
-
const confidenceSchema = schema.properties.findings.items.properties.confidence;
|
|
119
|
-
expect(confidenceSchema.minimum).toBe(0);
|
|
120
|
-
expect(confidenceSchema.maximum).toBe(1);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
// =============================================================================
|
|
124
|
-
// PARSE OUTPUT TESTS
|
|
125
|
-
// =============================================================================
|
|
126
|
-
describe('parseReviewOutput', () => {
|
|
127
|
-
const validOutput = {
|
|
128
|
-
reviewer: 'test',
|
|
129
|
-
findings: [],
|
|
130
|
-
agreements: [],
|
|
131
|
-
disagreements: [],
|
|
132
|
-
alternatives: [],
|
|
133
|
-
risk_assessment: {
|
|
134
|
-
overall_level: 'low',
|
|
135
|
-
score: 20,
|
|
136
|
-
summary: 'Low risk',
|
|
137
|
-
top_concerns: [],
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
it('should parse valid JSON string', () => {
|
|
141
|
-
const result = parseReviewOutput(JSON.stringify(validOutput));
|
|
142
|
-
expect(result).not.toBeNull();
|
|
143
|
-
expect(result?.reviewer).toBe('test');
|
|
144
|
-
});
|
|
145
|
-
it('should extract JSON from markdown code blocks', () => {
|
|
146
|
-
const markdown = `Here is the review:
|
|
147
|
-
|
|
148
|
-
\`\`\`json
|
|
149
|
-
${JSON.stringify(validOutput)}
|
|
150
|
-
\`\`\`
|
|
151
|
-
|
|
152
|
-
That's all.`;
|
|
153
|
-
const result = parseReviewOutput(markdown);
|
|
154
|
-
expect(result).not.toBeNull();
|
|
155
|
-
});
|
|
156
|
-
it('should return null for invalid JSON', () => {
|
|
157
|
-
const result = parseReviewOutput('not valid json');
|
|
158
|
-
expect(result).toBeNull();
|
|
159
|
-
});
|
|
160
|
-
it('should return null for incomplete output', () => {
|
|
161
|
-
const incomplete = { reviewer: 'test' }; // missing required fields
|
|
162
|
-
const result = parseReviewOutput(JSON.stringify(incomplete));
|
|
163
|
-
expect(result).toBeNull();
|
|
164
|
-
});
|
|
165
|
-
});
|