@simonren/quorum 0.7.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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/commands/multi-consult.md +109 -0
- package/commands/multi-review.md +139 -0
- package/dist/adapters/base.d.ts +120 -0
- package/dist/adapters/base.js +98 -0
- package/dist/adapters/claude.d.ts +25 -0
- package/dist/adapters/claude.js +217 -0
- package/dist/adapters/codex.d.ts +20 -0
- package/dist/adapters/codex.js +227 -0
- package/dist/adapters/gemini.d.ts +20 -0
- package/dist/adapters/gemini.js +197 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.js +15 -0
- package/dist/cli/check.d.ts +20 -0
- package/dist/cli/check.js +78 -0
- package/dist/cli/codex.d.ts +11 -0
- package/dist/cli/codex.js +255 -0
- package/dist/cli/gemini.d.ts +12 -0
- package/dist/cli/gemini.js +253 -0
- package/dist/commands.d.ts +28 -0
- package/dist/commands.js +105 -0
- package/dist/config.d.ts +244 -0
- package/dist/config.js +179 -0
- package/dist/consult-prompt.d.ts +10 -0
- package/dist/consult-prompt.js +72 -0
- package/dist/context.d.ts +1538 -0
- package/dist/context.js +383 -0
- package/dist/decoders/claude.d.ts +53 -0
- package/dist/decoders/claude.js +106 -0
- package/dist/decoders/codex.d.ts +71 -0
- package/dist/decoders/codex.js +145 -0
- package/dist/decoders/gemini.d.ts +33 -0
- package/dist/decoders/gemini.js +58 -0
- package/dist/decoders/index.d.ts +6 -0
- package/dist/decoders/index.js +3 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.js +192 -0
- package/dist/executor.d.ts +103 -0
- package/dist/executor.js +244 -0
- package/dist/handoff.d.ts +270 -0
- package/dist/handoff.js +599 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +134 -0
- package/dist/pipeline.d.ts +135 -0
- package/dist/pipeline.js +462 -0
- package/dist/prompt-v2.d.ts +38 -0
- package/dist/prompt-v2.js +391 -0
- package/dist/prompt.d.ts +71 -0
- package/dist/prompt.js +309 -0
- package/dist/schema.d.ts +660 -0
- package/dist/schema.js +536 -0
- package/dist/tools/consult.d.ts +104 -0
- package/dist/tools/consult.js +220 -0
- package/dist/tools/feedback.d.ts +91 -0
- package/dist/tools/feedback.js +117 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.js +31 -0
- package/package.json +54 -0
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Response Processing Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Processes reviewer output through multiple stages:
|
|
5
|
+
* 1. Parse - Extract structured data
|
|
6
|
+
* 2. Verify - Check file/line references exist
|
|
7
|
+
* 3. Cross-check - Compare with CC's knowledge
|
|
8
|
+
* 4. Prioritize - Rank by impact and confidence
|
|
9
|
+
* 5. Plan - Generate actionable next steps
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from 'fs';
|
|
12
|
+
import { resolve, normalize } from 'path';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// FILE CACHE (Performance optimization)
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Simple file cache to avoid re-reading files for each finding
|
|
18
|
+
*/
|
|
19
|
+
export class FileCache {
|
|
20
|
+
workingDir;
|
|
21
|
+
contentCache = new Map(); // null = file doesn't exist
|
|
22
|
+
lineCountCache = new Map();
|
|
23
|
+
linesCache = new Map();
|
|
24
|
+
constructor(workingDir) {
|
|
25
|
+
this.workingDir = workingDir;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if file exists (cached)
|
|
29
|
+
*/
|
|
30
|
+
exists(relativePath) {
|
|
31
|
+
const fullPath = resolve(this.workingDir, normalize(relativePath));
|
|
32
|
+
if (this.contentCache.has(fullPath)) {
|
|
33
|
+
return this.contentCache.get(fullPath) !== null;
|
|
34
|
+
}
|
|
35
|
+
// Check existence and cache
|
|
36
|
+
if (existsSync(fullPath)) {
|
|
37
|
+
// Don't read content yet - just mark as existing
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.contentCache.set(fullPath, null);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get file content (cached, lazy-loaded)
|
|
47
|
+
*/
|
|
48
|
+
getContent(relativePath) {
|
|
49
|
+
const fullPath = resolve(this.workingDir, normalize(relativePath));
|
|
50
|
+
if (this.contentCache.has(fullPath)) {
|
|
51
|
+
return this.contentCache.get(fullPath) ?? null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
55
|
+
this.contentCache.set(fullPath, content);
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
this.contentCache.set(fullPath, null);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get lines array (cached)
|
|
65
|
+
*/
|
|
66
|
+
getLines(relativePath) {
|
|
67
|
+
const fullPath = resolve(this.workingDir, normalize(relativePath));
|
|
68
|
+
if (this.linesCache.has(fullPath)) {
|
|
69
|
+
return this.linesCache.get(fullPath) ?? null;
|
|
70
|
+
}
|
|
71
|
+
const content = this.getContent(relativePath);
|
|
72
|
+
if (content === null)
|
|
73
|
+
return null;
|
|
74
|
+
const lines = content.split('\n');
|
|
75
|
+
this.linesCache.set(fullPath, lines);
|
|
76
|
+
this.lineCountCache.set(fullPath, lines.length);
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get line count (cached)
|
|
81
|
+
*/
|
|
82
|
+
getLineCount(relativePath) {
|
|
83
|
+
const fullPath = resolve(this.workingDir, normalize(relativePath));
|
|
84
|
+
if (this.lineCountCache.has(fullPath)) {
|
|
85
|
+
return this.lineCountCache.get(fullPath) ?? null;
|
|
86
|
+
}
|
|
87
|
+
const lines = this.getLines(relativePath);
|
|
88
|
+
return lines?.length ?? null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get stats about cache usage
|
|
92
|
+
*/
|
|
93
|
+
getStats() {
|
|
94
|
+
let filesLoaded = 0;
|
|
95
|
+
for (const content of this.contentCache.values()) {
|
|
96
|
+
if (content !== null)
|
|
97
|
+
filesLoaded++;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
filesChecked: this.contentCache.size,
|
|
101
|
+
filesLoaded,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// VERIFICATION STAGE
|
|
107
|
+
// =============================================================================
|
|
108
|
+
/**
|
|
109
|
+
* Build verification data by scanning the filesystem
|
|
110
|
+
*/
|
|
111
|
+
export async function buildVerificationData(workingDir) {
|
|
112
|
+
const existingFiles = new Set();
|
|
113
|
+
const fileContents = new Map();
|
|
114
|
+
const fileLineCounts = new Map();
|
|
115
|
+
// This would recursively scan the directory
|
|
116
|
+
// For now, we'll verify on-demand
|
|
117
|
+
return { existingFiles, fileContents, fileLineCounts };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Verify a single finding's references
|
|
121
|
+
* @param finding The finding to verify
|
|
122
|
+
* @param workingDir Working directory for path resolution
|
|
123
|
+
* @param cache Optional file cache for performance (recommended for multiple findings)
|
|
124
|
+
*/
|
|
125
|
+
export async function verifyFinding(finding, workingDir, cache) {
|
|
126
|
+
const verification = {
|
|
127
|
+
fileExists: true,
|
|
128
|
+
lineValid: true,
|
|
129
|
+
codeSnippetMatches: undefined,
|
|
130
|
+
verificationNotes: undefined,
|
|
131
|
+
};
|
|
132
|
+
// Check file exists
|
|
133
|
+
if (finding.location) {
|
|
134
|
+
// Sanitize path to prevent traversal attacks
|
|
135
|
+
const normalizedFile = normalize(finding.location.file);
|
|
136
|
+
const fullPath = resolve(workingDir, normalizedFile);
|
|
137
|
+
const resolvedWorkingDir = resolve(workingDir);
|
|
138
|
+
// Block path traversal attempts (paths that escape working directory)
|
|
139
|
+
if (!fullPath.startsWith(resolvedWorkingDir + '/') && fullPath !== resolvedWorkingDir) {
|
|
140
|
+
verification.fileExists = false;
|
|
141
|
+
verification.verificationNotes = `Path traversal blocked: ${finding.location.file}`;
|
|
142
|
+
return {
|
|
143
|
+
...finding,
|
|
144
|
+
verification,
|
|
145
|
+
crossCheck: { alreadyAddressedByCC: false, conflictsWithCC: false, ccMentioned: false },
|
|
146
|
+
adjustedConfidence: finding.confidence * 0.05, // Severe penalty for traversal attempt
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Use cache if provided, otherwise direct filesystem access
|
|
150
|
+
const fileExists = cache
|
|
151
|
+
? cache.exists(normalizedFile)
|
|
152
|
+
: existsSync(fullPath);
|
|
153
|
+
if (!fileExists) {
|
|
154
|
+
verification.fileExists = false;
|
|
155
|
+
verification.verificationNotes = `File not found: ${finding.location.file}`;
|
|
156
|
+
}
|
|
157
|
+
else if (finding.location.line_start) {
|
|
158
|
+
// Check line count using cache
|
|
159
|
+
try {
|
|
160
|
+
const lines = cache
|
|
161
|
+
? cache.getLines(normalizedFile)
|
|
162
|
+
: readFileSync(fullPath, 'utf-8').split('\n');
|
|
163
|
+
if (!lines) {
|
|
164
|
+
verification.verificationNotes = `Error reading file: ${finding.location.file}`;
|
|
165
|
+
}
|
|
166
|
+
else if (finding.location.line_start > lines.length) {
|
|
167
|
+
verification.lineValid = false;
|
|
168
|
+
verification.verificationNotes = `Line ${finding.location.line_start} exceeds file length (${lines.length} lines)`;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// If evidence provided, check if it matches
|
|
172
|
+
if (finding.evidence) {
|
|
173
|
+
const lineContent = lines[finding.location.line_start - 1] || '';
|
|
174
|
+
const evidenceClean = finding.evidence.replace(/\s+/g, ' ').trim();
|
|
175
|
+
const lineClean = lineContent.replace(/\s+/g, ' ').trim();
|
|
176
|
+
// Fuzzy match - check if evidence appears in or near the line
|
|
177
|
+
if (lineClean.includes(evidenceClean.slice(0, 50)) ||
|
|
178
|
+
evidenceClean.includes(lineClean.slice(0, 50))) {
|
|
179
|
+
verification.codeSnippetMatches = true;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
verification.codeSnippetMatches = false;
|
|
183
|
+
verification.verificationNotes = `Code at line ${finding.location.line_start} doesn't match evidence`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
verification.verificationNotes = `Error reading file: ${err.message}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Calculate adjusted confidence
|
|
194
|
+
let adjustedConfidence = finding.confidence;
|
|
195
|
+
if (!verification.fileExists) {
|
|
196
|
+
adjustedConfidence *= 0.1; // Major penalty for non-existent file
|
|
197
|
+
}
|
|
198
|
+
else if (!verification.lineValid) {
|
|
199
|
+
adjustedConfidence *= 0.3; // Significant penalty for invalid line
|
|
200
|
+
}
|
|
201
|
+
else if (verification.codeSnippetMatches === false) {
|
|
202
|
+
adjustedConfidence *= 0.5; // Moderate penalty for mismatched evidence
|
|
203
|
+
}
|
|
204
|
+
else if (verification.codeSnippetMatches === true) {
|
|
205
|
+
adjustedConfidence = Math.min(1, adjustedConfidence * 1.2); // Boost for matching evidence
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
...finding,
|
|
209
|
+
verification,
|
|
210
|
+
crossCheck: {
|
|
211
|
+
alreadyAddressedByCC: false,
|
|
212
|
+
conflictsWithCC: false,
|
|
213
|
+
ccMentioned: false,
|
|
214
|
+
},
|
|
215
|
+
adjustedConfidence,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// =============================================================================
|
|
219
|
+
// CROSS-CHECK STAGE
|
|
220
|
+
// =============================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Cross-check findings against CC's analysis
|
|
223
|
+
*/
|
|
224
|
+
export function crossCheckWithCC(finding, ccAnalysis) {
|
|
225
|
+
const crossCheck = { ...finding.crossCheck };
|
|
226
|
+
// Check if CC already mentioned this
|
|
227
|
+
if (ccAnalysis.findings) {
|
|
228
|
+
for (const ccFinding of ccAnalysis.findings) {
|
|
229
|
+
// Simple similarity check - could be more sophisticated
|
|
230
|
+
const descMatch = ccFinding.description.toLowerCase().includes(finding.title.toLowerCase().slice(0, 30));
|
|
231
|
+
const locMatch = ccFinding.location &&
|
|
232
|
+
finding.location?.file &&
|
|
233
|
+
ccFinding.location.includes(finding.location.file);
|
|
234
|
+
if (descMatch || locMatch) {
|
|
235
|
+
crossCheck.ccMentioned = true;
|
|
236
|
+
if (ccFinding.addressed) {
|
|
237
|
+
crossCheck.alreadyAddressedByCC = true;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Check if this contradicts CC's assumptions
|
|
244
|
+
if (ccAnalysis.assumptions) {
|
|
245
|
+
for (const assumption of ccAnalysis.assumptions) {
|
|
246
|
+
if (finding.description.toLowerCase().includes('incorrect') &&
|
|
247
|
+
finding.description.toLowerCase().includes(assumption.toLowerCase().slice(0, 20))) {
|
|
248
|
+
crossCheck.conflictsWithCC = true;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { ...finding, crossCheck };
|
|
254
|
+
}
|
|
255
|
+
// =============================================================================
|
|
256
|
+
// PRIORITIZATION STAGE
|
|
257
|
+
// =============================================================================
|
|
258
|
+
const SEVERITY_SCORES = {
|
|
259
|
+
critical: 100,
|
|
260
|
+
high: 75,
|
|
261
|
+
medium: 50,
|
|
262
|
+
low: 25,
|
|
263
|
+
info: 10,
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Calculate priority score for a finding
|
|
267
|
+
*/
|
|
268
|
+
export function calculatePriority(finding) {
|
|
269
|
+
const severityScore = SEVERITY_SCORES[finding.severity] || 50;
|
|
270
|
+
const confidenceScore = finding.adjustedConfidence * 100;
|
|
271
|
+
// Weight factors
|
|
272
|
+
const hasLocation = finding.location ? 1.1 : 0.9;
|
|
273
|
+
const hasSuggestion = finding.suggestion ? 1.1 : 1.0;
|
|
274
|
+
const isVerified = finding.verification.codeSnippetMatches ? 1.2 : 1.0;
|
|
275
|
+
const notAddressed = finding.crossCheck.alreadyAddressedByCC ? 0.3 : 1.0;
|
|
276
|
+
// Combine scores
|
|
277
|
+
let priority = (severityScore * 0.4 + confidenceScore * 0.6) *
|
|
278
|
+
hasLocation * hasSuggestion * isVerified * notAddressed;
|
|
279
|
+
// Cap at 100
|
|
280
|
+
return Math.min(100, Math.max(0, priority));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Determine action for a finding
|
|
284
|
+
*/
|
|
285
|
+
export function determineAction(finding, priority) {
|
|
286
|
+
// Reject if verification failed
|
|
287
|
+
if (!finding.verification.fileExists) {
|
|
288
|
+
return { action: 'reject', reason: 'Referenced file does not exist (possible hallucination)' };
|
|
289
|
+
}
|
|
290
|
+
if (!finding.verification.lineValid) {
|
|
291
|
+
return { action: 'reject', reason: 'Referenced line number is invalid' };
|
|
292
|
+
}
|
|
293
|
+
if (finding.verification.codeSnippetMatches === false) {
|
|
294
|
+
return { action: 'investigate', reason: 'Code evidence does not match - needs manual verification' };
|
|
295
|
+
}
|
|
296
|
+
// Already addressed by CC
|
|
297
|
+
if (finding.crossCheck.alreadyAddressedByCC) {
|
|
298
|
+
return { action: 'reject', reason: 'Already addressed by Claude Code' };
|
|
299
|
+
}
|
|
300
|
+
// Low confidence
|
|
301
|
+
if (finding.adjustedConfidence < 0.3) {
|
|
302
|
+
return { action: 'defer', reason: 'Low confidence - may not be accurate' };
|
|
303
|
+
}
|
|
304
|
+
// Critical findings
|
|
305
|
+
if (finding.severity === 'critical' && priority > 70) {
|
|
306
|
+
return { action: 'fix_now', reason: 'Critical severity with high confidence' };
|
|
307
|
+
}
|
|
308
|
+
// High priority
|
|
309
|
+
if (priority > 60) {
|
|
310
|
+
return { action: 'fix_now', reason: 'High priority issue' };
|
|
311
|
+
}
|
|
312
|
+
// Medium priority
|
|
313
|
+
if (priority > 40) {
|
|
314
|
+
return { action: 'investigate', reason: 'Worth investigating further' };
|
|
315
|
+
}
|
|
316
|
+
// Low priority
|
|
317
|
+
return { action: 'defer', reason: 'Lower priority - can address later' };
|
|
318
|
+
}
|
|
319
|
+
// =============================================================================
|
|
320
|
+
// FULL PIPELINE
|
|
321
|
+
// =============================================================================
|
|
322
|
+
/**
|
|
323
|
+
* Process a review output through the full verification pipeline
|
|
324
|
+
*/
|
|
325
|
+
export async function processReviewOutput(output, context) {
|
|
326
|
+
const verified = [];
|
|
327
|
+
const rejected = [];
|
|
328
|
+
const actionPlan = [];
|
|
329
|
+
// Create file cache for efficient verification of multiple findings
|
|
330
|
+
const fileCache = new FileCache(context.workingDir);
|
|
331
|
+
// Process each finding
|
|
332
|
+
for (const finding of output.findings) {
|
|
333
|
+
// Stage 1: Verify (with cache for performance)
|
|
334
|
+
let verifiedFinding = await verifyFinding(finding, context.workingDir, fileCache);
|
|
335
|
+
// Stage 2: Cross-check
|
|
336
|
+
verifiedFinding = crossCheckWithCC(verifiedFinding, context.analysis);
|
|
337
|
+
// Stage 3: Prioritize
|
|
338
|
+
const priority = calculatePriority(verifiedFinding);
|
|
339
|
+
// Stage 4: Determine action
|
|
340
|
+
const { action, reason } = determineAction(verifiedFinding, priority);
|
|
341
|
+
if (action === 'reject') {
|
|
342
|
+
rejected.push({ finding, reason });
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
verified.push(verifiedFinding);
|
|
346
|
+
actionPlan.push({
|
|
347
|
+
finding: verifiedFinding,
|
|
348
|
+
action,
|
|
349
|
+
priority,
|
|
350
|
+
suggestedFix: finding.suggestion,
|
|
351
|
+
reason,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Sort action plan by priority
|
|
356
|
+
actionPlan.sort((a, b) => b.priority - a.priority);
|
|
357
|
+
// Build summary
|
|
358
|
+
const actionableCount = actionPlan.filter(a => a.action === 'fix_now').length;
|
|
359
|
+
return {
|
|
360
|
+
original: output,
|
|
361
|
+
verified,
|
|
362
|
+
rejected,
|
|
363
|
+
actionPlan,
|
|
364
|
+
summary: {
|
|
365
|
+
totalFindings: output.findings.length,
|
|
366
|
+
verifiedCount: verified.length,
|
|
367
|
+
rejectedCount: rejected.length,
|
|
368
|
+
actionableCount,
|
|
369
|
+
topPriority: actionPlan.filter(a => a.action === 'fix_now'),
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// =============================================================================
|
|
374
|
+
// FORMATTING
|
|
375
|
+
// =============================================================================
|
|
376
|
+
/**
|
|
377
|
+
* Format processed review for display
|
|
378
|
+
*/
|
|
379
|
+
export function formatProcessedReview(processed) {
|
|
380
|
+
const lines = [];
|
|
381
|
+
// Summary header
|
|
382
|
+
lines.push('# Review Analysis\n');
|
|
383
|
+
lines.push(`**Total Findings:** ${processed.summary.totalFindings}`);
|
|
384
|
+
lines.push(`**Verified:** ${processed.summary.verifiedCount}`);
|
|
385
|
+
lines.push(`**Rejected:** ${processed.summary.rejectedCount}`);
|
|
386
|
+
lines.push(`**Actionable:** ${processed.summary.actionableCount}`);
|
|
387
|
+
lines.push('');
|
|
388
|
+
// Action items by category
|
|
389
|
+
const fixNow = processed.actionPlan.filter(a => a.action === 'fix_now');
|
|
390
|
+
const investigate = processed.actionPlan.filter(a => a.action === 'investigate');
|
|
391
|
+
const defer = processed.actionPlan.filter(a => a.action === 'defer');
|
|
392
|
+
if (fixNow.length > 0) {
|
|
393
|
+
lines.push('## Fix Now (High Priority)\n');
|
|
394
|
+
for (const item of fixNow) {
|
|
395
|
+
const f = item.finding;
|
|
396
|
+
lines.push(`### ${f.title}`);
|
|
397
|
+
lines.push(`**Severity:** ${f.severity} | **Confidence:** ${Math.round(f.adjustedConfidence * 100)}% | **Priority:** ${Math.round(item.priority)}`);
|
|
398
|
+
if (f.location) {
|
|
399
|
+
lines.push(`**Location:** ${f.location.file}${f.location.line_start ? `:${f.location.line_start}` : ''}`);
|
|
400
|
+
}
|
|
401
|
+
lines.push(`\n${f.description}`);
|
|
402
|
+
if (f.suggestion) {
|
|
403
|
+
lines.push(`\nš” **Suggestion:** ${f.suggestion}`);
|
|
404
|
+
}
|
|
405
|
+
lines.push('');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (investigate.length > 0) {
|
|
409
|
+
lines.push('## Investigate\n');
|
|
410
|
+
for (const item of investigate) {
|
|
411
|
+
const f = item.finding;
|
|
412
|
+
lines.push(`- **${f.title}** [${f.severity}] - ${item.reason}`);
|
|
413
|
+
if (f.location) {
|
|
414
|
+
lines.push(` š ${f.location.file}${f.location.line_start ? `:${f.location.line_start}` : ''}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
lines.push('');
|
|
418
|
+
}
|
|
419
|
+
if (defer.length > 0) {
|
|
420
|
+
lines.push('## Deferred\n');
|
|
421
|
+
for (const item of defer) {
|
|
422
|
+
const f = item.finding;
|
|
423
|
+
lines.push(`- ${f.title} [${f.severity}] - ${item.reason}`);
|
|
424
|
+
}
|
|
425
|
+
lines.push('');
|
|
426
|
+
}
|
|
427
|
+
if (processed.rejected.length > 0) {
|
|
428
|
+
lines.push('## Rejected (Verification Failed)\n');
|
|
429
|
+
for (const { finding, reason } of processed.rejected) {
|
|
430
|
+
lines.push(`- ~~${finding.title}~~ - ${reason}`);
|
|
431
|
+
}
|
|
432
|
+
lines.push('');
|
|
433
|
+
}
|
|
434
|
+
return lines.join('\n');
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Generate follow-up questions for uncertain findings
|
|
438
|
+
*/
|
|
439
|
+
export function generateFollowUpQuestions(processed) {
|
|
440
|
+
const questions = [];
|
|
441
|
+
// Ask about investigate items
|
|
442
|
+
for (const item of processed.actionPlan.filter(a => a.action === 'investigate')) {
|
|
443
|
+
const f = item.finding;
|
|
444
|
+
if (f.verification.codeSnippetMatches === false) {
|
|
445
|
+
questions.push({
|
|
446
|
+
topic: f.title,
|
|
447
|
+
question: `The evidence for "${f.title}" doesn't match the code at the specified location. Can you verify this finding?`,
|
|
448
|
+
relatedFindings: [f.id],
|
|
449
|
+
context: `File: ${f.location?.file}, Line: ${f.location?.line_start}`,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
if (f.crossCheck.conflictsWithCC) {
|
|
453
|
+
questions.push({
|
|
454
|
+
topic: f.title,
|
|
455
|
+
question: `This finding conflicts with CC's assumptions. Which assessment is correct?`,
|
|
456
|
+
relatedFindings: [f.id],
|
|
457
|
+
context: f.description,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return questions;
|
|
462
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Prompt Builder v2
|
|
3
|
+
*
|
|
4
|
+
* Builds prompts using rich context with:
|
|
5
|
+
* - Layered information (summary ā details)
|
|
6
|
+
* - Focus-area specific emphasis
|
|
7
|
+
* - Smart diff integration
|
|
8
|
+
* - Explicit verification requirements
|
|
9
|
+
* - Targeted questions from CC
|
|
10
|
+
*/
|
|
11
|
+
import { ReviewContext } from './context.js';
|
|
12
|
+
import { FocusArea } from './types.js';
|
|
13
|
+
export interface EnhancedPromptOptions {
|
|
14
|
+
context: ReviewContext;
|
|
15
|
+
reviewerName: string;
|
|
16
|
+
focusAreas?: FocusArea[];
|
|
17
|
+
maxContextTokens?: number;
|
|
18
|
+
includeFullDiffs?: boolean;
|
|
19
|
+
retryContext?: {
|
|
20
|
+
attemptNumber: number;
|
|
21
|
+
previousError: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build an enhanced review prompt using rich context
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildEnhancedPrompt(options: EnhancedPromptOptions): string;
|
|
28
|
+
/**
|
|
29
|
+
* Build a prompt focused on reviewing a specific diff
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildDiffReviewPrompt(diff: string, filePath: string, context: Partial<ReviewContext>, focusAreas?: FocusArea[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Build a follow-up prompt for clarification
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildFollowUpPrompt(originalContext: ReviewContext, previousReview: string, questions: Array<{
|
|
36
|
+
question: string;
|
|
37
|
+
context?: string;
|
|
38
|
+
}>): string;
|