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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-reviewer",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "MCP server for Claude Code - Get second-opinion feedback from Codex/Gemini CLIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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,4 +0,0 @@
1
- /**
2
- * Tests for pipeline.ts - verification and security features
3
- */
4
- export {};
@@ -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,4 +0,0 @@
1
- /**
2
- * Tests for schema.ts - JSON Schema and Zod validation consistency
3
- */
4
- export {};
@@ -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
- });