codecritique 1.0.0 → 1.1.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.
Files changed (40) hide show
  1. package/README.md +82 -114
  2. package/package.json +10 -9
  3. package/src/content-retrieval.test.js +775 -0
  4. package/src/custom-documents.test.js +440 -0
  5. package/src/feedback-loader.test.js +529 -0
  6. package/src/llm.test.js +256 -0
  7. package/src/project-analyzer.test.js +747 -0
  8. package/src/rag-analyzer.js +12 -0
  9. package/src/rag-analyzer.test.js +1109 -0
  10. package/src/rag-review.test.js +317 -0
  11. package/src/setupTests.js +131 -0
  12. package/src/zero-shot-classifier-open.test.js +278 -0
  13. package/src/embeddings/cache-manager.js +0 -364
  14. package/src/embeddings/constants.js +0 -40
  15. package/src/embeddings/database.js +0 -921
  16. package/src/embeddings/errors.js +0 -208
  17. package/src/embeddings/factory.js +0 -447
  18. package/src/embeddings/file-processor.js +0 -851
  19. package/src/embeddings/model-manager.js +0 -337
  20. package/src/embeddings/similarity-calculator.js +0 -97
  21. package/src/embeddings/types.js +0 -113
  22. package/src/pr-history/analyzer.js +0 -579
  23. package/src/pr-history/bot-detector.js +0 -123
  24. package/src/pr-history/cli-utils.js +0 -204
  25. package/src/pr-history/comment-processor.js +0 -549
  26. package/src/pr-history/database.js +0 -819
  27. package/src/pr-history/github-client.js +0 -629
  28. package/src/technology-keywords.json +0 -753
  29. package/src/utils/command.js +0 -48
  30. package/src/utils/constants.js +0 -263
  31. package/src/utils/context-inference.js +0 -364
  32. package/src/utils/document-detection.js +0 -105
  33. package/src/utils/file-validation.js +0 -271
  34. package/src/utils/git.js +0 -232
  35. package/src/utils/language-detection.js +0 -170
  36. package/src/utils/logging.js +0 -24
  37. package/src/utils/markdown.js +0 -132
  38. package/src/utils/mobilebert-tokenizer.js +0 -141
  39. package/src/utils/pr-chunking.js +0 -276
  40. package/src/utils/string-utils.js +0 -28
