review-council 1.0.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/dist/config/defaults.d.ts +17 -0
  4. package/dist/config/defaults.js +27 -0
  5. package/dist/config/defaults.js.map +1 -0
  6. package/dist/config/loader.d.ts +2 -0
  7. package/dist/config/loader.js +66 -0
  8. package/dist/config/loader.js.map +1 -0
  9. package/dist/config/schema.d.ts +79 -0
  10. package/dist/config/schema.js +49 -0
  11. package/dist/config/schema.js.map +1 -0
  12. package/dist/consensus/deduper.d.ts +6 -0
  13. package/dist/consensus/deduper.js +127 -0
  14. package/dist/consensus/deduper.js.map +1 -0
  15. package/dist/consensus/parser.d.ts +6 -0
  16. package/dist/consensus/parser.js +90 -0
  17. package/dist/consensus/parser.js.map +1 -0
  18. package/dist/consensus/types.d.ts +57 -0
  19. package/dist/consensus/types.js +2 -0
  20. package/dist/consensus/types.js.map +1 -0
  21. package/dist/consensus/voter.d.ts +3 -0
  22. package/dist/consensus/voter.js +198 -0
  23. package/dist/consensus/voter.js.map +1 -0
  24. package/dist/dispatch/adapter.d.ts +15 -0
  25. package/dist/dispatch/adapter.js +24 -0
  26. package/dist/dispatch/adapter.js.map +1 -0
  27. package/dist/dispatch/anthropic.d.ts +9 -0
  28. package/dist/dispatch/anthropic.js +132 -0
  29. package/dist/dispatch/anthropic.js.map +1 -0
  30. package/dist/dispatch/google.d.ts +9 -0
  31. package/dist/dispatch/google.js +87 -0
  32. package/dist/dispatch/google.js.map +1 -0
  33. package/dist/dispatch/openai-compat.d.ts +18 -0
  34. package/dist/dispatch/openai-compat.js +90 -0
  35. package/dist/dispatch/openai-compat.js.map +1 -0
  36. package/dist/dispatch/openai.d.ts +9 -0
  37. package/dist/dispatch/openai.js +90 -0
  38. package/dist/dispatch/openai.js.map +1 -0
  39. package/dist/dispatch/runner.d.ts +11 -0
  40. package/dist/dispatch/runner.js +67 -0
  41. package/dist/dispatch/runner.js.map +1 -0
  42. package/dist/dispatch/utils.d.ts +6 -0
  43. package/dist/dispatch/utils.js +15 -0
  44. package/dist/dispatch/utils.js.map +1 -0
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +262 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/output/github.d.ts +3 -0
  49. package/dist/output/github.js +95 -0
  50. package/dist/output/github.js.map +1 -0
  51. package/dist/output/json.d.ts +3 -0
  52. package/dist/output/json.js +8 -0
  53. package/dist/output/json.js.map +1 -0
  54. package/dist/output/markdown.d.ts +3 -0
  55. package/dist/output/markdown.js +81 -0
  56. package/dist/output/markdown.js.map +1 -0
  57. package/dist/output/terminal.d.ts +2 -0
  58. package/dist/output/terminal.js +141 -0
  59. package/dist/output/terminal.js.map +1 -0
  60. package/dist/prepare/chunker.d.ts +9 -0
  61. package/dist/prepare/chunker.js +62 -0
  62. package/dist/prepare/chunker.js.map +1 -0
  63. package/dist/prepare/language.d.ts +1 -0
  64. package/dist/prepare/language.js +99 -0
  65. package/dist/prepare/language.js.map +1 -0
  66. package/dist/prepare/prompt-builder.d.ts +11 -0
  67. package/dist/prepare/prompt-builder.js +65 -0
  68. package/dist/prepare/prompt-builder.js.map +1 -0
  69. package/dist/prompts/base.d.ts +3 -0
  70. package/dist/prompts/base.js +53 -0
  71. package/dist/prompts/base.js.map +1 -0
  72. package/dist/prompts/hardening.d.ts +7 -0
  73. package/dist/prompts/hardening.js +30 -0
  74. package/dist/prompts/hardening.js.map +1 -0
  75. package/dist/prompts/languages/elixir.d.ts +1 -0
  76. package/dist/prompts/languages/elixir.js +13 -0
  77. package/dist/prompts/languages/elixir.js.map +1 -0
  78. package/dist/prompts/languages/generic.d.ts +1 -0
  79. package/dist/prompts/languages/generic.js +11 -0
  80. package/dist/prompts/languages/generic.js.map +1 -0
  81. package/dist/prompts/languages/python.d.ts +1 -0
  82. package/dist/prompts/languages/python.js +14 -0
  83. package/dist/prompts/languages/python.js.map +1 -0
  84. package/dist/prompts/languages/typescript.d.ts +1 -0
  85. package/dist/prompts/languages/typescript.js +13 -0
  86. package/dist/prompts/languages/typescript.js.map +1 -0
  87. package/dist/resolver/github.d.ts +8 -0
  88. package/dist/resolver/github.js +70 -0
  89. package/dist/resolver/github.js.map +1 -0
  90. package/dist/resolver/local.d.ts +3 -0
  91. package/dist/resolver/local.js +75 -0
  92. package/dist/resolver/local.js.map +1 -0
  93. package/dist/resolver/types.d.ts +28 -0
  94. package/dist/resolver/types.js +2 -0
  95. package/dist/resolver/types.js.map +1 -0
  96. package/dist/roles/builtin.d.ts +4 -0
  97. package/dist/roles/builtin.js +234 -0
  98. package/dist/roles/builtin.js.map +1 -0
  99. package/dist/roles/dispatcher.d.ts +23 -0
  100. package/dist/roles/dispatcher.js +87 -0
  101. package/dist/roles/dispatcher.js.map +1 -0
  102. package/dist/roles/loader.d.ts +11 -0
  103. package/dist/roles/loader.js +107 -0
  104. package/dist/roles/loader.js.map +1 -0
  105. package/dist/roles/types.d.ts +13 -0
  106. package/dist/roles/types.js +2 -0
  107. package/dist/roles/types.js.map +1 -0
  108. package/package.json +67 -0
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+ const FindingSchema = z.object({
3
+ id: z.string(),
4
+ file: z.string(),
5
+ startLine: z.number().int().nonnegative(),
6
+ endLine: z.number().int().nonnegative(),
7
+ severity: z.enum(['critical', 'important', 'minor', 'nitpick']),
8
+ category: z.enum(['security', 'correctness', 'best-practices', 'tests', 'api-design']),
9
+ title: z.string().max(200),
10
+ description: z.string(),
11
+ suggestedFix: z.string().optional(),
12
+ });
13
+ const ReviewOutputSchema = z.object({
14
+ findings: z.array(FindingSchema),
15
+ });
16
+ /**
17
+ * Extract JSON from a string that may have prose around it
18
+ */
19
+ function extractJson(text) {
20
+ // Try direct parse first
21
+ const trimmed = text.trim();
22
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
23
+ return trimmed;
24
+ }
25
+ // Try to find JSON block in markdown code fences
26
+ const fenceMatch = text.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
27
+ if (fenceMatch) {
28
+ return fenceMatch[1].trim();
29
+ }
30
+ // Try to find the first { ... } block
31
+ const start = text.indexOf('{');
32
+ const end = text.lastIndexOf('}');
33
+ if (start >= 0 && end > start) {
34
+ return text.slice(start, end + 1);
35
+ }
36
+ return null;
37
+ }
38
+ export function parseReviewOutput(rawOutput, model, role) {
39
+ const warnings = [];
40
+ if (!rawOutput || rawOutput.trim().length === 0) {
41
+ warnings.push(`${model}/${role}: empty output`);
42
+ return { findings: [], warnings };
43
+ }
44
+ const jsonText = extractJson(rawOutput);
45
+ if (!jsonText) {
46
+ warnings.push(`${model}/${role}: could not extract JSON from output`);
47
+ return { findings: [], warnings };
48
+ }
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(jsonText);
52
+ }
53
+ catch (err) {
54
+ warnings.push(`${model}/${role}: JSON parse error: ${String(err)}`);
55
+ return { findings: [], warnings };
56
+ }
57
+ const result = ReviewOutputSchema.safeParse(parsed);
58
+ if (!result.success) {
59
+ // Try to salvage individual findings
60
+ if (typeof parsed === 'object' &&
61
+ parsed !== null &&
62
+ 'findings' in parsed &&
63
+ Array.isArray(parsed.findings)) {
64
+ const rawFindings = parsed.findings;
65
+ const salvaged = [];
66
+ for (const item of rawFindings) {
67
+ const itemResult = FindingSchema.safeParse(item);
68
+ if (itemResult.success) {
69
+ salvaged.push(itemResult.data);
70
+ }
71
+ else {
72
+ warnings.push(`${model}/${role}: dropped malformed finding: ${JSON.stringify(item).slice(0, 100)}`);
73
+ }
74
+ }
75
+ if (salvaged.length > 0) {
76
+ warnings.push(`${model}/${role}: schema validation errors (salvaged ${salvaged.length} findings)`);
77
+ return { findings: salvaged, warnings };
78
+ }
79
+ }
80
+ warnings.push(`${model}/${role}: schema validation failed: ${result.error.issues.map((e) => e.message).join(', ')}`);
81
+ return { findings: [], warnings };
82
+ }
83
+ // Assign stable IDs if missing or colliding
84
+ const findings = result.data.findings.map((f, i) => ({
85
+ ...f,
86
+ id: f.id || `${model.replace(/[^a-z0-9]/gi, '')}_${role}_${i}`,
87
+ }));
88
+ return { findings, warnings };
89
+ }
90
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/consensus/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ export interface Finding {
2
+ id: string;
3
+ file: string;
4
+ startLine: number;
5
+ endLine: number;
6
+ severity: 'critical' | 'important' | 'minor' | 'nitpick';
7
+ category: 'security' | 'correctness' | 'best-practices' | 'tests' | 'api-design';
8
+ title: string;
9
+ description: string;
10
+ suggestedFix?: string;
11
+ }
12
+ export interface ModelReview {
13
+ model: string;
14
+ role: string;
15
+ provider: string;
16
+ findings: Finding[];
17
+ durationMs: number;
18
+ status: 'success' | 'timeout' | 'error';
19
+ error?: string;
20
+ }
21
+ export interface ConsensusInfo {
22
+ score: number;
23
+ total: number;
24
+ models: string[];
25
+ roles: string[];
26
+ crossRole: boolean;
27
+ crossModel: boolean;
28
+ elevated: boolean;
29
+ original_severity?: string;
30
+ elevation: 'none' | 'cross-role' | 'cross-model' | 'unanimous';
31
+ confidence: number;
32
+ confidenceLabel: 'Very High' | 'High' | 'Medium' | 'Low' | 'Minimal';
33
+ disputed?: boolean;
34
+ disputeDetails?: string;
35
+ }
36
+ export interface ConsensusFinding extends Finding {
37
+ consensus: ConsensusInfo;
38
+ }
39
+ export interface DeduplicatedGroup {
40
+ representative: Finding;
41
+ members: Array<{
42
+ finding: Finding;
43
+ model: string;
44
+ role: string;
45
+ }>;
46
+ }
47
+ export interface ReviewResult {
48
+ reviews: ModelReview[];
49
+ findings: ConsensusFinding[];
50
+ stats: {
51
+ totalReviews: number;
52
+ successfulReviews: number;
53
+ totalRawFindings: number;
54
+ totalDeduped: number;
55
+ durationMs: number;
56
+ };
57
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/consensus/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { ModelReview, ConsensusFinding, DeduplicatedGroup } from './types.js';
2
+ import type { Role } from '../roles/types.js';
3
+ export declare function computeConsensus(groups: DeduplicatedGroup[], reviews: ModelReview[], roleMap: Map<string, Role>): ConsensusFinding[];
@@ -0,0 +1,198 @@
1
+ import { CONFIDENCE_THRESHOLDS } from '../config/defaults.js';
2
+ const SEVERITY_LEVELS = ['critical', 'important', 'minor', 'nitpick'];
3
+ function severityIndex(s) {
4
+ return SEVERITY_LEVELS.indexOf(s);
5
+ }
6
+ function indexToSeverity(i) {
7
+ const clamped = Math.max(0, Math.min(SEVERITY_LEVELS.length - 1, i));
8
+ return SEVERITY_LEVELS[clamped];
9
+ }
10
+ function bumpSeverity(severity, bumps) {
11
+ const idx = severityIndex(severity);
12
+ return indexToSeverity(idx - bumps); // lower index = higher severity
13
+ }
14
+ function confidenceLabel(score) {
15
+ if (score >= CONFIDENCE_THRESHOLDS.veryHigh)
16
+ return 'Very High';
17
+ if (score >= CONFIDENCE_THRESHOLDS.high)
18
+ return 'High';
19
+ if (score >= CONFIDENCE_THRESHOLDS.medium)
20
+ return 'Medium';
21
+ if (score >= CONFIDENCE_THRESHOLDS.low)
22
+ return 'Low';
23
+ return 'Minimal';
24
+ }
25
+ /**
26
+ * Layer 2: Diversity score
27
+ * = (unique_models / total_models) * 0.5 + (unique_roles / total_roles) * 0.5
28
+ */
29
+ function computeDiversity(group, allModels, allRoles) {
30
+ const uniqueModels = new Set(group.members.map((m) => m.model));
31
+ const uniqueRoles = new Set(group.members.map((m) => m.role));
32
+ const modelDiversity = allModels.length > 0 ? uniqueModels.size / allModels.length : 0;
33
+ const roleDiversity = allRoles.length > 0 ? uniqueRoles.size / allRoles.length : 0;
34
+ return modelDiversity * 0.5 + roleDiversity * 0.5;
35
+ }
36
+ /**
37
+ * Layer 2: Relevance score
38
+ * For each reporter's role, check if this category is in their focus area.
39
+ * Expected finding from focused role = 0.5, unexpected = 1.0; take mean.
40
+ */
41
+ function computeRelevance(group, roleMap) {
42
+ if (group.members.length === 0)
43
+ return 0.5;
44
+ const scores = group.members.map(({ role: roleName }) => {
45
+ const role = roleMap.get(roleName);
46
+ if (!role)
47
+ return 1.0; // unknown role — treat as unexpected = high signal
48
+ const category = group.representative.category;
49
+ const isExpected = role.focus.includes(category) ||
50
+ role.focus.some((f) => f.includes(category) || category.includes(f));
51
+ return isExpected ? 0.5 : 1.0;
52
+ });
53
+ return scores.reduce((sum, s) => sum + s, 0) / scores.length;
54
+ }
55
+ /**
56
+ * Layer 2: Isolation score
57
+ * = flagged_relevant / (flagged_relevant + missed_relevant)
58
+ *
59
+ * "relevant" reviewers = those whose role.focus includes this category
60
+ * flagged_relevant = relevant reviewers who flagged this finding
61
+ * missed_relevant = relevant reviewers who did NOT flag this finding
62
+ */
63
+ function computeIsolation(group, reviews, roleMap) {
64
+ const category = group.representative.category;
65
+ // Find all reviewers whose role is focused on this category
66
+ const relevantReviewers = reviews.filter((r) => {
67
+ if (r.status !== 'success')
68
+ return false;
69
+ const role = roleMap.get(r.role);
70
+ if (!role)
71
+ return false;
72
+ return (role.focus.includes(category) ||
73
+ role.focus.some((f) => f.includes(category) || category.includes(f)));
74
+ });
75
+ if (relevantReviewers.length === 0) {
76
+ // No focused reviewers — neutral score
77
+ return 0.5;
78
+ }
79
+ const flaggedModels = new Set(group.members.map((m) => `${m.model}::${m.role}`));
80
+ let flaggedRelevant = 0;
81
+ let missedRelevant = 0;
82
+ for (const reviewer of relevantReviewers) {
83
+ const key = `${reviewer.model}::${reviewer.role}`;
84
+ if (flaggedModels.has(key)) {
85
+ flaggedRelevant++;
86
+ }
87
+ else {
88
+ missedRelevant++;
89
+ }
90
+ }
91
+ const total = flaggedRelevant + missedRelevant;
92
+ return total > 0 ? flaggedRelevant / total : 0.5;
93
+ }
94
+ /**
95
+ * Layer 5: Detect opposing/disputed findings at the same location
96
+ */
97
+ function detectDisputes(group, allGroups) {
98
+ const rep = group.representative;
99
+ // Look for another group at the same location with a contradicting title/description
100
+ for (const other of allGroups) {
101
+ if (other === group)
102
+ continue;
103
+ if (other.representative.file !== rep.file)
104
+ continue;
105
+ // Check if they're at the same location
106
+ const lineDiff = Math.abs(other.representative.startLine - rep.startLine);
107
+ if (lineDiff > 5)
108
+ continue;
109
+ // Check for opposing sentiment in titles
110
+ const aTitle = rep.title.toLowerCase();
111
+ const bTitle = other.representative.title.toLowerCase();
112
+ const opposingPairs = [
113
+ ['missing', 'present'],
114
+ ['no ', 'has '],
115
+ ['lacks', 'has'],
116
+ ['not ', 'is '],
117
+ ['should add', 'should remove'],
118
+ ];
119
+ for (const [posA, posB] of opposingPairs) {
120
+ if ((aTitle.includes(posA) && bTitle.includes(posB)) ||
121
+ (aTitle.includes(posB) && bTitle.includes(posA))) {
122
+ return {
123
+ disputed: true,
124
+ disputeDetails: `Conflicting finding at same location: "${rep.title}" vs "${other.representative.title}"`,
125
+ };
126
+ }
127
+ }
128
+ }
129
+ return { disputed: false };
130
+ }
131
+ export function computeConsensus(groups, reviews, roleMap) {
132
+ const allModels = [...new Set(reviews.filter((r) => r.status === 'success').map((r) => r.model))];
133
+ const allRoles = [...new Set(reviews.filter((r) => r.status === 'success').map((r) => r.role))];
134
+ return groups.map((group) => {
135
+ const rep = group.representative;
136
+ // Layer 2: Signal scoring
137
+ const diversity = computeDiversity(group, allModels, allRoles);
138
+ const relevance = computeRelevance(group, roleMap);
139
+ const isolation = computeIsolation(group, reviews, roleMap);
140
+ // Layer 3: Raw confidence
141
+ const rawConfidence = diversity * 0.4 + relevance * 0.3 + isolation * 0.3;
142
+ const clampedConfidence = Math.min(1, Math.max(0, rawConfidence));
143
+ const label = confidenceLabel(clampedConfidence);
144
+ // Layer 4: Severity elevation
145
+ let elevatedSeverity = rep.severity;
146
+ let bumps = 0;
147
+ const elevation = (() => {
148
+ const uniqueModels = new Set(group.members.map((m) => m.model));
149
+ const uniqueRoles = new Set(group.members.map((m) => m.role));
150
+ if (label === 'Very High' &&
151
+ group.members.length >= 3) {
152
+ bumps = 2;
153
+ return 'unanimous';
154
+ }
155
+ if (label === 'High' || label === 'Very High') {
156
+ if (uniqueModels.size >= 2 && uniqueRoles.size >= 2) {
157
+ bumps = 1;
158
+ return 'cross-model';
159
+ }
160
+ if (uniqueRoles.size >= 2) {
161
+ bumps = 1;
162
+ return 'cross-role';
163
+ }
164
+ }
165
+ return 'none';
166
+ })();
167
+ if (bumps > 0) {
168
+ elevatedSeverity = bumpSeverity(rep.severity, bumps);
169
+ }
170
+ const elevated = elevatedSeverity !== rep.severity;
171
+ const original_severity = elevated ? rep.severity : undefined;
172
+ // Layer 5: Dispute detection
173
+ const { disputed, disputeDetails } = detectDisputes(group, groups);
174
+ const uniqueModels = [...new Set(group.members.map((m) => m.model))];
175
+ const uniqueRoles = [...new Set(group.members.map((m) => m.role))];
176
+ const consensus = {
177
+ score: group.members.length,
178
+ total: reviews.filter((r) => r.status === 'success').length,
179
+ models: uniqueModels,
180
+ roles: uniqueRoles,
181
+ crossRole: uniqueRoles.length >= 2,
182
+ crossModel: uniqueModels.length >= 2,
183
+ elevated,
184
+ original_severity,
185
+ elevation,
186
+ confidence: clampedConfidence,
187
+ confidenceLabel: label,
188
+ disputed: disputed || undefined,
189
+ disputeDetails,
190
+ };
191
+ return {
192
+ ...rep,
193
+ severity: elevatedSeverity,
194
+ consensus,
195
+ };
196
+ });
197
+ }
198
+ //# sourceMappingURL=voter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voter.js","sourceRoot":"","sources":["../../src/consensus/voter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ModelReview } from '../consensus/types.js';
2
+ export interface AdapterOptions {
3
+ timeoutMs: number;
4
+ maxRetries: number;
5
+ apiKey?: string;
6
+ baseUrl?: string;
7
+ }
8
+ export interface ReviewAdapter {
9
+ name: string;
10
+ provider: string;
11
+ review(model: string, role: string, systemPrompt: string, userPrompt: string, options: AdapterOptions): Promise<ModelReview>;
12
+ }
13
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T>;
14
+ export declare function parseProviderFromModel(model: string): string;
15
+ export declare function stripProviderPrefix(model: string): string;
@@ -0,0 +1,24 @@
1
+ export function withTimeout(promise, ms) {
2
+ return new Promise((resolve, reject) => {
3
+ const timer = setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms);
4
+ promise.then((val) => { clearTimeout(timer); resolve(val); }, (err) => { clearTimeout(timer); reject(err); });
5
+ });
6
+ }
7
+ export function parseProviderFromModel(model) {
8
+ if (model.startsWith('claude'))
9
+ return 'anthropic';
10
+ if (model.startsWith('gpt') || model.startsWith('o1') || model.startsWith('o3') || model.startsWith('o4'))
11
+ return 'openai';
12
+ if (model.startsWith('gemini'))
13
+ return 'google';
14
+ // format: provider/model
15
+ if (model.includes('/'))
16
+ return model.split('/')[0];
17
+ return 'openai'; // default for unknown
18
+ }
19
+ export function stripProviderPrefix(model) {
20
+ if (model.includes('/'))
21
+ return model.split('/').slice(1).join('/');
22
+ return model;
23
+ }
24
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/dispatch/adapter.ts"],"names":[],"mappings":"AAqBA,MAAM,UAAU,WAAW,CAAI,OAAmB,EAAE,EAAU;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,EAClD,EAAE,CACH,CAAC;QACF,OAAO,CAAC,IAAI,CACV,CAAC,GAAG,EAAE,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAC/C,CAAC,GAAG,EAAE,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAC/C,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,WAAW,CAAC;IACnD,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC3H,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChD,yBAAyB;IACzB,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACrD,OAAO,QAAQ,CAAC,CAAC,sBAAsB;AACzC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ModelReview } from '../consensus/types.js';
2
+ import type { ReviewAdapter, AdapterOptions } from './adapter.js';
3
+ export declare class AnthropicAdapter implements ReviewAdapter {
4
+ name: string;
5
+ provider: string;
6
+ private client;
7
+ constructor(apiKey?: string);
8
+ review(model: string, role: string, systemPrompt: string, userPrompt: string, options: AdapterOptions): Promise<ModelReview>;
9
+ }
@@ -0,0 +1,132 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { parseReviewOutput } from '../consensus/parser.js';
3
+ import { stripKnownProviderPrefix } from './utils.js';
4
+ const RETRY_DELAYS = [1000, 2000, 4000];
5
+ function isRetryable(err) {
6
+ if (err instanceof Anthropic.APIError) {
7
+ return err.status === 429 || err.status === 500 || err.status === 503;
8
+ }
9
+ return false;
10
+ }
11
+ export class AnthropicAdapter {
12
+ name = 'anthropic';
13
+ provider = 'anthropic';
14
+ client;
15
+ constructor(apiKey) {
16
+ this.client = new Anthropic({
17
+ apiKey: apiKey ?? process.env['ANTHROPIC_API_KEY'],
18
+ });
19
+ }
20
+ async review(model, role, systemPrompt, userPrompt, options) {
21
+ const start = Date.now();
22
+ const controller = new AbortController();
23
+ const timeoutHandle = setTimeout(() => controller.abort(), options.timeoutMs);
24
+ let lastErr = new Error('no attempts made');
25
+ const modelId = stripKnownProviderPrefix(model);
26
+ for (let attempt = 0; attempt <= (options.maxRetries ?? 3); attempt++) {
27
+ try {
28
+ // Use tool use for reliable JSON extraction
29
+ const response = await this.client.messages.create({
30
+ model: modelId,
31
+ max_tokens: 4096,
32
+ system: systemPrompt,
33
+ messages: [{ role: 'user', content: userPrompt }],
34
+ tools: [
35
+ {
36
+ name: 'report_findings',
37
+ description: 'Report code review findings as structured JSON',
38
+ input_schema: {
39
+ type: 'object',
40
+ properties: {
41
+ findings: {
42
+ type: 'array',
43
+ items: {
44
+ type: 'object',
45
+ properties: {
46
+ id: { type: 'string' },
47
+ file: { type: 'string' },
48
+ startLine: { type: 'number' },
49
+ endLine: { type: 'number' },
50
+ severity: {
51
+ type: 'string',
52
+ enum: ['critical', 'important', 'minor', 'nitpick'],
53
+ },
54
+ category: {
55
+ type: 'string',
56
+ enum: ['security', 'correctness', 'best-practices', 'tests', 'api-design'],
57
+ },
58
+ title: { type: 'string' },
59
+ description: { type: 'string' },
60
+ suggestedFix: { type: 'string' },
61
+ },
62
+ required: ['id', 'file', 'startLine', 'endLine', 'severity', 'category', 'title', 'description'],
63
+ },
64
+ },
65
+ },
66
+ required: ['findings'],
67
+ },
68
+ },
69
+ ],
70
+ tool_choice: { type: 'any' },
71
+ }, { signal: controller.signal });
72
+ clearTimeout(timeoutHandle);
73
+ // Extract from tool use
74
+ let rawOutput = '';
75
+ for (const block of response.content) {
76
+ if (block.type === 'tool_use' && block.name === 'report_findings') {
77
+ rawOutput = JSON.stringify(block.input);
78
+ break;
79
+ }
80
+ if (block.type === 'text') {
81
+ rawOutput += block.text;
82
+ }
83
+ }
84
+ // If tool wasn't used, parse text output
85
+ const { findings, warnings } = parseReviewOutput(rawOutput, model, role);
86
+ for (const w of warnings)
87
+ console.warn(w);
88
+ return {
89
+ model,
90
+ role,
91
+ provider: 'anthropic',
92
+ findings,
93
+ durationMs: Date.now() - start,
94
+ status: 'success',
95
+ };
96
+ }
97
+ catch (err) {
98
+ lastErr = err;
99
+ if (err instanceof Error && err.name === 'AbortError') {
100
+ clearTimeout(timeoutHandle);
101
+ return {
102
+ model,
103
+ role,
104
+ provider: 'anthropic',
105
+ findings: [],
106
+ durationMs: Date.now() - start,
107
+ status: 'timeout',
108
+ error: 'Request timed out',
109
+ };
110
+ }
111
+ if (isRetryable(err) && attempt < (options.maxRetries ?? 3)) {
112
+ const delay = RETRY_DELAYS[attempt] ?? 4000;
113
+ await new Promise((r) => setTimeout(r, delay));
114
+ continue;
115
+ }
116
+ break;
117
+ }
118
+ }
119
+ clearTimeout(timeoutHandle);
120
+ const errMsg = lastErr instanceof Error ? `${lastErr.name}: ${lastErr.message}` : String(lastErr);
121
+ return {
122
+ model,
123
+ role,
124
+ provider: 'anthropic',
125
+ findings: [],
126
+ durationMs: Date.now() - start,
127
+ status: 'error',
128
+ error: errMsg,
129
+ };
130
+ }
131
+ }
132
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/dispatch/anthropic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { ModelReview } from '../consensus/types.js';
2
+ import type { ReviewAdapter, AdapterOptions } from './adapter.js';
3
+ export declare class GoogleAdapter implements ReviewAdapter {
4
+ name: string;
5
+ provider: string;
6
+ private client;
7
+ constructor(apiKey?: string);
8
+ review(model: string, role: string, systemPrompt: string, userPrompt: string, options: AdapterOptions): Promise<ModelReview>;
9
+ }