@kb-labs/review-llm 0.5.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.
@@ -0,0 +1,558 @@
1
+ import { InputFile, IDiffProvider, ReviewFinding, FileDiff } from '@kb-labs/review-contracts';
2
+ export { BaseLLMAnalyzer } from '@kb-labs/review-contracts';
3
+ export { ArchitectureAnalyzer } from './analyzers/architecture-analyzer.js';
4
+ export { SecurityAnalyzer } from './analyzers/security-analyzer.js';
5
+ export { NamingAnalyzer } from './analyzers/naming-analyzer.js';
6
+
7
+ /**
8
+ * @module @kb-labs/review-llm/llm-lite/llm-lite-analyzer
9
+ * LLM-Lite analyzer for efficient code review.
10
+ *
11
+ * Uses batch tools, diff-based context, and anti-hallucination verification.
12
+ */
13
+
14
+ /**
15
+ * LLM-Lite review request
16
+ */
17
+ interface LLMLiteRequest {
18
+ /** Working directory */
19
+ cwd: string;
20
+ /** Changed files with content */
21
+ files: InputFile[];
22
+ /** Task context (what the changes are trying to achieve) */
23
+ taskContext?: string;
24
+ /** Repository scope */
25
+ repoScope?: string[];
26
+ /** Diff provider (injected from review-core to avoid circular dependency) */
27
+ diffProvider: IDiffProvider;
28
+ }
29
+ /**
30
+ * LLM-Lite review result
31
+ */
32
+ interface LLMLiteResult {
33
+ /** Verified findings */
34
+ findings: ReviewFinding[];
35
+ /** Metadata */
36
+ metadata: {
37
+ /** LLM calls made */
38
+ llmCalls: number;
39
+ /** Tool calls made */
40
+ toolCalls: {
41
+ get_diffs: number;
42
+ get_file_chunks: number;
43
+ report_findings: number;
44
+ };
45
+ /** Token usage */
46
+ tokens: {
47
+ input: number;
48
+ output: number;
49
+ total: number;
50
+ };
51
+ /** Estimated cost in USD */
52
+ estimatedCost: number;
53
+ /** Verification stats */
54
+ verification: {
55
+ rawFindings: number;
56
+ verified: number;
57
+ downgraded: number;
58
+ discarded: number;
59
+ hallucinationRate: number;
60
+ };
61
+ /** Timing */
62
+ timing: {
63
+ totalMs: number;
64
+ llmMs: number;
65
+ verifyMs: number;
66
+ };
67
+ };
68
+ }
69
+ /**
70
+ * LLM-Lite Analyzer
71
+ *
72
+ * Efficient code review using:
73
+ * - Batch tools (get_diffs, get_file_chunks, report_findings)
74
+ * - Diff-based context (not full files)
75
+ * - Anti-hallucination verification
76
+ * - Dynamic categories from rules directory
77
+ */
78
+ declare class LLMLiteAnalyzer {
79
+ private cwd;
80
+ private files;
81
+ private taskContext?;
82
+ private repoScope?;
83
+ private diffProvider;
84
+ constructor(request: LLMLiteRequest);
85
+ /**
86
+ * Run LLM-Lite analysis
87
+ */
88
+ analyze(): Promise<LLMLiteResult>;
89
+ /**
90
+ * Build file summaries for initial prompt
91
+ */
92
+ private buildFileSummaries;
93
+ /**
94
+ * Build system prompt from loaded prompts and rules
95
+ */
96
+ private buildSystemPrompt;
97
+ /**
98
+ * Build initial prompt with file list and task context
99
+ */
100
+ private buildInitialPrompt;
101
+ /**
102
+ * Convert verified findings to ReviewFinding format
103
+ */
104
+ private convertToReviewFindings;
105
+ }
106
+ /**
107
+ * Run LLM-Lite analysis
108
+ */
109
+ declare function runLLMLiteAnalysis(request: LLMLiteRequest): Promise<LLMLiteResult>;
110
+
111
+ /**
112
+ * @module @kb-labs/review-llm/llm-lite/tool-executor
113
+ * Batch tool execution for LLM-lite review mode.
114
+ *
115
+ * Executes LLM tools with limits and budget tracking.
116
+ */
117
+
118
+ /**
119
+ * Tool call from LLM
120
+ */
121
+ interface ToolCall {
122
+ name: string;
123
+ arguments: Record<string, unknown>;
124
+ }
125
+ /**
126
+ * Tool result
127
+ */
128
+ interface ToolResult {
129
+ name: string;
130
+ result: unknown;
131
+ error?: string;
132
+ }
133
+ /**
134
+ * Valid severity levels for LLM findings.
135
+ * Matches the enum in report_findings tool schema.
136
+ */
137
+ type RawFindingSeverity = 'blocker' | 'high' | 'medium' | 'low' | 'info';
138
+ /**
139
+ * Raw finding from LLM (before validation).
140
+ * These are the findings reported by the LLM via the report_findings tool.
141
+ */
142
+ interface RawFinding {
143
+ file: string;
144
+ line: number;
145
+ endLine?: number;
146
+ /** Severity level - must be one of: blocker, high, medium, low, info */
147
+ severity: RawFindingSeverity;
148
+ category: string;
149
+ message: string;
150
+ suggestion?: string;
151
+ codeSnippet?: string;
152
+ /** Rule ID if this finding matches a project rule (e.g., "security/no-eval") */
153
+ ruleId?: string | null;
154
+ }
155
+ /**
156
+ * Tool budget tracking
157
+ */
158
+ interface ToolBudget {
159
+ /** Max calls to get_diffs */
160
+ maxDiffCalls: number;
161
+ /** Max files per get_diffs call */
162
+ maxFilesPerDiff: number;
163
+ /** Max calls to get_file_chunks */
164
+ maxChunkCalls: number;
165
+ /** Max chunks across all calls */
166
+ maxTotalChunks: number;
167
+ /** Max lines per chunk */
168
+ maxLinesPerChunk: number;
169
+ /** Current usage */
170
+ usage: {
171
+ diffCalls: number;
172
+ filesRequested: number;
173
+ chunkCalls: number;
174
+ totalChunks: number;
175
+ };
176
+ }
177
+ /**
178
+ * Tool definitions for LLM
179
+ */
180
+ declare function buildToolDefinitions(validCategories: string[], validRuleIds?: string[]): ({
181
+ name: string;
182
+ description: string;
183
+ parameters: {
184
+ type: string;
185
+ properties: {
186
+ files: {
187
+ type: string;
188
+ items: {
189
+ type: string;
190
+ };
191
+ maxItems: number;
192
+ description: string;
193
+ };
194
+ requests?: undefined;
195
+ findings?: undefined;
196
+ summary?: undefined;
197
+ };
198
+ required: string[];
199
+ };
200
+ } | {
201
+ name: string;
202
+ description: string;
203
+ parameters: {
204
+ type: string;
205
+ properties: {
206
+ requests: {
207
+ type: string;
208
+ items: {
209
+ type: string;
210
+ properties: {
211
+ file: {
212
+ type: string;
213
+ };
214
+ startLine: {
215
+ type: string;
216
+ description: string;
217
+ };
218
+ endLine: {
219
+ type: string;
220
+ description: string;
221
+ };
222
+ };
223
+ required: string[];
224
+ };
225
+ maxItems: number;
226
+ description: string;
227
+ };
228
+ files?: undefined;
229
+ findings?: undefined;
230
+ summary?: undefined;
231
+ };
232
+ required: string[];
233
+ };
234
+ } | {
235
+ name: string;
236
+ description: string;
237
+ parameters: {
238
+ type: string;
239
+ properties: {
240
+ findings: {
241
+ type: string;
242
+ items: {
243
+ type: string;
244
+ properties: {
245
+ file: {
246
+ type: string;
247
+ description: string;
248
+ };
249
+ line: {
250
+ type: string;
251
+ description: string;
252
+ };
253
+ endLine: {
254
+ type: string;
255
+ description: string;
256
+ };
257
+ severity: {
258
+ type: string;
259
+ enum: string[];
260
+ };
261
+ category: {
262
+ type: string;
263
+ enum: string[] | undefined;
264
+ description: string;
265
+ };
266
+ message: {
267
+ type: string;
268
+ description: string;
269
+ };
270
+ suggestion: {
271
+ type: string;
272
+ description: string;
273
+ };
274
+ codeSnippet: {
275
+ type: string;
276
+ description: string;
277
+ };
278
+ ruleId: {
279
+ type: string[];
280
+ description: string;
281
+ };
282
+ };
283
+ required: string[];
284
+ };
285
+ };
286
+ summary: {
287
+ type: string;
288
+ description: string;
289
+ };
290
+ files?: undefined;
291
+ requests?: undefined;
292
+ };
293
+ required: string[];
294
+ };
295
+ })[];
296
+ /**
297
+ * Default budget limits
298
+ */
299
+ declare const DEFAULT_BUDGET: ToolBudget;
300
+ /**
301
+ * ToolExecutor - executes LLM tool calls with limits
302
+ */
303
+ declare class ToolExecutor {
304
+ private diffProvider;
305
+ private cwd;
306
+ private budget;
307
+ private changedFiles;
308
+ private fetchedDiffs;
309
+ constructor(cwd: string, changedFiles: string[], diffProvider: IDiffProvider, budget?: Partial<ToolBudget>);
310
+ /**
311
+ * Execute a tool call
312
+ */
313
+ execute(toolCall: ToolCall): Promise<ToolResult>;
314
+ /**
315
+ * Execute get_diffs tool
316
+ */
317
+ private executeGetDiffs;
318
+ /**
319
+ * Execute get_file_chunks tool
320
+ */
321
+ private executeGetFileChunks;
322
+ /**
323
+ * Execute report_findings tool (just passes through)
324
+ */
325
+ private executeReportFindings;
326
+ /**
327
+ * Get fetched diffs (for verification)
328
+ */
329
+ getFetchedDiffs(): Map<string, FileDiff>;
330
+ /**
331
+ * Get current budget usage
332
+ */
333
+ getBudgetUsage(): ToolBudget['usage'];
334
+ /**
335
+ * Check if a file was in the changed files list
336
+ */
337
+ isValidFile(file: string): boolean;
338
+ }
339
+ /**
340
+ * Create a ToolExecutor instance
341
+ */
342
+ declare function createToolExecutor(cwd: string, changedFiles: string[], diffProvider: IDiffProvider, budget?: Partial<ToolBudget>): ToolExecutor;
343
+
344
+ /**
345
+ * @module @kb-labs/review-llm/llm-lite/verification
346
+ * Anti-hallucination verification for LLM findings.
347
+ *
348
+ * Validates LLM output against actual code to filter hallucinations.
349
+ */
350
+
351
+ /**
352
+ * Verification check results
353
+ */
354
+ interface VerificationChecks {
355
+ /** Is severity a valid enum value? */
356
+ severityValid: boolean;
357
+ /** Is category a valid enum value? */
358
+ categoryValid: boolean;
359
+ /** Is file in the changed files list? */
360
+ fileExists: boolean;
361
+ /** Is line number within file bounds? */
362
+ lineInBounds: boolean;
363
+ /** Is line actually in the diff (changed)? */
364
+ lineInDiff: boolean;
365
+ /** Fuzzy match score for code snippet (0.0-1.0) */
366
+ snippetMatch: number;
367
+ /** Does the issue make sense for this file type? */
368
+ contextValid: boolean;
369
+ /** Is ruleId valid (matches a project rule) or null? */
370
+ ruleIdValid: boolean;
371
+ }
372
+ /**
373
+ * Verified finding with score
374
+ */
375
+ interface VerifiedFinding {
376
+ /** Original finding */
377
+ finding: RawFinding;
378
+ /** Verification score (0.0-1.0) */
379
+ score: number;
380
+ /** Individual check results */
381
+ checks: VerificationChecks;
382
+ /** Action taken based on score */
383
+ action: 'keep' | 'downgrade' | 'discard';
384
+ /** Adjusted severity (if downgraded) */
385
+ adjustedSeverity?: string;
386
+ /** Enum normalization info */
387
+ enumNormalization?: {
388
+ severityOriginal: string;
389
+ severityNormalized: string;
390
+ categoryOriginal: string;
391
+ categoryNormalized: string;
392
+ };
393
+ }
394
+ /**
395
+ * Verification result summary
396
+ */
397
+ interface VerificationResult {
398
+ /** Findings that passed verification */
399
+ verified: VerifiedFinding[];
400
+ /** Findings that were discarded */
401
+ discarded: VerifiedFinding[];
402
+ /** Statistics */
403
+ stats: {
404
+ total: number;
405
+ kept: number;
406
+ downgraded: number;
407
+ discarded: number;
408
+ hallucinationRate: number;
409
+ };
410
+ }
411
+ /**
412
+ * VerificationEngine - validates LLM findings against actual code
413
+ */
414
+ declare class VerificationEngine {
415
+ private cwd;
416
+ private changedFiles;
417
+ private fetchedDiffs;
418
+ private validCategories;
419
+ private categoryAliases;
420
+ private validRuleIds;
421
+ private fileContents;
422
+ constructor(cwd: string, changedFiles: string[], fetchedDiffs: Map<string, FileDiff>, validCategories: string[], categoryAliases?: Record<string, string>, validRuleIds?: Set<string>);
423
+ /**
424
+ * Verify all findings from LLM
425
+ */
426
+ verify(findings: RawFinding[]): Promise<VerificationResult>;
427
+ /**
428
+ * Verify a single finding
429
+ */
430
+ private verifyFinding;
431
+ /**
432
+ * Normalize severity value to RawFindingSeverity type.
433
+ */
434
+ private normalizeSeverity;
435
+ /**
436
+ * Normalize category value
437
+ */
438
+ private normalizeCategory;
439
+ /**
440
+ * Check if line is within file bounds
441
+ */
442
+ private checkLineInBounds;
443
+ /**
444
+ * Check if line is in the diff (actually changed)
445
+ */
446
+ private checkLineInDiff;
447
+ /**
448
+ * Match code snippet against actual file content
449
+ */
450
+ private matchSnippet;
451
+ /**
452
+ * Normalize a single line of code (preserves strings)
453
+ */
454
+ private normalizeCodeLine;
455
+ /**
456
+ * Extract identifiers from code
457
+ */
458
+ private extractIdentifiers;
459
+ /**
460
+ * Validate ruleId - must be null or a valid rule ID from project rules
461
+ */
462
+ private validateRuleId;
463
+ /**
464
+ * Check if finding makes sense for file context
465
+ */
466
+ private checkContext;
467
+ /**
468
+ * Calculate verification score
469
+ */
470
+ private calculateScore;
471
+ /**
472
+ * Downgrade severity by one level.
473
+ * Returns the next lower severity or 'info' if already at lowest.
474
+ */
475
+ private downgradeSeverity;
476
+ /**
477
+ * Get file content (with caching)
478
+ */
479
+ private getFileContent;
480
+ }
481
+ /**
482
+ * Create a VerificationEngine instance
483
+ */
484
+ declare function createVerificationEngine(cwd: string, changedFiles: string[], fetchedDiffs: Map<string, FileDiff>, validCategories: string[], categoryAliases?: Record<string, string>, validRuleIds?: Set<string>): VerificationEngine;
485
+
486
+ /**
487
+ * @module @kb-labs/review-llm/llm-lite/category-validator
488
+ * Dynamic category discovery from rules directory.
489
+ *
490
+ * Categories are NOT hardcoded - they come from .kb/ai-review/rules/ subdirectories.
491
+ */
492
+ /**
493
+ * CategoryValidator - discovers and validates categories dynamically
494
+ *
495
+ * Categories come from .kb/ai-review/rules/ subdirectories.
496
+ * No hardcoded categories - fully configurable per project.
497
+ */
498
+ declare class CategoryValidator {
499
+ private validCategories;
500
+ private categoryAliases;
501
+ private defaultCategory;
502
+ private initialized;
503
+ /**
504
+ * Initialize validator - discovers categories from config-defined rules directory
505
+ *
506
+ * @param cwd - Project root directory (ctx.cwd from command handler)
507
+ */
508
+ init(cwd: string): Promise<void>;
509
+ /**
510
+ * Validate and normalize a category value
511
+ */
512
+ validate(category: string): {
513
+ valid: boolean;
514
+ normalized: string;
515
+ };
516
+ /**
517
+ * Get list of valid categories
518
+ */
519
+ getValidCategories(): string[];
520
+ /**
521
+ * Get category aliases map
522
+ */
523
+ getCategoryAliases(): Record<string, string>;
524
+ /**
525
+ * Check if validator has been initialized
526
+ */
527
+ isInitialized(): boolean;
528
+ }
529
+ /**
530
+ * Discover valid categories from rules directory
531
+ *
532
+ * Reads subdirectories of .kb/ai-review/rules/ (or config-defined path).
533
+ * Each subdirectory name becomes a valid category.
534
+ *
535
+ * @example
536
+ * .kb/ai-review/rules/
537
+ * ├── security/ → "security"
538
+ * ├── naming/ → "naming"
539
+ * ├── architecture/ → "architecture"
540
+ * └── .hidden/ → (ignored)
541
+ */
542
+ declare function discoverCategories(cwd: string): Promise<string[]>;
543
+ /**
544
+ * Build category aliases from discovered categories
545
+ *
546
+ * Maps common LLM outputs to actual category names (if they exist in the project).
547
+ */
548
+ declare function buildCategoryAliases(validCategories: string[]): Record<string, string>;
549
+ /**
550
+ * Create a CategoryValidator instance
551
+ */
552
+ declare function createCategoryValidator(): CategoryValidator;
553
+ /**
554
+ * Get or create the global CategoryValidator instance
555
+ */
556
+ declare function getCategoryValidator(cwd: string): Promise<CategoryValidator>;
557
+
558
+ export { CategoryValidator, DEFAULT_BUDGET, LLMLiteAnalyzer, type LLMLiteRequest, type LLMLiteResult, type RawFinding, type ToolBudget, type ToolCall, ToolExecutor, type ToolResult, type VerificationChecks, VerificationEngine, type VerificationResult, type VerifiedFinding, buildCategoryAliases, buildToolDefinitions, createCategoryValidator, createToolExecutor, createVerificationEngine, discoverCategories, getCategoryValidator, runLLMLiteAnalysis };