@@ -0,0 +1,256 @@
1
+ import { sendPromptToClaude } from './llm.js';
2
+
3
+ // Create hoisted mock for the Anthropic SDK
4
+ const mockMessagesCreate = vi.hoisted(() => vi.fn());
5
+
6
+ vi.mock('@anthropic-ai/sdk', () => ({
7
+ Anthropic: class MockAnthropic {
8
+ messages = {
9
+ create: mockMessagesCreate,
10
+ };
11
+ },
12
+ }));
13
+
14
+ describe('sendPromptToClaude', () => {
15
+ beforeEach(() => {
16
+ mockConsoleSelective('log', 'error');
17
+
18
+ // Set up mock API key
19
+ process.env.ANTHROPIC_API_KEY = 'test-api-key';
20
+ });
21
+
22
+ afterEach(() => {
23
+ delete process.env.ANTHROPIC_API_KEY;
24
+ });
25
+
26
+ describe('basic text response', () => {
27
+ it('should send prompt and return text response', async () => {
28
+ mockMessagesCreate.mockResolvedValue({
29
+ content: [{ type: 'text', text: 'This is the response' }],
30
+ model: 'claude-sonnet-4-5',
31
+ usage: { input_tokens: 100, output_tokens: 50 },
32
+ });
33
+
34
+ const result = await sendPromptToClaude('Review this code');
35
+
36
+ expect(result.content).toBe('This is the response');
37
+ expect(result.model).toBe('claude-sonnet-4-5');
38
+ expect(result.usage).toEqual({ input_tokens: 100, output_tokens: 50 });
39
+ });
40
+
41
+ it('should use default model and settings', async () => {
42
+ mockMessagesCreate.mockResolvedValue({
43
+ content: [{ type: 'text', text: 'Response' }],
44
+ model: 'claude-sonnet-4-5',
45
+ usage: {},
46
+ });
47
+
48
+ await sendPromptToClaude('Test prompt');
49
+
50
+ expect(mockMessagesCreate).toHaveBeenCalledWith(
51
+ expect.objectContaining({
52
+ model: 'claude-sonnet-4-5',
53
+ max_tokens: 4096,
54
+ temperature: 0.7,
55
+ })
56
+ );
57
+ });
58
+
59
+ it('should use custom options', async () => {
60
+ mockMessagesCreate.mockResolvedValue({
61
+ content: [{ type: 'text', text: 'Response' }],
62
+ model: 'claude-3-opus',
63
+ usage: {},
64
+ });
65
+
66
+ await sendPromptToClaude('Test prompt', {
67
+ model: 'claude-3-opus',
68
+ maxTokens: 8192,
69
+ temperature: 0.5,
70
+ system: 'Custom system prompt',
71
+ });
72
+
73
+ expect(mockMessagesCreate).toHaveBeenCalledWith(
74
+ expect.objectContaining({
75
+ model: 'claude-3-opus',
76
+ max_tokens: 8192,
77
+ temperature: 0.5,
78
+ system: 'Custom system prompt',
79
+ })
80
+ );
81
+ });
82
+ });
83
+
84
+ describe('structured JSON response with tool calling', () => {
85
+ const jsonSchema = {
86
+ type: 'object',
87
+ properties: {
88
+ issues: {
89
+ type: 'array',
90
+ items: {
91
+ type: 'object',
92
+ properties: {
93
+ severity: { type: 'string' },
94
+ description: { type: 'string' },
95
+ },
96
+ },
97
+ },
98
+ summary: { type: 'string' },
99
+ },
100
+ };
101
+
102
+ it('should use tool calling for structured output', async () => {
103
+ const structuredData = {
104
+ issues: [{ severity: 'high', description: 'Missing error handling' }],
105
+ summary: 'Code needs improvement',
106
+ };
107
+
108
+ mockMessagesCreate.mockResolvedValue({
109
+ content: [
110
+ {
111
+ type: 'tool_use',
112
+ name: 'return_json',
113
+ input: structuredData,
114
+ },
115
+ ],
116
+ model: 'claude-sonnet-4-5',
117
+ usage: { input_tokens: 100, output_tokens: 50 },
118
+ });
119
+
120
+ const result = await sendPromptToClaude('Review this code', {
121
+ jsonSchema,
122
+ });
123
+
124
+ expect(result.json).toEqual(structuredData);
125
+ expect(result.content).toBe(JSON.stringify(structuredData, null, 2));
126
+ });
127
+
128
+ it('should include tool definition in request', async () => {
129
+ mockMessagesCreate.mockResolvedValue({
130
+ content: [{ type: 'tool_use', name: 'return_json', input: {} }],
131
+ model: 'claude-sonnet-4-5',
132
+ usage: {},
133
+ });
134
+
135
+ await sendPromptToClaude('Test', { jsonSchema });
136
+
137
+ expect(mockMessagesCreate).toHaveBeenCalledWith(
138
+ expect.objectContaining({
139
+ tools: [
140
+ expect.objectContaining({
141
+ name: 'return_json',
142
+ input_schema: jsonSchema,
143
+ }),
144
+ ],
145
+ tool_choice: { type: 'tool', name: 'return_json' },
146
+ })
147
+ );
148
+ });
149
+
150
+ it('should throw error if no tool_use block in response', async () => {
151
+ mockMessagesCreate.mockResolvedValue({
152
+ content: [{ type: 'text', text: 'Unexpected text response' }],
153
+ model: 'claude-sonnet-4-5',
154
+ usage: {},
155
+ });
156
+
157
+ await expect(sendPromptToClaude('Test', { jsonSchema })).rejects.toThrow('No structured output received from Claude');
158
+ });
159
+
160
+ it('should throw error if tool_use block has wrong name', async () => {
161
+ mockMessagesCreate.mockResolvedValue({
162
+ content: [{ type: 'tool_use', name: 'wrong_tool', input: {} }],
163
+ model: 'claude-sonnet-4-5',
164
+ usage: {},
165
+ });
166
+
167
+ await expect(sendPromptToClaude('Test', { jsonSchema })).rejects.toThrow('No structured output received from Claude');
168
+ });
169
+ });
170
+
171
+ describe('error handling', () => {
172
+ it('should throw error when API key is missing', async () => {
173
+ // Reset modules to clear cached anthropic client
174
+ vi.resetModules();
175
+ delete process.env.ANTHROPIC_API_KEY;
176
+
177
+ // Re-import the module after resetting (dynamic import needed to test module-level caching)
178
+ // eslint-disable-next-line no-restricted-syntax
179
+ const { sendPromptToClaude: freshSendPrompt } = await import('./llm.js');
180
+
181
+ await expect(freshSendPrompt('Test')).rejects.toThrow('ANTHROPIC_API_KEY is required');
182
+ });
183
+
184
+ it('should propagate API errors', async () => {
185
+ mockMessagesCreate.mockRejectedValue(new Error('Rate limit exceeded'));
186
+
187
+ await expect(sendPromptToClaude('Test')).rejects.toThrow('Rate limit exceeded');
188
+ });
189
+
190
+ it('should log error before throwing', async () => {
191
+ mockMessagesCreate.mockRejectedValue(new Error('API Error'));
192
+
193
+ await expect(sendPromptToClaude('Test')).rejects.toThrow();
194
+
195
+ expect(console.error).toHaveBeenCalled();
196
+ });
197
+ });
198
+
199
+ describe('system prompt', () => {
200
+ it('should use default system prompt when not provided', async () => {
201
+ mockMessagesCreate.mockResolvedValue({
202
+ content: [{ type: 'text', text: 'Response' }],
203
+ model: 'claude-sonnet-4-5',
204
+ usage: {},
205
+ });
206
+
207
+ await sendPromptToClaude('Test');
208
+
209
+ expect(mockMessagesCreate).toHaveBeenCalledWith(
210
+ expect.objectContaining({
211
+ system: expect.stringContaining('expert code reviewer'),
212
+ })
213
+ );
214
+ });
215
+
216
+ it('should use custom system prompt when provided', async () => {
217
+ mockMessagesCreate.mockResolvedValue({
218
+ content: [{ type: 'text', text: 'Response' }],
219
+ model: 'claude-sonnet-4-5',
220
+ usage: {},
221
+ });
222
+
223
+ const customSystem = 'You are a security expert';
224
+ await sendPromptToClaude('Test', { system: customSystem });
225
+
226
+ expect(mockMessagesCreate).toHaveBeenCalledWith(
227
+ expect.objectContaining({
228
+ system: customSystem,
229
+ })
230
+ );
231
+ });
232
+ });
233
+
234
+ describe('message format', () => {
235
+ it('should send prompt as user message', async () => {
236
+ mockMessagesCreate.mockResolvedValue({
237
+ content: [{ type: 'text', text: 'Response' }],
238
+ model: 'claude-sonnet-4-5',
239
+ usage: {},
240
+ });
241
+
242
+ await sendPromptToClaude('Review this code:\n```js\nconst x = 1;\n```');
243
+
244
+ expect(mockMessagesCreate).toHaveBeenCalledWith(
245
+ expect.objectContaining({
246
+ messages: [
247
+ {
248
+ role: 'user',
249
+ content: 'Review this code:\n```js\nconst x = 1;\n```',
250
+ },
251
+ ],
252
+ })
253
+ );
254
+ });
255
+ });
256
+ });