codecritique 2.1.0 → 2.1.2
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/README.md +5 -1
- package/package.json +1 -1
- package/src/codecritique.test.js +169 -0
- package/src/index.js +16 -8
- package/src/llm.js +4 -2
- package/src/llm.test.js +1 -1
- package/src/project-analyzer.js +4 -1
- package/src/project-analyzer.test.js +3 -1
- package/src/rag-analyzer.js +4 -4
package/README.md
CHANGED
|
@@ -202,6 +202,8 @@ For easier integration with non-JavaScript projects, you can use the provided sh
|
|
|
202
202
|
- Validates Node.js 24+ requirement
|
|
203
203
|
- Provides helpful error messages for missing dependencies
|
|
204
204
|
|
|
205
|
+
The wrapper forwards only `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_LOG`, `GITHUB_TOKEN`, `GH_TOKEN`, `DEBUG`, `VERBOSE`, `CI`, and `GITHUB_WORKSPACE_PATH` from `.env`; other entries, including process-control variables such as `NODE_OPTIONS` and `PATH`, are ignored. An unreadable `.env` stops the wrapper with an error. Run it only in trusted project directories: `ANTHROPIC_BASE_URL` controls where API requests are sent, while `CI` and `GITHUB_WORKSPACE_PATH` control where embedding data is stored.
|
|
206
|
+
|
|
205
207
|
## Quick Start
|
|
206
208
|
|
|
207
209
|
Follow this three-step workflow for optimal code review results:
|
|
@@ -413,7 +415,7 @@ jobs:
|
|
|
413
415
|
| `anthropic-api-key` | Anthropic API key for Claude models | **Yes** | - |
|
|
414
416
|
| `skip-label` | Label name to skip AI review | No | `ai-review-disabled` |
|
|
415
417
|
| `verbose` | Show verbose output | No | `false` |
|
|
416
|
-
| `model` | LLM model to use (e.g., `claude-sonnet-4-
|
|
418
|
+
| `model` | LLM model to use (e.g., `claude-sonnet-4-6`) | No | Auto-selected |
|
|
417
419
|
| `max-tokens` | Maximum tokens for LLM response | No | Auto-calculated |
|
|
418
420
|
| `cache-ttl` | Cache TTL for LLM prompts: "5m" (default, no extra cost) or "1h" (extended, extra cost for cache writes) | No | `5m` |
|
|
419
421
|
| `concurrency` | Concurrency for processing multiple files | No | `3` |
|
|
@@ -541,6 +543,8 @@ DEBUG=true
|
|
|
541
543
|
VERBOSE=true
|
|
542
544
|
```
|
|
543
545
|
|
|
546
|
+
Set `CODECRITIQUE_SKIP_DOTENV=1` to disable `.env` loading, as the shell wrapper does after forwarding its supported variables.
|
|
547
|
+
|
|
544
548
|
## Output Formats
|
|
545
549
|
|
|
546
550
|
CodeCritique supports three output formats:
|
package/package.json
CHANGED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { access, chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { describe, expect, it, onTestFinished } from 'vitest';
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
async function runWrapper(envFile, args = [], command = 'codecritique', extraEnv = {}, envIsDirectory = false, nodeMajorVersion = null) {
|
|
11
|
+
const directory = await mkdtemp(join(tmpdir(), 'codecritique-shell-'));
|
|
12
|
+
onTestFinished(() => rm(directory, { recursive: true, force: true }));
|
|
13
|
+
const fakeCommand = join(directory, command);
|
|
14
|
+
const envInspector = join(directory, 'inspect-entry-env.mjs');
|
|
15
|
+
|
|
16
|
+
if (envIsDirectory) {
|
|
17
|
+
await mkdir(join(directory, '.env'));
|
|
18
|
+
}
|
|
19
|
+
else if (envFile !== null) {
|
|
20
|
+
await writeFile(join(directory, '.env'), envFile);
|
|
21
|
+
}
|
|
22
|
+
await writeFile(
|
|
23
|
+
envInspector,
|
|
24
|
+
`process.argv = ['node'];
|
|
25
|
+
process.exit = code => { throw new Error(\`exit \${code}\`); };
|
|
26
|
+
try { await import(${JSON.stringify(new URL('./index.js', import.meta.url).href)}); } catch {}
|
|
27
|
+
console.log(\`inspected_node_options=\${process.env.NODE_OPTIONS || ''}\`);
|
|
28
|
+
`
|
|
29
|
+
);
|
|
30
|
+
await writeFile(
|
|
31
|
+
fakeCommand,
|
|
32
|
+
`#!/bin/bash
|
|
33
|
+
[ "$1" = "--terminate" ] && kill -TERM $$
|
|
34
|
+
[ "$1" = "--inspect-entry" ] && exec node "${envInspector}"
|
|
35
|
+
[ "$1" = "--inspect-entry-with-dotenv" ] && unset CODECRITIQUE_SKIP_DOTENV && exec node "${envInspector}"
|
|
36
|
+
/usr/bin/env
|
|
37
|
+
printf "arg=%s\\n" "$@"
|
|
38
|
+
`
|
|
39
|
+
);
|
|
40
|
+
await chmod(fakeCommand, 0o755);
|
|
41
|
+
if (nodeMajorVersion === null) {
|
|
42
|
+
await symlink(process.execPath, join(directory, 'node'));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await writeFile(
|
|
46
|
+
join(directory, 'node'),
|
|
47
|
+
`#!/bin/bash\nif [ "$1" = "-p" ]; then echo ${nodeMajorVersion}; exit; fi\nif [ "$1" = "--version" ]; then echo v${nodeMajorVersion}.0.0; exit; fi\nexec "${process.execPath}" "$@"\n`
|
|
48
|
+
);
|
|
49
|
+
await chmod(join(directory, 'node'), 0o755);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = await execFileAsync('/bin/bash', [join(import.meta.dirname, 'codecritique.sh'), ...args], {
|
|
53
|
+
cwd: directory,
|
|
54
|
+
env: { PATH: directory, ...extraEnv },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return { ...result, directory };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('codecritique.sh', () => {
|
|
61
|
+
it.each([
|
|
62
|
+
[20, 'Error: CodeCritique requires Node.js 24 or newer (found v20.0.0).\n'],
|
|
63
|
+
['not-a-version', 'Error: Unable to determine the Node.js major version (received: not-a-version).\n'],
|
|
64
|
+
])('rejects unsupported Node version output: %s', async (nodeVersion, stderr) => {
|
|
65
|
+
await expect(runWrapper(null, [], 'codecritique', {}, false, nodeVersion)).rejects.toMatchObject({
|
|
66
|
+
code: 1,
|
|
67
|
+
stderr,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it.each([
|
|
72
|
+
['unquoted spaces and globs', 'ANTHROPIC_API_KEY=value with spaces *\n', 'value with spaces *'],
|
|
73
|
+
['literal JSON quotes', 'ANTHROPIC_API_KEY={"token":"a*b c"}\n', '{"token":"a*b c"}'],
|
|
74
|
+
])('loads %s without corruption', async (_label, envFile, expected) => {
|
|
75
|
+
const { stdout } = await runWrapper(envFile, ['--flag', 'value with spaces']);
|
|
76
|
+
|
|
77
|
+
expect(stdout).toContain(`ANTHROPIC_API_KEY=${expected}`);
|
|
78
|
+
expect(stdout).toContain('arg=--flag');
|
|
79
|
+
expect(stdout).toContain('arg=value with spaces');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('does not execute .env contents', async () => {
|
|
83
|
+
const { directory, stdout } = await runWrapper(
|
|
84
|
+
'ANTHROPIC_API_KEY=test\nMALICIOUS=$(touch executed)\nNODE_OPTIONS=--require=./does-not-exist.cjs\n'
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(stdout).toContain('ANTHROPIC_API_KEY=test');
|
|
88
|
+
expect(stdout).not.toContain('NODE_OPTIONS=');
|
|
89
|
+
await expect(access(join(directory, 'executed'))).rejects.toThrow();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('prevents the real CLI entry point from reloading filtered .env values', async () => {
|
|
93
|
+
const { stdout } = await runWrapper('ANTHROPIC_API_KEY=test\nNODE_OPTIONS=--require=./does-not-exist.cjs\n', ['--inspect-entry']);
|
|
94
|
+
|
|
95
|
+
expect(stdout).toContain('inspected_node_options=\n');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('loads .env at the real CLI entry point when the wrapper sentinel is absent', async () => {
|
|
99
|
+
const { stdout } = await runWrapper('ANTHROPIC_API_KEY=test\nNODE_OPTIONS=loaded-by-dotenv\n', ['--inspect-entry-with-dotenv']);
|
|
100
|
+
|
|
101
|
+
expect(stdout).toContain('inspected_node_options=loaded-by-dotenv\n');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('forwards every supported .env setting', async () => {
|
|
105
|
+
const supportedEnv = {
|
|
106
|
+
ANTHROPIC_API_KEY: 'test',
|
|
107
|
+
ANTHROPIC_BASE_URL: 'https://api.example.test',
|
|
108
|
+
ANTHROPIC_LOG: 'debug',
|
|
109
|
+
GITHUB_TOKEN: 'github-token',
|
|
110
|
+
GH_TOKEN: 'gh-token',
|
|
111
|
+
DEBUG: '1',
|
|
112
|
+
VERBOSE: 'true',
|
|
113
|
+
CI: 'true',
|
|
114
|
+
GITHUB_WORKSPACE_PATH: '/workspace',
|
|
115
|
+
};
|
|
116
|
+
const envFile = Object.entries(supportedEnv)
|
|
117
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
118
|
+
.join('\n');
|
|
119
|
+
const { stdout } = await runWrapper(envFile);
|
|
120
|
+
|
|
121
|
+
for (const [key, value] of Object.entries(supportedEnv)) {
|
|
122
|
+
expect(stdout).toContain(`${key}=${value}`);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('reports null bytes in supported values without an internal stack', async () => {
|
|
127
|
+
await expect(runWrapper('ANTHROPIC_API_KEY=test\0value\n')).rejects.toMatchObject({
|
|
128
|
+
stderr: 'Unable to load .env: ANTHROPIC_API_KEY contains a null byte\n',
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('preserves an inherited API key when .env is absent', async () => {
|
|
133
|
+
const { stdout } = await runWrapper(null, [], 'codecritique', { ANTHROPIC_API_KEY: 'inherited' });
|
|
134
|
+
|
|
135
|
+
expect(stdout).toContain('ANTHROPIC_API_KEY=inherited');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('keeps the original .env-over-inherited-value precedence', async () => {
|
|
139
|
+
const { stdout } = await runWrapper('ANTHROPIC_API_KEY=from-file\n', [], 'codecritique', {
|
|
140
|
+
ANTHROPIC_API_KEY: 'from-parent',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(stdout).toContain('ANTHROPIC_API_KEY=from-file');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it.each([null, 'ANTHROPIC_AUTH_TOKEN=token\n'])('warns when no supported Anthropic credentials are configured', async (envFile) => {
|
|
147
|
+
const { stderr } = await runWrapper(envFile);
|
|
148
|
+
|
|
149
|
+
expect(stderr).toContain('Warning: ANTHROPIC_API_KEY is not set.');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('reports unreadable .env paths without an internal stack', async () => {
|
|
153
|
+
await expect(runWrapper(null, [], 'codecritique', {}, true)).rejects.toMatchObject({
|
|
154
|
+
stderr: expect.stringMatching(/^Unable to load \.env: .+\n$/),
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('falls back to npx when codecritique is not installed globally', async () => {
|
|
159
|
+
const { stdout } = await runWrapper('ANTHROPIC_API_KEY=test\n', ['--version'], 'npx');
|
|
160
|
+
|
|
161
|
+
expect(stdout).toContain('codecritique not found globally, trying with npx...');
|
|
162
|
+
expect(stdout).toContain('arg=codecritique');
|
|
163
|
+
expect(stdout).toContain('arg=--version');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('preserves signal-derived child exit codes', async () => {
|
|
167
|
+
await expect(runWrapper('ANTHROPIC_API_KEY=test\n', ['--terminate'])).rejects.toMatchObject({ code: 143 });
|
|
168
|
+
});
|
|
169
|
+
});
|
package/src/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import { Spinner } from 'cli-spinner';
|
|
16
16
|
import { program } from 'commander';
|
|
17
17
|
import { glob } from 'glob';
|
|
18
|
-
import { getDefaultEmbeddingsSystem } from './embeddings/factory.js';
|
|
18
|
+
import { getDefaultEmbeddingsSystem, getDefaultEmbeddingsSystemIfInitialized } from './embeddings/factory.js';
|
|
19
19
|
import { PRHistoryAnalyzer } from './pr-history/analyzer.js';
|
|
20
20
|
import {
|
|
21
21
|
displayAnalysisResults,
|
|
@@ -34,8 +34,12 @@ import { diagnosticLog, isDebugEnabled, isVerboseEnabled, verboseLog } from './u
|
|
|
34
34
|
import { collectPRLevelFindings, getFileLevelIssueCount, hasPRLevelFindings } from './utils/review-results.js';
|
|
35
35
|
import { configureCleanStdoutForDataOutput, isBrokenStdoutPipeError, writeStdout } from './utils/stdout.js';
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
const embeddingsSystem =
|
|
37
|
+
async function cleanupEmbeddingsSystemIfInitialized() {
|
|
38
|
+
const embeddingsSystem = getDefaultEmbeddingsSystemIfInitialized();
|
|
39
|
+
if (embeddingsSystem) {
|
|
40
|
+
await embeddingsSystem.cleanup();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
39
43
|
|
|
40
44
|
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
41
45
|
|
|
@@ -58,7 +62,7 @@ program
|
|
|
58
62
|
.option('--output-file <file>', 'Save output to file (useful with --output json)')
|
|
59
63
|
.option('--no-color', 'Disable colored output')
|
|
60
64
|
.option('--verbose', 'Show verbose output')
|
|
61
|
-
.option('--model <model>', 'LLM model to use (e.g., claude-sonnet-4-
|
|
65
|
+
.option('--model <model>', 'LLM model to use (e.g., claude-sonnet-4-6)')
|
|
62
66
|
.option('--temperature <number>', 'LLM temperature', parseFloat, 0.2)
|
|
63
67
|
.option('--max-tokens <number>', 'LLM max tokens', parseInt, 8192)
|
|
64
68
|
.option('--cache-ttl <ttl>', 'Cache TTL for LLM prompts: "5m" (default, no extra cost) or "1h" (extended, extra cost for writes)', '5m')
|
|
@@ -103,6 +107,7 @@ program
|
|
|
103
107
|
.command('embeddings:clear-all')
|
|
104
108
|
.description('Clear ALL stored embeddings (affects all projects - use with caution)')
|
|
105
109
|
.action(async () => {
|
|
110
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
106
111
|
try {
|
|
107
112
|
console.log(chalk.red('WARNING: This will clear embeddings for ALL projects on this machine!'));
|
|
108
113
|
console.log(chalk.cyan('Clearing all embeddings...'));
|
|
@@ -227,7 +232,7 @@ process.on('SIGINT', async () => {
|
|
|
227
232
|
|
|
228
233
|
try {
|
|
229
234
|
diagnosticLog(chalk.cyan('SIGINT handler: Attempting embeddingsSystem.cleanup()...'));
|
|
230
|
-
await
|
|
235
|
+
await cleanupEmbeddingsSystemIfInitialized();
|
|
231
236
|
diagnosticLog(chalk.green('embeddingsSystem.cleanup() completed.'));
|
|
232
237
|
clearTimeout(forceExitTimeout); // Cleanup finished, clear the timeout
|
|
233
238
|
diagnosticLog(chalk.cyan('SIGINT handler: Exiting normally (code 0).'));
|
|
@@ -251,7 +256,7 @@ process.on('SIGTERM', async () => {
|
|
|
251
256
|
|
|
252
257
|
try {
|
|
253
258
|
diagnosticLog(chalk.cyan('SIGTERM handler: Attempting embeddingsSystem.cleanup()...'));
|
|
254
|
-
await
|
|
259
|
+
await cleanupEmbeddingsSystemIfInitialized();
|
|
255
260
|
diagnosticLog(chalk.green('embeddingsSystem.cleanup() completed.'));
|
|
256
261
|
clearTimeout(forceExitTimeout); // Cleanup finished, clear the timeout
|
|
257
262
|
diagnosticLog(chalk.cyan('SIGTERM handler: Exiting normally (code 0).'));
|
|
@@ -468,7 +473,7 @@ async function runCodeReview(options) {
|
|
|
468
473
|
// Clean up resources
|
|
469
474
|
diagnosticLog(chalk.cyan('Cleaning up resources...'));
|
|
470
475
|
try {
|
|
471
|
-
await
|
|
476
|
+
await cleanupEmbeddingsSystemIfInitialized();
|
|
472
477
|
await cleanupClassifier();
|
|
473
478
|
diagnosticLog(chalk.green('All resources cleaned up successfully'));
|
|
474
479
|
}
|
|
@@ -497,7 +502,7 @@ async function runCodeReview(options) {
|
|
|
497
502
|
|
|
498
503
|
// Clean up resources even on error
|
|
499
504
|
try {
|
|
500
|
-
await
|
|
505
|
+
await cleanupEmbeddingsSystemIfInitialized();
|
|
501
506
|
await cleanupClassifier();
|
|
502
507
|
diagnosticLog(chalk.green('All resources cleaned up successfully'));
|
|
503
508
|
}
|
|
@@ -516,6 +521,7 @@ async function runCodeReview(options) {
|
|
|
516
521
|
* @param {Object} options - Command options
|
|
517
522
|
*/
|
|
518
523
|
async function generateEmbeddings(options) {
|
|
524
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
519
525
|
try {
|
|
520
526
|
console.log(chalk.bold.blue('AI Code Review - Generating embeddings...'));
|
|
521
527
|
const startTime = Date.now();
|
|
@@ -717,6 +723,7 @@ async function generateEmbeddings(options) {
|
|
|
717
723
|
* Clear stored embeddings for the current project
|
|
718
724
|
*/
|
|
719
725
|
async function clearEmbeddings(options) {
|
|
726
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
720
727
|
try {
|
|
721
728
|
// Determine the working directory for project separation
|
|
722
729
|
// If --directory is specified, use that as the project directory
|
|
@@ -751,6 +758,7 @@ async function clearEmbeddings(options) {
|
|
|
751
758
|
* Show statistics about stored embeddings
|
|
752
759
|
*/
|
|
753
760
|
async function showEmbeddingStats(options) {
|
|
761
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
754
762
|
try {
|
|
755
763
|
// Determine the working directory for project separation
|
|
756
764
|
// If --directory is specified, use that as the project directory
|
package/src/llm.js
CHANGED
|
@@ -18,7 +18,9 @@ import dotenv from 'dotenv';
|
|
|
18
18
|
import { verboseLog } from './utils/logging.js';
|
|
19
19
|
|
|
20
20
|
// Load env variables if present; do not enforce key at import time
|
|
21
|
-
|
|
21
|
+
if (process.env.CODECRITIQUE_SKIP_DOTENV !== '1') {
|
|
22
|
+
dotenv.config({ quiet: true });
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
let anthropic = null;
|
|
24
26
|
|
|
@@ -39,7 +41,7 @@ function getAnthropicClient() {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Default model
|
|
42
|
-
const DEFAULT_MODEL = 'claude-sonnet-4-
|
|
44
|
+
const DEFAULT_MODEL = 'claude-sonnet-4-6';
|
|
43
45
|
|
|
44
46
|
// Maximum tokens for response
|
|
45
47
|
const MAX_TOKENS = 4096;
|
package/src/llm.test.js
CHANGED
package/src/project-analyzer.js
CHANGED
|
@@ -17,6 +17,7 @@ import { verboseLog } from './utils/logging.js';
|
|
|
17
17
|
import { escapeSqlString } from './utils/string-utils.js';
|
|
18
18
|
|
|
19
19
|
const TERM_SEARCH_CONTENT_CANDIDATE_LIMIT = 500;
|
|
20
|
+
const PROJECT_ANALYSIS_MODEL = 'claude-haiku-4-5';
|
|
20
21
|
|
|
21
22
|
// Consolidated file classification configuration
|
|
22
23
|
const FILE_PATTERNS = {
|
|
@@ -586,6 +587,7 @@ Select files following the criteria in the system instructions.`;
|
|
|
586
587
|
};
|
|
587
588
|
|
|
588
589
|
const response = await this.llm.sendPromptToClaude(prompt, {
|
|
590
|
+
model: PROJECT_ANALYSIS_MODEL,
|
|
589
591
|
system: FILE_SELECTION_SYSTEM_PROMPT,
|
|
590
592
|
temperature: 0.1,
|
|
591
593
|
maxTokens: 1000,
|
|
@@ -878,6 +880,7 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
878
880
|
};
|
|
879
881
|
|
|
880
882
|
const response = await this.llm.sendPromptToClaude(prompt, {
|
|
883
|
+
model: PROJECT_ANALYSIS_MODEL,
|
|
881
884
|
system: PROJECT_SUMMARY_SYSTEM_PROMPT,
|
|
882
885
|
temperature: 0.1,
|
|
883
886
|
maxTokens: 4000,
|
|
@@ -886,7 +889,7 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
886
889
|
|
|
887
890
|
const summary = response.json;
|
|
888
891
|
if (summary) {
|
|
889
|
-
// Validate and ensure required fields exist
|
|
892
|
+
// Validate and ensure required fields exist across model responses
|
|
890
893
|
const validatedSummary = this.validateProjectSummary(summary);
|
|
891
894
|
|
|
892
895
|
// Add metadata
|
|
@@ -556,7 +556,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
556
556
|
|
|
557
557
|
const result = await analyzer.selectFinalKeyFiles(candidates, mockProjectPath);
|
|
558
558
|
|
|
559
|
-
expect(llm.sendPromptToClaude).
|
|
559
|
+
expect(llm.sendPromptToClaude).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ model: 'claude-haiku-4-5' }));
|
|
560
560
|
expect(result.length).toBe(1);
|
|
561
561
|
expect(result[0].relativePath).toBe('package.json');
|
|
562
562
|
});
|
|
@@ -668,9 +668,11 @@ describe('ProjectAnalyzer', () => {
|
|
|
668
668
|
content: JSON.stringify(mockSummary),
|
|
669
669
|
json: mockSummary,
|
|
670
670
|
});
|
|
671
|
+
analyzer.llm = llm;
|
|
671
672
|
|
|
672
673
|
const result = await analyzer.generateProjectSummary(mockKeyFiles, mockProjectPath);
|
|
673
674
|
|
|
675
|
+
expect(llm.sendPromptToClaude).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ model: 'claude-haiku-4-5' }));
|
|
674
676
|
expect(result.projectName).toBe('test-project');
|
|
675
677
|
expect(result.analysisDate).toBeDefined();
|
|
676
678
|
expect(result.projectPath).toBe(mockProjectPath);
|
package/src/rag-analyzer.js
CHANGED
|
@@ -37,9 +37,6 @@ const DEFAULT_TRUNCATE_LINES = 300;
|
|
|
37
37
|
const GUIDELINE_TRUNCATE_LINES = 400;
|
|
38
38
|
const MAX_PR_COMMENTS_FOR_CONTEXT = 15;
|
|
39
39
|
|
|
40
|
-
// Create embeddings system instance
|
|
41
|
-
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
42
|
-
|
|
43
40
|
// Track if semantic similarity has been initialized
|
|
44
41
|
let semanticSimilarityInitialized = false;
|
|
45
42
|
|
|
@@ -234,6 +231,7 @@ ${ex.content}
|
|
|
234
231
|
*/
|
|
235
232
|
async function getProjectSummary(projectPath, options = {}) {
|
|
236
233
|
const resolvedPath = path.resolve(projectPath);
|
|
234
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
237
235
|
|
|
238
236
|
try {
|
|
239
237
|
// Retrieve from database
|
|
@@ -766,7 +764,7 @@ function prepareContextForLLM(filePath, content, language, finalCodeExamples, fi
|
|
|
766
764
|
async function callLLMForAnalysis(context, options = {}) {
|
|
767
765
|
try {
|
|
768
766
|
let prompt;
|
|
769
|
-
const model = options.model || 'claude-sonnet-4-
|
|
767
|
+
const model = options.model || 'claude-sonnet-4-6';
|
|
770
768
|
const maxTokens = options.maxTokens || 8192; // Default to a safe limit
|
|
771
769
|
|
|
772
770
|
if (options.isHolisticPRReview) {
|
|
@@ -1929,6 +1927,7 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1929
1927
|
* @returns {Promise<Object>} An object containing the gathered context.
|
|
1930
1928
|
*/
|
|
1931
1929
|
async function getContextForFile(filePath, content, options = {}) {
|
|
1930
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
1932
1931
|
const RELEVANT_CHUNK_THRESHOLD = 0.1;
|
|
1933
1932
|
const W_H1_SIM = 0.2;
|
|
1934
1933
|
const W_DOC_CONTEXT_MATCH = 0.6;
|
|
@@ -2283,6 +2282,7 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
2283
2282
|
}
|
|
2284
2283
|
|
|
2285
2284
|
async function gatherUnifiedContextForPR(prFiles, options = {}) {
|
|
2285
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
2286
2286
|
const allProcessedContext = {
|
|
2287
2287
|
codeExamples: new Map(),
|
|
2288
2288
|
guidelines: new Map(),
|