@j0hanz/code-review-analyst-mcp 1.2.1 → 1.4.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 (55) hide show
  1. package/dist/index.js +12 -3
  2. package/dist/lib/context-budget.d.ts +2 -2
  3. package/dist/lib/context-budget.js +12 -6
  4. package/dist/lib/diff-budget.js +6 -2
  5. package/dist/lib/diff-cleaner.d.ts +12 -0
  6. package/dist/lib/diff-cleaner.js +51 -0
  7. package/dist/lib/diff-parser.js +31 -36
  8. package/dist/lib/diff-store.d.ts +22 -0
  9. package/dist/lib/diff-store.js +28 -0
  10. package/dist/lib/env-config.d.ts +1 -0
  11. package/dist/lib/env-config.js +9 -3
  12. package/dist/lib/errors.d.ts +1 -0
  13. package/dist/lib/errors.js +5 -5
  14. package/dist/lib/gemini-schema.js +2 -1
  15. package/dist/lib/gemini.js +135 -67
  16. package/dist/lib/model-config.d.ts +14 -2
  17. package/dist/lib/model-config.js +30 -6
  18. package/dist/lib/tool-contracts.d.ts +222 -0
  19. package/dist/lib/tool-contracts.js +289 -0
  20. package/dist/lib/tool-factory.d.ts +5 -1
  21. package/dist/lib/tool-factory.js +48 -54
  22. package/dist/lib/tool-response.js +10 -12
  23. package/dist/lib/types.d.ts +3 -3
  24. package/dist/prompts/index.js +47 -41
  25. package/dist/resources/index.d.ts +1 -1
  26. package/dist/resources/index.js +99 -18
  27. package/dist/resources/instructions.d.ts +1 -0
  28. package/dist/resources/instructions.js +69 -0
  29. package/dist/resources/server-config.d.ts +1 -0
  30. package/dist/resources/server-config.js +71 -0
  31. package/dist/resources/tool-catalog.d.ts +1 -0
  32. package/dist/resources/tool-catalog.js +39 -0
  33. package/dist/resources/tool-info.d.ts +5 -0
  34. package/dist/resources/tool-info.js +122 -0
  35. package/dist/resources/workflows.d.ts +1 -0
  36. package/dist/resources/workflows.js +72 -0
  37. package/dist/schemas/inputs.d.ts +6 -5
  38. package/dist/schemas/inputs.js +17 -29
  39. package/dist/schemas/outputs.d.ts +17 -1
  40. package/dist/schemas/outputs.js +84 -52
  41. package/dist/server.js +28 -27
  42. package/dist/tools/analyze-complexity.d.ts +2 -0
  43. package/dist/tools/analyze-complexity.js +51 -0
  44. package/dist/tools/analyze-pr-impact.js +32 -20
  45. package/dist/tools/detect-api-breaking.d.ts +2 -0
  46. package/dist/tools/detect-api-breaking.js +48 -0
  47. package/dist/tools/generate-diff.d.ts +2 -0
  48. package/dist/tools/generate-diff.js +71 -0
  49. package/dist/tools/generate-review-summary.js +34 -29
  50. package/dist/tools/generate-test-plan.js +38 -28
  51. package/dist/tools/index.js +11 -2
  52. package/dist/tools/inspect-code-quality.js +47 -36
  53. package/dist/tools/suggest-search-replace.js +34 -20
  54. package/package.json +1 -2
  55. package/dist/instructions.md +0 -149
@@ -4,17 +4,14 @@ export declare const FileContextSchema: z.ZodObject<{
4
4
  content: z.ZodString;
5
5
  }, z.core.$strict>;
6
6
  export declare const AnalyzePrImpactInputSchema: z.ZodObject<{
7
- diff: z.ZodString;
8
7
  repository: z.ZodString;
9
8
  language: z.ZodOptional<z.ZodString>;
10
9
  }, z.core.$strict>;
11
10
  export declare const GenerateReviewSummaryInputSchema: z.ZodObject<{
12
- diff: z.ZodString;
13
11
  repository: z.ZodString;
14
12
  language: z.ZodOptional<z.ZodString>;
15
13
  }, z.core.$strict>;
16
14
  export declare const InspectCodeQualityInputSchema: z.ZodObject<{
17
- diff: z.ZodString;
18
15
  repository: z.ZodString;
19
16
  language: z.ZodOptional<z.ZodString>;
20
17
  focusAreas: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -25,14 +22,18 @@ export declare const InspectCodeQualityInputSchema: z.ZodObject<{
25
22
  }, z.core.$strict>>>;
26
23
  }, z.core.$strict>;
27
24
  export declare const SuggestSearchReplaceInputSchema: z.ZodObject<{
28
- diff: z.ZodString;
29
25
  findingTitle: z.ZodString;
30
26
  findingDetails: z.ZodString;
31
27
  }, z.core.$strict>;
