codecritique 1.1.1 → 1.2.1

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": "codecritique",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "AI-powered code review tool for any programming language",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -54,6 +54,7 @@ program
54
54
  .option('--model <model>', 'LLM model to use (e.g., claude-sonnet-4-5)')
55
55
  .option('--temperature <number>', 'LLM temperature', parseFloat, 0.2)
56
56
  .option('--max-tokens <number>', 'LLM max tokens', parseInt, 8192)
57
+ .option('--cache-ttl <ttl>', 'Cache TTL for LLM prompts: "5m" (default, no extra cost) or "1h" (extended, extra cost for writes)', '5m')
57
58
  .option('--similarity-threshold <number>', 'Threshold for finding similar code examples', parseFloat, 0.6)
58
59
  .option('--max-examples <number>', 'Max similar code examples to use', parseInt, 5)
59
60
  .option('--concurrency <number>', 'Concurrency for processing multiple files', parseInt, 3)
@@ -329,6 +330,7 @@ async function runCodeReview(options) {
329
330
  model: options.model,
330
331
  temperature: options.temperature,
331
332
  maxTokens: options.maxTokens,
333
+ cacheTtl: options.cacheTtl,
332
334
  similarityThreshold: options.similarityThreshold,
333
335
  maxExamples: options.maxExamples,
334
336
  concurrency: options.concurrency,
@@ -418,19 +420,18 @@ async function runCodeReview(options) {
418
420
  outputFn(reviewResult.results, options);
419
421
  console.log(chalk.bold.green(`\nAnalysis complete for ${operationDescription}! (${duration}s)`));
420
422
  } else {
421
- console.log(chalk.yellow('No results to display. Review result structure:'));
422
- console.log(chalk.yellow('reviewResult.results exists?'), reviewResult.results ? 'Yes' : 'No');
423
- if (reviewResult.results) {
424
- console.log(chalk.yellow('reviewResult.results type:'), typeof reviewResult.results);
425
- console.log(chalk.yellow('reviewResult.results is array?'), Array.isArray(reviewResult.results));
426
- if (!Array.isArray(reviewResult.results)) {
427
- console.log(
428
- chalk.yellow('reviewResult.results content:'),
429
- JSON.stringify(reviewResult.results, null, 2).substring(0, 500) + '...'
430
- );
431
- }
423
+ // No results to display (e.g., all files were excluded/skipped)
424
+ const message = reviewResult.message || 'All files were excluded from review (e.g., config files, lock files).';
425
+ console.log(chalk.yellow(message));
426
+
427
+ // Still output empty results if outputFile is specified (for CI/CD pipelines)
428
+ if (options.outputFile) {
429
+ const outputFn = options.output === 'json' ? outputJson : options.output === 'markdown' ? outputMarkdown : outputText;
430
+ outputFn([], options);
431
+ console.log(chalk.yellow(`Empty results written to: ${options.outputFile}`));
432
432
  }
433
- console.log(chalk.yellow(reviewResult.message || 'Review completed, but no results to display.'));
433
+
434
+ console.log(chalk.bold.yellow(`\nReview complete for ${operationDescription} - no reviewable files found (${duration}s)`));
434
435
  }
435
436
  } else {
436
437
  console.error(chalk.red('\nCode review process failed.'));
package/src/llm.js CHANGED
@@ -5,6 +5,11 @@
5
5
  * for code analysis and review. Enhanced to leverage project-specific patterns and
6
6
  * feedback from PR reviews for more context-aware recommendations.
7
7
  * Currently supports Anthropic's Claude Sonnet 4.
8
+ *
9
+ * Prompt Caching:
10
+ * This module uses Anthropic's prompt caching feature for cost optimization.
11
+ * Static content in the system message is cached and reused across multiple
12
+ * requests, reducing input token costs by 75%.
8
13
  */
9
14
 
10
15
  import { Anthropic } from '@anthropic-ai/sdk';
@@ -15,6 +20,7 @@ import dotenv from 'dotenv';
15
20
  dotenv.config();
16
21
 
17
22
  let anthropic = null;
23
+
18
24
  /**
19
25
  * Get the Anthropic client
20
26
  * @returns {Anthropic} The Anthropic client
@@ -36,80 +42,93 @@ const DEFAULT_MODEL = 'claude-sonnet-4-5';
36
42
  const MAX_TOKENS = 4096;
37
43
 
38
44
  /**
39
- * Send a prompt to Claude and get a structured JSON response using tool calling
45
+ * Send a prompt to Claude and get a structured JSON response using tool calling.
46
+ * Uses prompt caching for system prompts to reduce token costs.
40
47
  *
41
48
  * @param {string} prompt - The prompt to send to Claude
42
49
  * @param {Object} options - Options for the request
50
+ * @param {string} options.system - System prompt (will be cached for cost optimization)
43
51
  * @param {Object} options.jsonSchema - JSON schema for structured output
52
+ * @param {string} options.cacheTtl - Cache TTL: '5m' (default, no extra cost) or '1h' (extended, extra cost for writes)
44
53
  * @returns {Promise<Object>} The response from Claude with structured data
45
54
  */
46
55
  async function sendPromptToClaude(prompt, options = {}) {
47
- const { model = DEFAULT_MODEL, maxTokens = MAX_TOKENS, temperature = 0.7, system = '', jsonSchema = null } = options;
56
+ const { model = DEFAULT_MODEL, maxTokens = MAX_TOKENS, temperature = 0.7, system = '', jsonSchema = null, cacheTtl = '5m' } = options;
48
57
 
49
58
  try {
50
59
  console.log(chalk.cyan('Sending prompt to Claude...'));
51
60
 
52
61
  const client = getAnthropicClient();
53
62
 
54
- // Use structured output with tool calling if schema is provided
63
+ // Build system content with cache_control for cost optimization
64
+ // The system is passed as an array of blocks with cache_control on the static portion
65
+ // TTL options: '5m' (default, no extra cost) or '1h' (extended, extra cost for cache writes)
66
+ const cacheControl = cacheTtl === '1h' ? { type: 'ephemeral', ttl: '1h' } : { type: 'ephemeral' };
67
+
68
+ const systemContent = system
69
+ ? [
70
+ {
71
+ type: 'text',
72
+ text: system,
73
+ cache_control: cacheControl,
74
+ },
75
+ ]
76
+ : 'You are an expert code reviewer with deep knowledge of software engineering principles, design patterns, and best practices.';
77
+
78
+ // Build base request parameters
79
+ const requestParams = {
80
+ model,
81
+ max_tokens: maxTokens,
82
+ temperature,
83
+ system: systemContent,
84
+ messages: [
85
+ {
86
+ role: 'user',
87
+ content: prompt,
88
+ },
89
+ ],
90
+ };
91
+
92
+ // Add tool calling if JSON schema is provided
55
93
  if (jsonSchema) {
56
- const tools = [
94
+ requestParams.tools = [
57
95
  {
58
96
  name: 'return_json',
59
97
  description: 'Return the final answer strictly as JSON matching the schema.',
60
98
  input_schema: jsonSchema,
61
99
  },
62
100
  ];
101
+ requestParams.tool_choice = { type: 'tool', name: 'return_json' };
102
+ }
63
103
 
64
- const response = await client.messages.create({
65
- model,
66
- max_tokens: maxTokens,
67
- temperature,
68
- tools,
69
- tool_choice: { type: 'tool', name: 'return_json' },
70
- system:
71
- system ||
72
- 'You are an expert code reviewer with deep knowledge of software engineering principles, design patterns, and best practices.',
73
- messages: [
74
- {
75
- role: 'user',
76
- content: prompt,
77
- },
78
- ],
79
- });
104
+ const response = await client.messages.create(requestParams);
105
+
106
+ // Log response structure for debugging
107
+ console.log(chalk.gray(` Response stop_reason: ${response.stop_reason}`));
108
+ console.log(chalk.gray(` Response content blocks: ${response.content?.length || 0}`));
80
109
 
81
- // Find the tool_use block and extract the structured data
110
+ // Process response based on whether we used tool calling
111
+ if (jsonSchema) {
82
112
  const toolUse = response.content.find((block) => block.type === 'tool_use' && block.name === 'return_json');
83
113
 
84
114
  if (!toolUse) {
115
+ // Log actual content for debugging
116
+ console.error(chalk.red('No tool_use block found. Response content:'));
117
+ response.content?.forEach((block, i) => {
118
+ console.error(chalk.gray(` Block ${i}: type=${block.type}, name=${block.name || 'N/A'}`));
119
+ });
85
120
  throw new Error('No structured output received from Claude');
86
121
  }
87
122
 
88
123
  return {
89
- content: JSON.stringify(toolUse.input, null, 2), // For backward compatibility
124
+ content: JSON.stringify(toolUse.input, null, 2),
90
125
  model: response.model,
91
126
  usage: response.usage,
92
- json: toolUse.input, // The parsed JavaScript object
127
+ json: toolUse.input,
93
128
  };
94
129
  } else {
95
- // Fallback to regular text response
96
- const response = await client.messages.create({
97
- model,
98
- max_tokens: maxTokens,
99
- temperature,
100
- system:
101
- system ||
102
- 'You are an expert code reviewer with deep knowledge of software engineering principles, design patterns, and best practices.',
103
- messages: [
104
- {
105
- role: 'user',
106
- content: prompt,
107
- },
108
- ],
109
- });
110
-
111
130
  return {
112
- content: response.content[0].text,
131
+ content: response.content[0]?.text || '',
113
132
  model: response.model,
114
133
  usage: response.usage,
115
134
  };
package/src/llm.test.js CHANGED
@@ -75,7 +75,13 @@ describe('sendPromptToClaude', () => {
75
75
  model: 'claude-3-opus',
76
76
  max_tokens: 8192,
77
77
  temperature: 0.5,
78
- system: 'Custom system prompt',
78
+ system: [
79
+ {
80
+ type: 'text',
81
+ text: 'Custom system prompt',
82
+ cache_control: { type: 'ephemeral' },
83
+ },
84
+ ],
79
85
  })
80
86
  );
81
87
  });
@@ -225,7 +231,13 @@ describe('sendPromptToClaude', () => {
225
231
 
226
232
  expect(mockMessagesCreate).toHaveBeenCalledWith(
227
233
  expect.objectContaining({
228
- system: customSystem,
234
+ system: [
235
+ {
236
+ type: 'text',
237
+ text: customSystem,
238
+ cache_control: { type: 'ephemeral' },
239
+ },
240
+ ],
229
241
  })
230
242
  );
231
243
  });
@@ -11,6 +11,7 @@ import path from 'path';
11
11
  import chalk from 'chalk';
12
12
  import { getDefaultEmbeddingsSystem } from './embeddings/factory.js';
13
13
  import * as llm from './llm.js';
14
+ import { FILE_SELECTION_SYSTEM_PROMPT, PROJECT_SUMMARY_SYSTEM_PROMPT } from './prompt-cache.js';
14
15
  import { isDocumentationFile, isTestFile } from './utils/file-validation.js';
15
16
 
16
17
  // Consolidated file classification configuration
@@ -494,18 +495,7 @@ Project: ${path.basename(projectPath)}
494
495
  Files found by embeddings search:
495
496
  ${candidatesSummary}
496
497
 
497
- Select files that best reveal the project's architecture:
498
- - Framework setup & key configurations
499
- - Custom utilities, hooks, and wrappers
500
- - API/data layer patterns and GraphQL setup
501
- - Type definitions & core interfaces
502
- - Entry points, routing, and main structure
503
- - State management and data flow patterns
504
-
505
- IMPORTANT: Return ONLY a JSON array of file paths, nothing else:
506
- ["path1", "path2", "path3"]
507
-
508
- Select files that define HOW this project works, especially custom implementations.`;
498
+ Select files following the criteria in the system instructions.`;
509
499
 
510
500
  try {
511
501
  const fileSelectionSchema = {
@@ -524,6 +514,7 @@ Select files that define HOW this project works, especially custom implementatio
524
514
  };
525
515
 
526
516
  const response = await this.llm.sendPromptToClaude(prompt, {
517
+ system: FILE_SELECTION_SYSTEM_PROMPT,
527
518
  temperature: 0.1,
528
519
  maxTokens: 1000,
529
520
  jsonSchema: fileSelectionSchema,
@@ -636,11 +627,15 @@ Select files that define HOW this project works, especially custom implementatio
636
627
  async generateProjectSummary(keyFiles, projectPath) {
637
628
  const fileContents = await this.extractFileContents(keyFiles);
638
629
 
639
- const prompt = `Analyze this project's architecture and provide a comprehensive summary. Here are the key files:
630
+ const prompt = `Analyze this project's architecture and provide a comprehensive summary.
631
+
632
+ ## KEY FILES
640
633
 
641
634
  ${fileContents}
642
635
 
643
- Please analyze this project and provide a JSON response with:
636
+ ## OUTPUT FORMAT
637
+
638
+ Provide a JSON response with this structure:
644
639
 
645
640
  {
646
641
  "projectName": "Project name from package.json or inferred",
@@ -661,7 +656,7 @@ Please analyze this project and provide a JSON response with:
661
656
  "name": "Custom feature/hook/utility name",
662
657
  "description": "What it does and HOW it modifies standard library behavior",
663
658
  "files": ["Files where it's defined"],
664
- "properties": ["Key properties/methods it exposes, especially any that extend standard objects"],
659
+ "properties": ["Key properties/methods it exposes"],
665
660
  "usage": "How it should be used",
666
661
  "extendsStandard": "Which standard library/framework objects or APIs this modifies"
667
662
  }
@@ -697,44 +692,11 @@ Please analyze this project and provide a JSON response with:
697
692
  "reviewGuidelines": [
698
693
  "Specific guidelines for code review based on this project's patterns",
699
694
  "What to look for in PRs",
700
- "Common patterns that should be maintained",
701
- "Potential issues specific to this architecture"
695
+ "Common patterns that should be maintained"
702
696
  ]
703
697
  }
704
698
 
705
- Focus on identifying patterns that would help in code review, especially:
706
- - Custom utilities or modules that extend standard frameworks and libraries
707
- - **CRITICAL: Custom properties or methods added to standard library objects** (e.g., custom properties on database query results, API responses, or framework objects)
708
- - **Extensions to library APIs** - any way this project modifies or enhances standard library behavior
709
- - Specific ways APIs are called and results are handled (look for non-standard patterns)
710
- - Data flow and processing patterns
711
- - Module organization and code structure patterns
712
- - Type definitions and interfaces that define contracts, especially those that extend standard types
713
- - Configuration patterns and environment handling
714
- - **Custom wrappers** around standard libraries that add functionality
715
-
716
- **CRITICAL ANALYSIS REQUIRED**: Look specifically for code that:
717
- 1. **Takes standard library return values and adds custom properties** - For example:
718
- - Functions that take query results and add success/loading/error properties
719
- - Wrappers that enhance API responses with additional metadata
720
- - Custom hooks that extend standard framework hooks with extra functionality
721
- 2. **Modifies or extends standard library interfaces** - Look for:
722
- - TypeScript interfaces that extend standard types with additional fields
723
- - Custom implementations that add methods to standard objects
724
- - Wrapper classes that enhance standard library functionality
725
- 3. **Creates custom versions of standard patterns** - Such as:
726
- - Custom error handling that adds properties to standard error objects
727
- - Middleware that modifies standard request/response patterns
728
- - Custom state management that extends standard patterns
729
-
730
- **EXAMPLES TO RECOGNIZE**:
731
- - If you see a function that takes a standard query result and returns an object with added success/error properties, identify this as a custom implementation
732
- - If you see custom hooks that wrap standard library hooks and add properties, document these
733
- - If you see type definitions that extend standard interfaces, note what properties they add
734
-
735
- **OUTPUT REQUIREMENT**: For each custom implementation found, specifically identify what standard library object or pattern it extends in the "extendsStandard" field.
736
-
737
- Be thorough but concise. This summary will be used to provide context during automated code reviews to prevent false positives about "non-standard" properties that are actually valid custom implementations in this project.`;
699
+ Follow the analysis guidelines from the system instructions to identify custom implementations and patterns.`;
738
700
 
739
701
  try {
740
702
  const projectSummarySchema = {
@@ -807,6 +769,7 @@ Be thorough but concise. This summary will be used to provide context during aut
807
769
  };
808
770
 
809
771
  const response = await this.llm.sendPromptToClaude(prompt, {
772
+ system: PROJECT_SUMMARY_SYSTEM_PROMPT,
810
773
  temperature: 0.1,
811
774
  maxTokens: 4000,
812
775
  jsonSchema: projectSummarySchema,