Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,6 @@ export function myUtility(input) {
newCode: string
}
}],
positives: string[]
},
skipped: boolean,
error: string | undefined
Expand Down
19 changes: 4 additions & 15 deletions docs/OUTPUT_FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ Issues:
interface ButtonProps { onClick?: () => void; disabled?: boolean; }
const Button = (props: ButtonProps) => {

Positives:
- Good use of semantic HTML elements
- Proper accessibility attributes
```

## JSON Format
Expand Down Expand Up @@ -91,8 +88,7 @@ The JSON format provides structured output perfect for programmatic processing,
"newCode": "interface ButtonProps { onClick?: () => void; disabled?: boolean; }\nconst Button = (props: ButtonProps) => {"
}
}
],
"positives": ["Good use of semantic HTML elements", "Proper accessibility attributes"]
]
}
}
]
Expand Down Expand Up @@ -149,14 +145,6 @@ The markdown format is documentation-friendly and ideal for generating reports,
```
````

**Positives Found (2):**

- Good use of semantic HTML elements

- Proper accessibility attributes

````

## Usage

Specify the output format using the `--output` (or `-o`) option:
Expand All @@ -170,7 +158,7 @@ codecritique analyze --file src/app.ts --output json

# Markdown format
codecritique analyze --file src/app.ts --output markdown
````
```

### Saving Output to a File

Expand All @@ -180,10 +168,11 @@ You can redirect output to a file using shell redirection:
codecritique analyze --files "src/**/*.ts" --output json > review.json
```

Or use the `--output-file` option (for JSON format):
Or use the `--output-file` option for `json` or `markdown` output:

```bash
codecritique analyze --files "src/**/*.ts" --output json --output-file review.json
codecritique analyze --files "src/**/*.ts" --output markdown --output-file review.md
```

---
Expand Down
87 changes: 40 additions & 47 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ function outputJson(reviewResults, options) {
filePath: r.filePath,
success: true,
language: r.language,
review: r.results, // Contains summary, issues (with optional codeSuggestion), positives from LLM
review: r.results, // Contains summary and actionable issues (with optional codeSuggestion)
// Optionally include similar examples if needed
// similarExamplesUsed: r.similarExamples
};
Expand All @@ -1038,86 +1038,86 @@ function outputJson(reviewResults, options) {
* Output results in Markdown format
*
* @param {Array<Object>} reviewResults - Array of individual file review results
* @param {Object} cliOptions - Command line options
* @param {Object} options - Command line options
*/
function outputMarkdown(reviewResults) {
console.log('# AI Code Review Results (RAG Approach)\n');

function outputMarkdown(reviewResults, options) {
const totalFiles = reviewResults.length;
const filesWithIssues = reviewResults.filter((r) => r.success && !r.skipped && r.results?.issues?.length > 0).length;
const totalIssues = reviewResults.reduce((sum, r) => sum + (r.results?.issues?.length || 0), 0);
const skippedFiles = reviewResults.filter((r) => r.skipped).length;
const errorFiles = reviewResults.filter((r) => !r.success).length;

console.log('## Summary\n');
console.log(`- **Files Analyzed:** ${totalFiles}`);
console.log(`- **Files with Issues:** ${filesWithIssues}`);
console.log(`- **Total Issues Found:** ${totalIssues}`);
if (skippedFiles > 0) console.log(`- **Files Skipped:** ${skippedFiles}`);
if (errorFiles > 0) console.log(`- **Errors:** ${errorFiles}`);
console.log('\n');
const lines = [
'# AI Code Review Results (RAG Approach)',
'',
'## Summary',
'',
`- **Files Analyzed:** ${totalFiles}`,
`- **Files with Issues:** ${filesWithIssues}`,
`- **Total Issues Found:** ${totalIssues}`,
];

if (skippedFiles > 0) lines.push(`- **Files Skipped:** ${skippedFiles}`);
if (errorFiles > 0) lines.push(`- **Errors:** ${errorFiles}`);

console.log('## Detailed Review per File\n');
lines.push('', '## Detailed Review per File', '');

reviewResults.forEach((fileResult) => {
console.log(`### ${fileResult.filePath}\n`);
lines.push(`### ${fileResult.filePath}`, '');
if (!fileResult.success) {
console.log(`**Error:** ${fileResult.error}\n`);
lines.push(`**Error:** ${fileResult.error}`, '');
return;
}
if (fileResult.skipped) {
console.log(`*Skipped (based on exclusion patterns or file type).*\n`);
lines.push('*Skipped (based on exclusion patterns or file type).*', '');
return;
}
if (!fileResult.results || (!fileResult.results.issues?.length && !fileResult.results.positives?.length)) {
console.log(`*No significant findings or issues reported.*\n`);
if (!fileResult.results || !fileResult.results.issues?.length) {
lines.push('*No actionable issues reported.*', '');
if (fileResult.results?.summary) {
console.log(`**Summary:** ${fileResult.results.summary}\n`);
lines.push(`**Summary:** ${fileResult.results.summary}`, '');
}
return;
}

const review = fileResult.results;
if (review.summary) {
console.log(`**Summary:** ${review.summary}\n`);
lines.push(`**Summary:** ${review.summary}`, '');
}

if (review.issues && review.issues.length > 0) {
console.log(`**Issues Found (${review.issues.length}):**\n`);
lines.push(`**Issues Found (${review.issues.length}):**`, '');
review.issues.forEach((issue) => {
const severityEmoji = getSeverityEmoji(issue.severity);
console.log(
lines.push(
`- **[${issue.severity.toUpperCase()}] ${severityEmoji} (Lines: ${issue.lineNumbers?.join(', ') || 'N/A'})**: ${
issue.description
}`
);
if (issue.suggestion) {
console.log(`\n *Suggestion:* ${issue.suggestion}\n`);
lines.push('', ` *Suggestion:* ${issue.suggestion}`, '');
}
// Include code suggestion if available
if (issue.codeSuggestion) {
const { startLine, endLine, newCode } = issue.codeSuggestion;
const lineRange = endLine ? `${startLine}-${endLine}` : `${startLine}`;
console.log(`\n **Suggested change (lines ${lineRange}):**\n`);
console.log(' ```suggestion');
console.log(
newCode
.split('\n')
.map((line) => ` ${line}`)
.join('\n')
);
console.log(' ```\n');
lines.push('', ` **Suggested change (lines ${lineRange}):**`, '', ' ```suggestion');
lines.push(...newCode.split('\n').map((line) => ` ${line}`));
lines.push(' ```', '');
}
});
}

if (review.positives && review.positives.length > 0) {
console.log(`**Positives Found (${review.positives.length}):**\n`);
review.positives.forEach((positive) => {
console.log(` - ${positive}\n`);
});
}
});

const markdownOutput = `${lines.join('\n')}\n`;

if (options?.outputFile) {
fs.writeFileSync(options.outputFile, markdownOutput, 'utf8');
console.log(chalk.green(`Markdown output saved to: ${options.outputFile}`));
return;
}

process.stdout.write(markdownOutput);
}

/**
Expand Down Expand Up @@ -1154,7 +1154,7 @@ function outputText(reviewResults, cliOptions) {
}
return;
}
if (!fileResult.results || (!fileResult.results.issues?.length && !fileResult.results.positives?.length)) {
if (!fileResult.results || !fileResult.results.issues?.length) {
if (cliOptions.verbose) {
console.log(chalk.green(`\nNo findings for: ${fileResult.filePath}`));
if (fileResult.results?.summary) {
Expand Down Expand Up @@ -1194,13 +1194,6 @@ function outputText(reviewResults, cliOptions) {
});
}

if (review.positives && review.positives.length > 0) {
console.log(chalk.bold.green('\nPositives:'));
review.positives.forEach((positive) => {
console.log(` - ${positive}`);
});
console.log('');
}
console.log(chalk.gray(`========================================${'='.repeat(fileResult.filePath.length)}`));
});
}
Expand Down
84 changes: 51 additions & 33 deletions src/rag-analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1975,40 +1975,58 @@ async function getContextForFile(filePath, content, options = {}) {
}
};

const withContextFallback = async (promise, fallbackValue, label) => {
try {
return await promise;
} catch (error) {
console.warn(chalk.yellow(`${label} failed: ${error.message}`));
return fallbackValue;
}
};

const [prContextResult, guidelineCandidates, codeExampleCandidates, relevantCustomDocChunks] = await Promise.all([
getPRCommentContext(filePath, {
...options,
projectPath,
precomputedQueryEmbedding: fileContentQueryEmbedding,
maxComments: MAX_PR_COMMENTS_FOR_CONTEXT,
similarityThreshold: options.prSimilarityThreshold || 0.3,
timeout: options.prTimeout || 300000,
repository: options.repository || null,
}),
embeddingsSystem.findRelevantDocs(guidelineQuery, {
...options,
projectPath,
precomputedQueryEmbedding: guidelineQueryEmbedding,
limit: GUIDELINE_CANDIDATE_LIMIT,
similarityThreshold: 0.05,
useReranking: true,
queryContextForReranking: reviewedSnippetContext,
}),
embeddingsSystem.findSimilarCode(isTestFile ? `${content}\\n// Looking for similar test files and testing patterns` : content, {
...options,
projectPath,
isTestFile,
precomputedQueryEmbedding: fileContentQueryEmbedding,
limit: CODE_EXAMPLE_LIMIT,
similarityThreshold: 0.3,
queryFilePath: filePath,
includeProjectStructure: false,
}),
processCustomDocuments(), // Add custom document processing as 4th parallel operation
]).catch((error) => {
console.warn(chalk.yellow(`Parallel context retrieval failed: ${error.message}`));
return [[], [], [], []];
});
withContextFallback(
getPRCommentContext(filePath, {
...options,
projectPath,
precomputedQueryEmbedding: fileContentQueryEmbedding,
maxComments: MAX_PR_COMMENTS_FOR_CONTEXT,
similarityThreshold: options.prSimilarityThreshold || 0.3,
timeout: options.prTimeout || 300000,
repository: options.repository || null,
}),
{ comments: [] },
'PR comment context retrieval'
),
withContextFallback(
embeddingsSystem.findRelevantDocs(guidelineQuery, {
...options,
projectPath,
precomputedQueryEmbedding: guidelineQueryEmbedding,
limit: GUIDELINE_CANDIDATE_LIMIT,
similarityThreshold: 0.05,
useReranking: true,
queryContextForReranking: reviewedSnippetContext,
}),
[],
'Guideline retrieval'
),
withContextFallback(
embeddingsSystem.findSimilarCode(isTestFile ? `${content}\\n// Looking for similar test files and testing patterns` : content, {
...options,
projectPath,
isTestFile,
precomputedQueryEmbedding: fileContentQueryEmbedding,
limit: CODE_EXAMPLE_LIMIT,
similarityThreshold: 0.3,
queryFilePath: filePath,
includeProjectStructure: false,
}),
[],
'Code example retrieval'
),
withContextFallback(processCustomDocuments(), [], 'Custom document retrieval'),
]);

const prCommentContext = prContextResult?.comments || [];
const prContextAvailable = prCommentContext.length > 0;
Expand Down
23 changes: 23 additions & 0 deletions src/rag-analyzer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,29 @@ describe('rag-analyzer', () => {
const result = await runAnalysis('/test/file.js');
expect(result.success).toBe(true);
});

it('should preserve successful context sources when one parallel retrieval fails', async () => {
mockEmbeddingsSystem.findRelevantDocs.mockResolvedValue([
{
path: '/docs/api.md',
content: 'API docs',
similarity: 0.8,
type: 'documentation-chunk',
document_title: 'API Docs',
heading_text: 'Usage',
},
]);
mockEmbeddingsSystem.findSimilarCode.mockResolvedValue([{ path: '/similar.js', content: 'similar code', similarity: 0.9 }]);
findRelevantPRComments.mockRejectedValue(new Error('PR comments failed'));
llm.sendPromptToClaude.mockResolvedValue({ json: { summary: 'Review', issues: [] } });

const result = await runAnalysis('/test/file.js');

expect(result.success).toBe(true);
expect(result.context.codeExamples).toBeGreaterThan(0);
expect(result.context.guidelines).toBeGreaterThanOrEqual(0);
expect(result.context.prComments).toBe(0);
});
});

// ==========================================================================
Expand Down
Loading