@mate_tsaava/pr-review 1.0.2 → 1.0.3

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/dist/cli.js CHANGED
@@ -58,14 +58,14 @@ yargs(hideBin(process.argv))
58
58
  default: false,
59
59
  })
60
60
  .option("max-tokens", {
61
- describe: "Max tokens per batch (default: 150000)",
61
+ describe: "Max tokens per batch",
62
62
  type: "number",
63
- default: 150000,
63
+ default: 40000,
64
64
  })
65
65
  .option("max-files", {
66
- describe: "Max files per batch (default: 10)",
66
+ describe: "Max files per batch",
67
67
  type: "number",
68
- default: 10,
68
+ default: 3,
69
69
  })
70
70
  .option("config", {
71
71
  alias: "c",
@@ -77,6 +77,11 @@ yargs(hideBin(process.argv))
77
77
  describe: "Show detailed logs (prompts, responses, API calls)",
78
78
  type: "boolean",
79
79
  default: false,
80
+ })
81
+ .option("review-context", {
82
+ describe: "Also flag issues in context lines (legacy code near changes)",
83
+ type: "boolean",
84
+ default: false,
80
85
  });
81
86
  }, async (argv) => {
82
87
  const { loadConfig } = await import("./config.js");
@@ -102,6 +107,7 @@ yargs(hideBin(process.argv))
102
107
  maxTokensPerBatch: argv.maxTokens,
103
108
  maxFilesPerBatch: argv.maxFiles,
104
109
  verbose: argv.verbose,
110
+ reviewContext: argv.reviewContext,
105
111
  });
106
112
  // Print summary
107
113
  console.log("\n" + chalk.bold("Result:"));
@@ -10,4 +10,5 @@ export interface LLMAdapterConfig {
10
10
  model: string;
11
11
  endpoint?: string;
12
12
  verbose?: boolean;
13
+ reviewContext?: boolean;
13
14
  }