32
28
  export declare const GenerateTestPlanInputSchema: z.ZodObject<{
33
- diff: z.ZodString;
34
29
  repository: z.ZodString;
35
30
  language: z.ZodOptional<z.ZodString>;
36
31
  testFramework: z.ZodOptional<z.ZodString>;
37
32
  maxTestCases: z.ZodOptional<z.ZodNumber>;
38
33
  }, z.core.$strict>;
34
+ export declare const AnalyzeComplexityInputSchema: z.ZodObject<{
35
+ language: z.ZodOptional<z.ZodString>;
36
+ }, z.core.$strict>;
37
+ export declare const DetectApiBreakingInputSchema: z.ZodObject<{
38
+ language: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strict>;
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  const INPUT_LIMITS = {
3
- diff: { min: 10 },
4
3
  repository: { min: 1, max: 200 },
5
4
  language: { min: 2, max: 32 },
6
5
  fileContext: {
@@ -24,29 +23,26 @@ function createOptionalBoundedString(min, max, description) {
24
23
  function createLanguageSchema(description) {
25
24
  return createOptionalBoundedString(INPUT_LIMITS.language.min, INPUT_LIMITS.language.max, description);
26
25
  }
27
- function createDiffSchema(description) {
28
- return z
29
- .string()
30
- .min(INPUT_LIMITS.diff.min)
31
- .describe(`${description} Budget is enforced at runtime via MAX_DIFF_CHARS (default 120,000 chars).`);
26
+ function createRepositorySchema() {
27
+ return createBoundedString(INPUT_LIMITS.repository.min, INPUT_LIMITS.repository.max, 'Repository identifier, e.g. org/repo.');
28
+ }
29
+ function createOptionalBoundedInteger(min, max, description) {
30
+ return z.number().int().min(min).max(max).optional().describe(description);
32
31
  }
33
32
  export const FileContextSchema = z.strictObject({
34
33
  path: createBoundedString(INPUT_LIMITS.fileContext.path.min, INPUT_LIMITS.fileContext.path.max, 'File path relative to repo root.'),
35
34
  content: createBoundedString(INPUT_LIMITS.fileContext.content.min, INPUT_LIMITS.fileContext.content.max, 'Full file content.'),
36
35
  });
37
36
  export const AnalyzePrImpactInputSchema = z.strictObject({
38
- diff: createDiffSchema('Unified diff text for the PR or commit.'),
39
- repository: createBoundedString(INPUT_LIMITS.repository.min, INPUT_LIMITS.repository.max, 'Repository identifier, e.g. org/repo.'),
37
+ repository: createRepositorySchema(),
40
38
  language: createLanguageSchema('Primary language to bias analysis.'),
41
39
  });
42
40
  export const GenerateReviewSummaryInputSchema = z.strictObject({
43
- diff: createDiffSchema('Unified diff text for one PR or commit.'),
44
- repository: createBoundedString(INPUT_LIMITS.repository.min, INPUT_LIMITS.repository.max, 'Repository identifier, e.g. org/repo.'),
41
+ repository: createRepositorySchema(),
45
42
  language: createLanguageSchema('Primary implementation language.'),
46
43
  });
47
44
  export const InspectCodeQualityInputSchema = z.strictObject({
48
- diff: createDiffSchema('Unified diff text for in-depth analysis.'),
49
- repository: createBoundedString(INPUT_LIMITS.repository.min, INPUT_LIMITS.repository.max, 'Repository identifier, e.g. org/repo.'),
45
+ repository: createRepositorySchema(),
50
46
  language: createLanguageSchema('Primary language.'),
51
47
  focusAreas: z
52
48
  .array(createBoundedString(INPUT_LIMITS.focusArea.min, INPUT_LIMITS.focusArea.max, 'Focus area tag value.'))
@@ -54,13 +50,7 @@ export const InspectCodeQualityInputSchema = z.strictObject({
54
50
  .max(INPUT_LIMITS.focusArea.maxItems)
55
51
  .optional()
56
52
  .describe('Specific areas to inspect: security, correctness, etc.'),
57
- maxFindings: z
58
- .number()
59
- .int()
60
- .min(1)
61
- .max(25)
62
- .optional()
63
- .describe('Maximum number of findings to return.'),
53
+ maxFindings: createOptionalBoundedInteger(INPUT_LIMITS.maxFindings.min, INPUT_LIMITS.maxFindings.max, 'Maximum number of findings to return.'),
64
54
  files: z
65
55
  .array(FileContextSchema)
66
56
  .min(1)
@@ -69,20 +59,18 @@ export const InspectCodeQualityInputSchema = z.strictObject({
69
59
  .describe('Full file contents for context-aware analysis. Provide the files changed in the diff for best results.'),
70
60
  });
71
61
  export const SuggestSearchReplaceInputSchema = z.strictObject({
72
- diff: createDiffSchema('Unified diff that contains the issue to fix.'),
73
62
  findingTitle: createBoundedString(INPUT_LIMITS.findingTitle.min, INPUT_LIMITS.findingTitle.max, 'Short title of the finding to fix.'),
74
63
  findingDetails: createBoundedString(INPUT_LIMITS.findingDetails.min, INPUT_LIMITS.findingDetails.max, 'Detailed explanation of the bug or risk.'),
75
64
  });
76
65
  export const GenerateTestPlanInputSchema = z.strictObject({
77
- diff: createDiffSchema('Unified diff to generate tests for.'),
78
- repository: createBoundedString(INPUT_LIMITS.repository.min, INPUT_LIMITS.repository.max, 'Repository identifier, e.g. org/repo.'),
66
+ repository: createRepositorySchema(),
79
67
  language: createLanguageSchema('Primary language.'),
80
68
  testFramework: createOptionalBoundedString(INPUT_LIMITS.testFramework.min, INPUT_LIMITS.testFramework.max, 'Test framework to use, e.g. jest, vitest, pytest, node:test.'),
81
- maxTestCases: z
82
- .number()
83
- .int()
84
- .min(INPUT_LIMITS.maxTestCases.min)
85
- .max(INPUT_LIMITS.maxTestCases.max)
86
- .optional()
87
- .describe('Maximum number of test cases to return.'),
69
+ maxTestCases: createOptionalBoundedInteger(INPUT_LIMITS.maxTestCases.min, INPUT_LIMITS.maxTestCases.max, 'Maximum number of test cases to return.'),
70
+ });
71
+ export const AnalyzeComplexityInputSchema = z.strictObject({
72
+ language: createLanguageSchema('Primary language to bias analysis.'),
73
+ });
74
+ export const DetectApiBreakingInputSchema = z.strictObject({
75
+ language: createLanguageSchema('Primary language to bias analysis.'),
88
76
  });
@@ -98,6 +98,7 @@ export declare const CodeQualityResultSchema: z.ZodObject<{
98
98
  contextualInsights: z.ZodArray<z.ZodString>;
99
99
  }, z.core.$strict>;
100
100
  export declare const CodeQualityOutputSchema: z.ZodObject<{
101
+ totalFindings: z.ZodOptional<z.ZodNumber>;
101
102
  summary: z.ZodString;
102
103
  overallRisk: z.ZodEnum<{
103
104
  low: "low";
@@ -120,7 +121,6 @@ export declare const CodeQualityOutputSchema: z.ZodObject<{
120
121
  }, z.core.$strict>>;
121
122
  testsNeeded: z.ZodArray<z.ZodString>;
122
123
  contextualInsights: z.ZodArray<z.ZodString>;
123
- totalFindings: z.ZodOptional<z.ZodNumber>;
124
124
  }, z.core.$strip>;
125
125
  export declare const SearchReplaceBlockSchema: z.ZodObject<{
126
126
  file: z.ZodString;
@@ -180,3 +180,19 @@ export declare const TestPlanResultSchema: z.ZodObject<{
180
180
  }, z.core.$strict>>;
181
181
  coverageSummary: z.ZodString;
182
182
  }, z.core.$strict>;
183
+ export declare const AnalyzeComplexityResultSchema: z.ZodObject<{
184
+ timeComplexity: z.ZodString;
185
+ spaceComplexity: z.ZodString;
186
+ explanation: z.ZodString;
187
+ potentialBottlenecks: z.ZodArray<z.ZodString>;
188
+ isDegradation: z.ZodBoolean;
189
+ }, z.core.$strict>;
190
+ export declare const DetectApiBreakingResultSchema: z.ZodObject<{
191
+ hasBreakingChanges: z.ZodBoolean;
192
+ breakingChanges: z.ZodArray<z.ZodObject<{
193
+ element: z.ZodString;
194
+ natureOfChange: z.ZodString;
195
+ consumerImpact: z.ZodString;
196
+ suggestedMitigation: z.ZodString;
197
+ }, z.core.$strict>>;
198
+ }, z.core.$strict>;
@@ -12,16 +12,30 @@ const OUTPUT_LIMITS = {
12
12
  findingsMax: 50,
13
13
  testsNeeded: { minItems: 0, maxItems: 20, itemMin: 1, itemMax: 300 },
14
14
  },
15
- riskScoreResult: {
16
- score: { min: 0, max: 100 },
17
- rationale: { minItems: 1, maxItems: 20, itemMin: 1, itemMax: 500 },
15
+ complexity: {
16
+ timeComplexity: { min: 1, max: 200 },
17
+ spaceComplexity: { min: 1, max: 200 },
18
+ explanation: { min: 1, max: 2_000 },
19
+ bottleneck: { min: 1, max: 500, maxItems: 10 },
18
20
  },
19
- patchSuggestionResult: {
20
- summary: { min: 1, max: 1_000 },
21
- patch: { min: 1, max: 60_000 },
22
- checklist: { minItems: 1, maxItems: 20, itemMin: 1, itemMax: 300 },
21
+ apiBreaking: {
22
+ element: { min: 1, max: 300 },
23
+ natureOfChange: { min: 1, max: 500 },
24
+ consumerImpact: { min: 1, max: 500 },
25
+ suggestedMitigation: { min: 1, max: 500 },
26
+ maxItems: 20,
23
27
  },
24
28
  };
29
+ const QUALITY_RISK_LEVELS = ['low', 'medium', 'high', 'critical'];
30
+ const MERGE_RISK_LEVELS = ['low', 'medium', 'high'];
31
+ const ERROR_KINDS = [
32
+ 'validation',
33
+ 'budget',
34
+ 'upstream',
35
+ 'timeout',
36
+ 'cancelled',
37
+ 'internal',
38
+ ];
25
39
  function createBoundedString(min, max, description) {
26
40
  return z.string().min(min).max(max).describe(description);
27
41
  }
@@ -32,6 +46,22 @@ function createBoundedStringArray(itemMin, itemMax, minItems, maxItems, descript
32
46
  .max(maxItems)
33
47
  .describe(description);
34
48
  }
49
+ function createReviewSummarySchema(description) {
50
+ return z
51
+ .string()
52
+ .min(OUTPUT_LIMITS.reviewDiffResult.summary.min)
53
+ .max(OUTPUT_LIMITS.reviewDiffResult.summary.max)
54
+ .describe(description);
55
+ }
56
+ const reviewFindingSeveritySchema = z
57
+ .enum(QUALITY_RISK_LEVELS)
58
+ .describe('Severity for this issue.');
59
+ const qualityRiskSchema = z
60
+ .enum(QUALITY_RISK_LEVELS)
61
+ .describe('Overall risk with full context.');
62
+ const mergeRiskSchema = z
63
+ .enum(MERGE_RISK_LEVELS)
64
+ .describe('High-level merge risk.');
35
65
  export const DefaultOutputSchema = z.strictObject({
36
66
  ok: z.boolean().describe('Whether the tool completed successfully.'),
37
67
  result: z.unknown().optional().describe('Successful result payload.'),
@@ -44,14 +74,7 @@ export const DefaultOutputSchema = z.strictObject({
44
74
  .optional()
45
75
  .describe('Whether the client should retry this request.'),
46
76
  kind: z
47
- .enum([
48
- 'validation',
49
- 'budget',
50
- 'upstream',
51
- 'timeout',
52
- 'cancelled',
53
- 'internal',
54
- ])
77
+ .enum(ERROR_KINDS)
55
78
  .optional()
56
79
  .describe('Machine-readable error category.'),
57
80
  })
@@ -59,9 +82,7 @@ export const DefaultOutputSchema = z.strictObject({
59
82
  .describe('Error payload when ok is false.'),
60
83
  });
61
84
  export const ReviewFindingSchema = z.strictObject({
62
- severity: z
63
- .enum(['low', 'medium', 'high', 'critical'])
64
- .describe('Severity for this issue.'),
85
+ severity: reviewFindingSeveritySchema,
65
86
  file: z
66
87
  .string()
67
88
  .min(1)
@@ -75,13 +96,22 @@ export const ReviewFindingSchema = z.strictObject({
75
96
  .nullable()
76
97
  .describe('1-based line number when known, otherwise null.'),
77
98
  title: createBoundedString(OUTPUT_LIMITS.reviewFinding.title.min, OUTPUT_LIMITS.reviewFinding.title.max, 'Short finding title.'),
78
- explanation: createBoundedString(OUTPUT_LIMITS.reviewFinding.text.min, OUTPUT_LIMITS.reviewFinding.text.max, 'Why this issue matters.'),
79
- recommendation: createBoundedString(OUTPUT_LIMITS.reviewFinding.text.min, OUTPUT_LIMITS.reviewFinding.text.max, 'Concrete fix recommendation.'),
99
+ explanation: createBoundedString(OUTPUT_LIMITS.reviewFinding.text.min, OUTPUT_LIMITS.reviewFinding.text.max, 'What the issue is and its runtime, security, or correctness impact.'),
100
+ recommendation: createBoundedString(OUTPUT_LIMITS.reviewFinding.text.min, OUTPUT_LIMITS.reviewFinding.text.max, 'Concrete fix - name the exact code, function, or pattern to change.'),
80
101
  });
102
+ const CODE_QUALITY_SHARED_FIELDS = {
103
+ summary: createReviewSummarySchema('Deep-dive review summary.'),
104
+ overallRisk: qualityRiskSchema,
105
+ findings: z
106
+ .array(ReviewFindingSchema)
107
+ .min(0)
108
+ .max(30)
109
+ .describe('Findings ordered by severity, highest first.'),
110
+ testsNeeded: createBoundedStringArray(1, 300, 0, 12, 'Test cases needed to validate this change.'),
111
+ contextualInsights: createBoundedStringArray(1, 500, 0, 5, 'Cross-file insights only discoverable from the full file context. Omit when no file context was provided.'),
112
+ };
81
113
  export const PrImpactResultSchema = z.strictObject({
82
- severity: z
83
- .enum(['low', 'medium', 'high', 'critical'])
84
- .describe('Overall impact severity.'),
114
+ severity: z.enum(QUALITY_RISK_LEVELS).describe('Overall impact severity.'),
85
115
  categories: z
86
116
  .array(z.enum([
87
117
  'breaking_change',
@@ -106,10 +136,8 @@ export const PrImpactResultSchema = z.strictObject({
106
136
  .describe('Estimated difficulty to revert this change.'),
107
137
  });
108
138
  export const ReviewSummaryResultSchema = z.strictObject({
109
- summary: z.string().min(1).max(2000).describe('Human-readable PR summary.'),
110
- overallRisk: z
111
- .enum(['low', 'medium', 'high'])
112
- .describe('High-level merge risk.'),
139
+ summary: createReviewSummarySchema('Human-readable PR summary.'),
140
+ overallRisk: mergeRiskSchema,
113
141
  keyChanges: createBoundedStringArray(1, 300, 1, 15, 'Most important changes, ordered by significance.'),
114
142
  recommendation: z
115
143
  .string()
@@ -129,30 +157,10 @@ export const ReviewSummaryResultSchema = z.strictObject({
129
157
  .describe('Change statistics (computed from diff before Gemini call).'),
130
158
  });
131
159
  export const CodeQualityResultSchema = z.strictObject({
132
- summary: z.string().min(1).max(2000).describe('Deep-dive review summary.'),
133
- overallRisk: z
134
- .enum(['low', 'medium', 'high', 'critical'])
135
- .describe('Overall risk with full context.'),
136
- findings: z
137
- .array(ReviewFindingSchema)
138
- .min(0)
139
- .max(30)
140
- .describe('Findings ordered by severity, highest first.'),
141
- testsNeeded: createBoundedStringArray(1, 300, 0, 12, 'Test cases needed to validate this change.'),
142
- contextualInsights: createBoundedStringArray(1, 500, 0, 5, 'Insights only possible with full file context that diff alone cannot reveal.'),
160
+ ...CODE_QUALITY_SHARED_FIELDS,
143
161
  });
144
162
  export const CodeQualityOutputSchema = z.object({
145
- summary: z.string().min(1).max(2000).describe('Deep-dive review summary.'),
146
- overallRisk: z
147
- .enum(['low', 'medium', 'high', 'critical'])
148
- .describe('Overall risk with full context.'),
149
- findings: z
150
- .array(ReviewFindingSchema)
151
- .min(0)
152
- .max(30)
153
- .describe('Findings ordered by severity, highest first.'),
154
- testsNeeded: createBoundedStringArray(1, 300, 0, 12, 'Test cases needed to validate this change.'),
155
- contextualInsights: createBoundedStringArray(1, 500, 0, 5, 'Insights only possible with full file context that diff alone cannot reveal.'),
163
+ ...CODE_QUALITY_SHARED_FIELDS,
156
164
  totalFindings: z
157
165
  .number()
158
166
  .int()
@@ -166,17 +174,17 @@ export const SearchReplaceBlockSchema = z.strictObject({
166
174
  .string()
167
175
  .min(1)
168
176
  .max(5000)
169
- .describe('Exact verbatim text to find in the file.'),
177
+ .describe('Verbatim source text to find - character-exact including all whitespace and indentation.'),
170
178
  replace: z
171
179
  .string()
172
180
  .min(0)
173
181
  .max(5000)
174
- .describe('Replacement text (empty string = deletion).'),
182
+ .describe('Replacement text. Use empty string to delete.'),
175
183
  explanation: z
176
184
  .string()
177
185
  .min(1)
178
186
  .max(500)
179
- .describe('Why this change fixes the finding.'),
187
+ .describe('Why this patch fixes the finding.'),
180
188
  });
181
189
  export const SearchReplaceResultSchema = z.strictObject({
182
190
  summary: z.string().min(1).max(1000).describe('What the fix accomplishes.'),
@@ -227,3 +235,27 @@ export const TestPlanResultSchema = z.strictObject({
227
235
  .max(500)
228
236
  .describe('Summary of coverage gaps this plan addresses.'),
229
237
  });
238
+ export const AnalyzeComplexityResultSchema = z.strictObject({
239
+ timeComplexity: createBoundedString(OUTPUT_LIMITS.complexity.timeComplexity.min, OUTPUT_LIMITS.complexity.timeComplexity.max, 'Big-O time complexity of the changed code (e.g., O(n log n)).'),
240
+ spaceComplexity: createBoundedString(OUTPUT_LIMITS.complexity.spaceComplexity.min, OUTPUT_LIMITS.complexity.spaceComplexity.max, 'Big-O space complexity of the changed code (e.g., O(n)).'),
241
+ explanation: createBoundedString(OUTPUT_LIMITS.complexity.explanation.min, OUTPUT_LIMITS.complexity.explanation.max, 'Detailed explanation of the complexity analysis, including key factors such as loop nesting and recursive calls.'),
242
+ potentialBottlenecks: createBoundedStringArray(OUTPUT_LIMITS.complexity.bottleneck.min, OUTPUT_LIMITS.complexity.bottleneck.max, 0, OUTPUT_LIMITS.complexity.bottleneck.maxItems, 'Potential performance bottlenecks identified in the change.'),
243
+ isDegradation: z
244
+ .boolean()
245
+ .describe('True if the change introduces a performance degradation compared to the original code.'),
246
+ });
247
+ export const DetectApiBreakingResultSchema = z.strictObject({
248
+ hasBreakingChanges: z
249
+ .boolean()
250
+ .describe('True if any breaking changes were detected.'),
251
+ breakingChanges: z
252
+ .array(z.strictObject({
253
+ element: createBoundedString(OUTPUT_LIMITS.apiBreaking.element.min, OUTPUT_LIMITS.apiBreaking.element.max, 'Name of the API element that changed (e.g., function signature, interface field, exported constant).'),
254
+ natureOfChange: createBoundedString(OUTPUT_LIMITS.apiBreaking.natureOfChange.min, OUTPUT_LIMITS.apiBreaking.natureOfChange.max, 'What changed and why it is breaking (e.g., removed parameter, changed return type, renamed export).'),
255
+ consumerImpact: createBoundedString(OUTPUT_LIMITS.apiBreaking.consumerImpact.min, OUTPUT_LIMITS.apiBreaking.consumerImpact.max, 'How existing consumers will be affected by this change.'),
256
+ suggestedMitigation: createBoundedString(OUTPUT_LIMITS.apiBreaking.suggestedMitigation.min, OUTPUT_LIMITS.apiBreaking.suggestedMitigation.max, 'Recommended mitigation strategy for consumers impacted by this breaking change.'),
257
+ }))
258
+ .min(0)
259
+ .max(OUTPUT_LIMITS.apiBreaking.maxItems)
260
+ .describe('List of breaking changes detected. Empty when hasBreakingChanges is false.'),
261
+ });
package/dist/server.js CHANGED
@@ -2,22 +2,20 @@ import { InMemoryTaskStore } from '@modelcontextprotocol/sdk/experimental/tasks/
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { findPackageJSON } from 'node:module';
5
- import { dirname, join } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
5
+ import { initDiffStore } from './lib/diff-store.js';
7
6
  import { getErrorMessage } from './lib/errors.js';
8
7
  import { registerAllPrompts } from './prompts/index.js';
9
8
  import { registerAllResources } from './resources/index.js';
9
+ import { buildServerInstructions } from './resources/instructions.js';
10
10
  import { registerAllTools } from './tools/index.js';
11
11
  const SERVER_NAME = 'code-review-analyst';
12
- const INSTRUCTIONS_FILENAME = 'instructions.md';
13
- const INSTRUCTIONS_FALLBACK = '(Instructions failed to load)';
14
12
  const UTF8_ENCODING = 'utf8';
15
- const CURRENT_DIR = dirname(fileURLToPath(import.meta.url));
16
- const INSTRUCTIONS_PATH = join(CURRENT_DIR, INSTRUCTIONS_FILENAME);
13
+ const PACKAGE_VERSION_FIELD = 'version';
14
+ const VERSION_FIELD_ERROR = 'missing or invalid version field';
17
15
  const SERVER_CAPABILITIES = {
18
16
  logging: {},
19
17
  completions: {},
20
- resources: {},
18
+ resources: { subscribe: true },
21
19
  tools: {},
22
20
  tasks: {
23
21
  list: {},
@@ -32,22 +30,27 @@ const SERVER_CAPABILITIES = {
32
30
  function isPackageJsonMetadata(value) {
33
31
  return (typeof value === 'object' &&
34
32
  value !== null &&
35
- 'version' in value &&
33
+ PACKAGE_VERSION_FIELD in value &&
36
34
  typeof value.version === 'string' &&
37
35
  value.version.trim().length > 0);
38
36
  }
39
37
  function parsePackageJson(packageJson, packageJsonPath) {
40
- let parsed;
38
+ const parsed = parseJsonText(packageJson, packageJsonPath);
39
+ ensurePackageJsonMetadata(parsed, packageJsonPath);
40
+ return parsed;
41
+ }
42
+ function parseJsonText(jsonText, sourcePath) {
41
43
  try {
42
- parsed = JSON.parse(packageJson);
44
+ return JSON.parse(jsonText);
43
45
  }
44
46
  catch (error) {
45
- throw new Error(`Invalid JSON in ${packageJsonPath}: ${getErrorMessage(error)}`);
47
+ throw new Error(`Invalid JSON in ${sourcePath}: ${getErrorMessage(error)}`);
46
48
  }
47
- if (!isPackageJsonMetadata(parsed)) {
48
- throw new Error(`Invalid package.json at ${packageJsonPath}: missing or invalid version field`);
49
+ }
50
+ function ensurePackageJsonMetadata(metadata, packageJsonPath) {
51
+ if (!isPackageJsonMetadata(metadata)) {
52
+ throw new Error(`Invalid package.json at ${packageJsonPath}: ${VERSION_FIELD_ERROR}`);
49
53
  }
50
- return parsed;
51
54
  }
52
55
  function readUtf8File(path) {
53
56
  try {
@@ -66,19 +69,9 @@ function loadVersion() {
66
69
  return parsePackageJson(packageJsonText, packageJsonPath).version;
67
70
  }
68
71
  const SERVER_VERSION = loadVersion();
69
- function loadInstructions() {
70
- try {
71
- return readUtf8File(INSTRUCTIONS_PATH);
72
- }
73
- catch (error) {
74
- process.emitWarning(`Failed to load ${INSTRUCTIONS_FILENAME}: ${getErrorMessage(error)}`);
75
- return INSTRUCTIONS_FALLBACK;
76
- }
77
- }
78
- const SERVER_INSTRUCTIONS = loadInstructions();
79
- export function createServer() {
80
- const taskStore = new InMemoryTaskStore();
81
- const server = new McpServer({
72
+ const SERVER_INSTRUCTIONS = buildServerInstructions();
73
+ function createMcpServer(taskStore) {
74
+ return new McpServer({
82
75
  name: SERVER_NAME,
83
76
  version: SERVER_VERSION,
84
77
  }, {
@@ -86,9 +79,17 @@ export function createServer() {
86
79
  taskStore,
87
80
  capabilities: SERVER_CAPABILITIES,
88
81
  });
82
+ }
83
+ function registerServerCapabilities(server) {
84
+ initDiffStore(server);
89
85
  registerAllTools(server);
90
86
  registerAllResources(server, SERVER_INSTRUCTIONS);
91
87
  registerAllPrompts(server, SERVER_INSTRUCTIONS);
88
+ }
89
+ export function createServer() {
90
+ const taskStore = new InMemoryTaskStore();
91
+ const server = createMcpServer(taskStore);
92
+ registerServerCapabilities(server);
92
93
  const shutdown = async () => {
93
94
  await server.close();
94
95
  taskStore.cleanup();
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAnalyzeComplexityTool(server: McpServer): void;
@@ -0,0 +1,51 @@
1
+ import { validateDiffBudget } from '../lib/diff-budget.js';
2
+ import { createNoDiffError, getDiff } from '../lib/diff-store.js';
3
+ import { requireToolContract } from '../lib/tool-contracts.js';
4
+ import { registerStructuredToolTask } from '../lib/tool-factory.js';
5
+ import { AnalyzeComplexityInputSchema } from '../schemas/inputs.js';
6
+ import { AnalyzeComplexityResultSchema } from '../schemas/outputs.js';
7
+ const SYSTEM_INSTRUCTION = `
8
+ You are an algorithm complexity analyst. Analyze the diff to identify Big-O time and space complexity for added or modified functions and algorithms.
9
+ Detect if the change introduces a performance degradation compared to the original code.
10
+ Identify potential bottlenecks arising from loop nesting, recursive calls, and auxiliary data structure usage.
11
+ Return strict JSON only.
12
+ `;
13
+ const TOOL_CONTRACT = requireToolContract('analyze_time_space_complexity');
14
+ export function registerAnalyzeComplexityTool(server) {
15
+ registerStructuredToolTask(server, {
16
+ name: 'analyze_time_space_complexity',
17
+ title: 'Analyze Time & Space Complexity',
18
+ description: 'Analyze Big-O complexity of the cached diff changes. Call generate_diff first.',
19
+ inputSchema: AnalyzeComplexityInputSchema,
20
+ fullInputSchema: AnalyzeComplexityInputSchema,
21
+ resultSchema: AnalyzeComplexityResultSchema,
22
+ errorCode: 'E_ANALYZE_COMPLEXITY',
23
+ model: TOOL_CONTRACT.model,
24
+ timeoutMs: TOOL_CONTRACT.timeoutMs,
25
+ maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
26
+ ...(TOOL_CONTRACT.thinkingBudget !== undefined
27
+ ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
28
+ : undefined),
29
+ validateInput: () => {
30
+ const slot = getDiff();
31
+ if (!slot)
32
+ return createNoDiffError();
33
+ return validateDiffBudget(slot.diff);
34
+ },
35
+ formatOutcome: (result) => result.isDegradation
36
+ ? 'Performance degradation detected'
37
+ : 'No degradation',
38
+ formatOutput: (result) => `Complexity Analysis: Time=${result.timeComplexity}, Space=${result.spaceComplexity}. ${result.explanation}`,
39
+ buildPrompt: (input) => {
40
+ const slot = getDiff();
41
+ const diff = slot?.diff ?? '';
42
+ const languageLine = input.language
43
+ ? `\nLanguage: ${input.language}`
44
+ : '';
45
+ return {
46
+ systemInstruction: SYSTEM_INSTRUCTION,
47
+ prompt: `${languageLine}\nDiff:\n${diff}`.trimStart(),
48
+ };
49
+ },
50
+ });
51
+ }
@@ -1,46 +1,58 @@
1
1
  import { validateDiffBudget } from '../lib/diff-budget.js';
2
2
  import { computeDiffStatsAndSummaryFromFiles, parseDiffFiles, } from '../lib/diff-parser.js';
3
- import { DEFAULT_LANGUAGE, FLASH_MODEL } from '../lib/model-config.js';
3
+ import { createNoDiffError, getDiff } from '../lib/diff-store.js';
4
+ import { requireToolContract } from '../lib/tool-contracts.js';
4
5
  import { registerStructuredToolTask } from '../lib/tool-factory.js';
5
6
  import { AnalyzePrImpactInputSchema } from '../schemas/inputs.js';
6
7
  import { PrImpactResultSchema } from '../schemas/outputs.js';
7
8
  const SYSTEM_INSTRUCTION = `
8
- You are a technical change analyst.
9
- Your goal is to identify observable facts about what changed and their downstream effects.
10
- Analyze the provided Unified Diff and determine its impact severity and categories.
11
- Focus on breaking changes, API modifications, and rollback complexity.
9
+ You are a technical change analyst. Analyze the diff for objective, evidence-based impact assessment.
10
+ Classify severity, categories, breaking changes, affected areas, and rollback complexity strictly from diff evidence.
11
+ Never infer behavior not visible in the diff.
12
12
  Return strict JSON only.
13
13
  `;
14
+ const TOOL_CONTRACT = requireToolContract('analyze_pr_impact');
15
+ function formatLanguageSegment(language) {
16
+ return language ? `\nLanguage: ${language}` : '';
17
+ }
14
18
  export function registerAnalyzePrImpactTool(server) {
15
19
  registerStructuredToolTask(server, {
16
20
  name: 'analyze_pr_impact',
17
21
  title: 'Analyze PR Impact',
18
- description: 'Assess the impact and risk of a pull request diff.',
22
+ description: 'Assess the impact and risk of the cached diff. Call generate_diff first.',
19
23
  inputSchema: AnalyzePrImpactInputSchema,
20
24
  fullInputSchema: AnalyzePrImpactInputSchema,
21
25
  resultSchema: PrImpactResultSchema,
22
26
  errorCode: 'E_ANALYZE_IMPACT',
23
- model: FLASH_MODEL,
24
- validateInput: (input) => validateDiffBudget(input.diff),
25
- formatOutcome: (result) => `severity: ${result.severity}`,
26
- formatOutput: (result) => {
27
- return `Impact Analysis (${result.severity}): ${result.summary}`;
27
+ model: TOOL_CONTRACT.model,
28
+ timeoutMs: TOOL_CONTRACT.timeoutMs,
29
+ maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
30
+ validateInput: () => {
31
+ const slot = getDiff();
32
+ if (!slot)
33
+ return createNoDiffError();
34
+ return validateDiffBudget(slot.diff);
28
35
  },
36
+ formatOutcome: (result) => `severity: ${result.severity}`,
37
+ formatOutput: (result) => `Impact Analysis (${result.severity}): ${result.summary}`,
29
38
  buildPrompt: (input) => {
30
- const files = parseDiffFiles(input.diff);
31
- const insights = computeDiffStatsAndSummaryFromFiles(files);
32
- const { stats, summary: fileSummary } = insights;
33
- const prompt = `
34
- Repository: ${input.repository}
35
- Language: ${input.language ?? DEFAULT_LANGUAGE}
39
+ const slot = getDiff();
40
+ const diff = slot?.diff ?? '';
41
+ const files = parseDiffFiles(diff);
42
+ const { stats, summary: fileSummary } = computeDiffStatsAndSummaryFromFiles(files);
43
+ const languageSegment = formatLanguageSegment(input.language);
44
+ return {
45
+ systemInstruction: SYSTEM_INSTRUCTION,
46
+ prompt: `
47
+ Repository: ${input.repository}${languageSegment}
36
48
  Change Stats: ${stats.files} files, +${stats.added} lines, -${stats.deleted} lines.
37
49
  Changed Files:
38
50
  ${fileSummary}
39
51
 
40
52
  Diff:
41
- ${input.diff}
42
- `;
43
- return { systemInstruction: SYSTEM_INSTRUCTION, prompt };
53
+ ${diff}
54
+ `,
55
+ };
44
56
  },
45
57
  });
46
58
  }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerDetectApiBreakingTool(server: McpServer): void;