@@ -50,6 +50,7 @@ export class AzureOpenAIAdapter {
50
50
  - Author: ${prompt.prMetadata.author}
51
51
  - Source Branch: ${prompt.prMetadata.sourceBranch}
52
52
  - Target Branch: ${prompt.prMetadata.targetBranch}
53
+ - Description: ${prompt.prMetadata.description || "No description"}
53
54
 
54
55
  ## File Diffs
55
56
  `;
@@ -4,6 +4,7 @@ export declare class ClaudeAdapter implements LLMAdapter {
4
4
  private client;
5
5
  private model;
6
6
  private verbose;
7
+ private reviewContext;
7
8
  constructor(config: LLMAdapterConfig);
8
9
  review(prompt: ReviewPrompt): Promise<ReviewResult>;
9
10
  private buildUserPrompt;
@@ -1,25 +1,30 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
- const SYSTEM_PROMPT = `You are a Senior Principal Engineer conducting a code review. You are direct, blunt, and pragmatic.
2
+ const SYSTEM_PROMPT_BASE = `You are a code reviewer. Follow the provided rules file EXACTLY.
3
3
 
4
- Your task is to review the PR diff and identify issues in the NEW CODE ONLY. For each issue, provide:
5
- - file: The EXACT file path from the diff header (e.g., "/CredoWebAPI/Controllers/ScoringController.cs")
6
- - line: The EXACT line number where the problem IS, not where the function starts. Point to the specific problematic line.
7
- - endLine: (optional) If the issue spans multiple lines, include the ending line number.
8
- - severity: BLOCK (must fix before merge), HIGH (should fix), or MEDIUM (nice to fix)
9
- - category: Type of issue (Security, Architecture, Naming, Performance, Clean Code, etc.)
10
- - codeSnippet: Quote the EXACT problematic code (1-3 lines max) so there's no ambiguity
11
- - message: A witty, memorable comment about the issue
12
- - fix: The specific action to fix the issue, referencing the exact lines
4
+ STRICT RULES:
5
+ 1. NO DUPLICATES - Flag each issue ONCE only. If branch name is invalid, ONE issue. If PR title is invalid, ONE issue.
6
+ 2. ONLY FLAG PROBLEMS - Never create issues for correct code. If code is fine, don't mention it.
7
+ 3. LINE NUMBERS - Read from diff @@ header. Example: "@@ -120,6 +145,10 @@" means new code starts at line 145. Use the NEW file line number (after the +).
8
+ 4. For metadata issues: file="/_metadata", line=1 (branch) or line=2 (title)
13
9
 
14
- CRITICAL RULES:
15
- 1. ONLY review lines starting with "+" (added or modified code). These are the NEW lines being introduced.
16
- 2. NEVER comment on lines starting with "-" (deleted/old code). These are being REMOVED from the codebase.
17
- 3. When a line is CHANGED, it appears as "-old" then "+new" - only review the "+new" version.
18
- 4. The "file" field MUST be the exact path from the diff (starts with /)
19
- 5. The "line" field MUST be a positive integer from the NEW code's line numbers (the + lines)
20
- 6. If old problematic code is being deleted and replaced with better code, that is GOOD - do not flag the old code as an issue.
10
+ "file" = exact path from diff header (starts with /)`;
11
+ const DIFF_RULES_STRICT = `
21
12
 
22
- Return your response as valid JSON in this exact format:
13
+ DIFF RULES (STRICT MODE - only review changed lines):
14
+ - ONLY review "+" lines (new code being added)
15
+ - NEVER flag "-" lines (old code being removed)
16
+ - NEVER flag " " lines (context/unchanged code) - these existed before this PR
17
+ - If code doesn't start with "+", SKIP IT completely`;
18
+ const DIFF_RULES_WITH_CONTEXT = `
19
+
20
+ DIFF RULES (CONTEXT MODE - review changes and surrounding code):
21
+ - Review "+" lines (new code being added) - PRIMARY focus
22
+ - Review " " lines (context) if they have issues near the changes
23
+ - NEVER flag "-" lines (old code being removed)
24
+ - For context issues, be helpful but less strict (use MEDIUM severity max)`;
25
+ const JSON_FORMAT = `
26
+
27
+ Return your response as valid JSON:
23
28
  {
24
29
  "issues": [
25
30
  {
@@ -45,14 +50,17 @@ export class ClaudeAdapter {
45
50
  client;
46
51
  model;
47
52
  verbose;
53
+ reviewContext;
48
54
  constructor(config) {
49
55
  this.client = new Anthropic({ apiKey: config.apiKey });
50
56
  this.model = config.model;
51
57
  this.verbose = config.verbose || false;
58
+ this.reviewContext = config.reviewContext || false;
52
59
  }
53
60
  async review(prompt) {
54
61
  const userPrompt = this.buildUserPrompt(prompt);
55
- const systemPrompt = SYSTEM_PROMPT + "\n\n" + prompt.rules;
62
+ const diffRules = this.reviewContext ? DIFF_RULES_WITH_CONTEXT : DIFF_RULES_STRICT;
63
+ const systemPrompt = SYSTEM_PROMPT_BASE + diffRules + JSON_FORMAT + "\n\n" + prompt.rules;
56
64
  const maxRetries = 3;
57
65
  let lastError = null;
58
66
  if (this.verbose) {
@@ -73,7 +81,7 @@ export class ClaudeAdapter {
73
81
  try {
74
82
  const response = await this.client.messages.create({
75
83
  model: this.model,
76
- max_tokens: 4096,
84
+ max_tokens: 8192,
77
85
  system: systemPrompt,
78
86
  messages: [
79
87
  {
@@ -50,6 +50,7 @@ export class OpenAIAdapter {
50
50
  - Author: ${prompt.prMetadata.author}
51
51
  - Source Branch: ${prompt.prMetadata.sourceBranch}
52
52
  - Target Branch: ${prompt.prMetadata.targetBranch}
53
+ - Description: ${prompt.prMetadata.description || "No description"}
53
54
 
54
55
  ## File Diffs
55
56
  `;
@@ -9,6 +9,7 @@ export interface ReviewOptions {
9
9
  maxTokensPerBatch?: number;
10
10
  maxFilesPerBatch?: number;
11
11
  verbose?: boolean;
12
+ reviewContext?: boolean;
12
13
  }
13
14
  export interface ReviewOutput {
14
15
  result: ReviewResult;
package/dist/reviewer.js CHANGED
@@ -28,8 +28,8 @@ export class PRReviewer {
28
28
  // 2. Load review rules
29
29
  const rules = this.loadRules();
30
30
  // 3. Create batches
31
- const maxTokens = options.maxTokensPerBatch || 150000;
32
- const maxFiles = options.maxFilesPerBatch || 10;
31
+ const maxTokens = options.maxTokensPerBatch || 40000;
32
+ const maxFiles = options.maxFilesPerBatch || 3;
33
33
  const diffs = prDiff.diffs.map((d) => ({ path: d.path, diff: d.diff }));
34
34
  const totalTokens = estimateDiffTokens(diffs);
35
35
  const batches = createBatches(diffs, maxTokens, maxFiles);
@@ -42,6 +42,7 @@ export class PRReviewer {
42
42
  model: this.config.llm.model || "claude-sonnet-4-20250514",
43
43
  endpoint: this.config.llm.endpoint,
44
44
  verbose: options.verbose,
45
+ reviewContext: options.reviewContext,
45
46
  });
46
47
  const allIssues = [];
47
48
  try {
@@ -67,17 +68,51 @@ export class PRReviewer {
67
68
  catch (error) {
68
69
  throw new Error(`Failed to analyze PR with ${this.config.llm.provider}: ${error instanceof Error ? error.message : String(error)}`);
69
70
  }
70
- // 5. Generate combined result
71
+ // 5. Deduplicate metadata issues (same metadata sent to each batch)
72
+ // Use line-only key since metadata line numbers uniquely identify the issue
73
+ // (line 1 = branch naming, line 2 = PR title, etc.)
74
+ const allMetadataIssues = allIssues.filter((i) => i.file === "/_metadata");
75
+ const allCodeIssues = allIssues.filter((i) => i.file !== "/_metadata");
76
+ const uniqueMetadata = new Map();
77
+ for (const issue of allMetadataIssues) {
78
+ const key = `${issue.line}`;
79
+ if (!uniqueMetadata.has(key)) {
80
+ uniqueMetadata.set(key, issue);
81
+ }
82
+ }
83
+ // 6. Generate combined result
84
+ const metadataIssues = [...uniqueMetadata.values()];
85
+ const codeIssues = allCodeIssues;
86
+ const deduplicatedIssues = [...metadataIssues, ...codeIssues];
71
87
  const result = {
72
- issues: allIssues,
73
- summary: this.generateSummary(allIssues),
88
+ issues: deduplicatedIssues,
89
+ summary: this.generateSummary(deduplicatedIssues),
74
90
  };
75
91
  // 4. Post comments (unless dry run)
76
92
  let postedComments = 0;
77
93
  let summaryPosted = false;
78
94
  if (!options.dryRun) {
79
95
  console.log("\nPosting comments...");
80
- for (const issue of result.issues) {
96
+ // Post metadata issues as a single thread comment (not inline)
97
+ if (metadataIssues.length > 0) {
98
+ try {
99
+ const metadataContent = metadataIssues
100
+ .map((issue) => this.formatComment(issue))
101
+ .join("\n\n---\n\n");
102
+ await this.poster.postSummaryComment(options.repositoryId, options.pullRequestId, {
103
+ content: `## ⚠️ PR Metadata Issues\n\n${metadataContent}`,
104
+ });
105
+ postedComments += metadataIssues.length;
106
+ for (const issue of metadataIssues) {
107
+ console.log(` • ${issue.severity}: ${issue.category} (${issue.message.slice(0, 50)}...)`);
108
+ }
109
+ }
110
+ catch (error) {
111
+ console.error(` ✗ Failed to post metadata comment: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ }
114
+ // Post code issues as inline comments
115
+ for (const issue of codeIssues) {
81
116
  try {
82
117
  await this.poster.postInlineComment(options.repositoryId, options.pullRequestId, {
83
118
  filePath: issue.file,
@@ -102,10 +137,21 @@ export class PRReviewer {
102
137
  }
103
138
  else {
104
139
  console.log("\nDry run - would post these comments:");
105
- for (const issue of result.issues) {
106
- console.log(` [${issue.severity}] ${issue.category} (${issue.file}:${issue.line})`);
107
- console.log(` ${issue.message}`);
108
- console.log(` Fix: ${issue.fix}\n`);
140
+ if (metadataIssues.length > 0) {
141
+ console.log("\n 📋 METADATA ISSUES:");
142
+ for (const issue of metadataIssues) {
143
+ console.log(` [${issue.severity}] ${issue.category}`);
144
+ console.log(` ${issue.message}`);
145
+ console.log(` Fix: ${issue.fix}\n`);
146
+ }
147
+ }
148
+ if (codeIssues.length > 0) {
149
+ console.log("\n 📝 CODE ISSUES:");
150
+ for (const issue of codeIssues) {
151
+ console.log(` [${issue.severity}] ${issue.category} (${issue.file}:${issue.line})`);
152
+ console.log(` ${issue.message}`);
153
+ console.log(` Fix: ${issue.fix}\n`);
154
+ }
109
155
  }
110
156
  console.log(`Summary: ${result.summary}`);
111
157
  }
@@ -1,5 +1,5 @@
1
1
  import { estimateTokens } from "./tokens.js";
2
- export function createBatches(diffs, maxTokensPerBatch = 150000, maxFilesPerBatch = 10) {
2
+ export function createBatches(diffs, maxTokensPerBatch = 40000, maxFilesPerBatch = 3) {
3
3
  // Add token estimates
4
4
  const filesWithTokens = diffs.map((d) => ({
5
5
  ...d,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mate_tsaava/pr-review",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "AI-powered code review CLI for Azure DevOps pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "zod": "^3.25.63",
39
39
  "chalk": "^5.4.1",
40
40
  "dotenv": "^16.4.7",
41
- "@mate_tsaava/azure-devops-core": "1.0.0"
41
+ "@mate_tsaava/azure-devops-core": "1.0.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.19.1",