@kb-labs/review-core 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.
package/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # @kb-labs/review-core
2
+
3
+ Core orchestration logic for AI Review plugin.
4
+
5
+ ## Overview
6
+
7
+ This package contains the main orchestrator that coordinates:
8
+ - Heuristic analysis engines (ESLint, Ruff, etc.)
9
+ - LLM-based analyzers (naming, architecture, logic bugs)
10
+ - Caching via State Broker
11
+ - Deduplication with engine type priority
12
+ - Analytics tracking
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import { runReview } from '@kb-labs/review-core';
18
+
19
+ const result = await runReview({
20
+ mode: 'heuristic', // 'heuristic' | 'full' | 'llm'
21
+ scope: 'changed', // 'all' | 'changed' | 'staged'
22
+ cwd: '/path/to/project',
23
+ files: ['src/**/*.ts'],
24
+ presetId: 'typescript-strict',
25
+ config: {
26
+ eslintConfig: '.eslintrc.json',
27
+ },
28
+ });
29
+
30
+ console.log(`Found ${result.findings.length} issues`);
31
+ console.log(`Analyzed ${result.metadata.analyzedFiles} files`);
32
+ console.log(`Duration: ${result.metadata.duration}ms`);
33
+ ```
34
+
35
+ ## Review Modes
36
+
37
+ ### heuristic (CI Mode)
38
+
39
+ Fast, deterministic analysis using only heuristic engines:
40
+ - ✅ ESLint, Ruff, golangci-lint, Clippy
41
+ - ✅ No LLM calls (free, fast, predictable)
42
+ - ✅ Perfect for CI pipelines
43
+ - ❌ Limited to syntactic issues
44
+
45
+ ```typescript
46
+ await runReview({
47
+ mode: 'heuristic',
48
+ scope: 'changed',
49
+ cwd: process.cwd(),
50
+ });
51
+ ```
52
+
53
+ **Use cases:**
54
+ - CI/CD pipelines
55
+ - Pre-commit hooks
56
+ - Quick local checks
57
+
58
+ ### full (Local Mode)
59
+
60
+ Comprehensive analysis with heuristic + LLM:
61
+ - ✅ All heuristic engines
62
+ - ✅ LLM analyzers for complex issues
63
+ - ✅ Cached LLM results (fast on second run)
64
+ - ❌ Costs tokens (configurable budget)
65
+
66
+ ```typescript
67
+ await runReview({
68
+ mode: 'full',
69
+ scope: 'all',
70
+ cwd: process.cwd(),
71
+ presetId: 'typescript-strict',
72
+ });
73
+ ```
74
+
75
+ **Use cases:**
76
+ - Local development
77
+ - Pre-PR review
78
+ - Weekly codebase audits
79
+
80
+ ### llm (Deep Analysis Mode)
81
+
82
+ LLM-only analysis for semantic issues:
83
+ - ❌ No heuristic engines
84
+ - ✅ LLM analyzers for naming, architecture, logic bugs
85
+ - ✅ Best quality (uses large tier LLM)
86
+ - ❌ Most expensive
87
+
88
+ ```typescript
89
+ await runReview({
90
+ mode: 'llm',
91
+ scope: 'staged',
92
+ cwd: process.cwd(),
93
+ });
94
+ ```
95
+
96
+ **Use cases:**
97
+ - Architecture reviews
98
+ - Complex refactoring
99
+ - Semantic bug detection
100
+
101
+ ## Scope Options
102
+
103
+ Control which files to analyze:
104
+
105
+ - **`all`** - Analyze entire codebase
106
+ - **`changed`** - Analyze files changed vs main branch (git diff)
107
+ - **`staged`** - Analyze only staged files (git diff --staged)
108
+
109
+ ```typescript
110
+ await runReview({
111
+ mode: 'heuristic',
112
+ scope: 'changed', // Only changed files
113
+ cwd: process.cwd(),
114
+ });
115
+ ```
116
+
117
+ ## Platform Integration
118
+
119
+ The orchestrator uses KB Labs platform composables:
120
+
121
+ ```typescript
122
+ import { useLLM, useCache, useAnalytics } from '@kb-labs/sdk';
123
+
124
+ // Inside orchestrator
125
+ const llm = useLLM({ tier: 'medium' });
126
+ const cache = useCache();
127
+ const analytics = useAnalytics();
128
+
129
+ // LLM tier selection
130
+ // - 'small': Simple tasks (categorization, quick fixes)
131
+ // - 'medium': Standard analysis (most LLM analyzers)
132
+ // - 'large': Complex reasoning (architecture, logic bugs)
133
+ ```
134
+
135
+ ## Caching
136
+
137
+ Results are cached via State Broker for fast repeat analysis:
138
+
139
+ ```typescript
140
+ // First run: ~10s (runs ESLint)
141
+ await runReview({ mode: 'heuristic', scope: 'all', cwd: '.' });
142
+
143
+ // Second run: ~100ms (cached)
144
+ await runReview({ mode: 'heuristic', scope: 'all', cwd: '.' });
145
+ ```
146
+
147
+ Cache keys include:
148
+ - Engine ID (eslint, ruff, etc.)
149
+ - Scope (all, changed, staged)
150
+ - Preset ID
151
+ - File patterns
152
+
153
+ **Cache invalidation:**
154
+ - TTL: 1 hour
155
+ - Manual: Clear State Broker cache
156
+ - Automatic: File content hash changes (TODO)
157
+
158
+ ## Deduplication
159
+
160
+ Findings from multiple engines are deduplicated using:
161
+
162
+ 1. **Fingerprint**: `sha1(ruleId|file|bucket|snippetHash)`
163
+ 2. **Engine Type Priority**: compiler > linter > sast > ast > llm
164
+ 3. **Severity**: blocker > high > medium > low > info (if same type)
165
+
166
+ Example:
167
+ ```typescript
168
+ // Input: 3 findings for same issue
169
+ // - ESLint: "no-unused-vars" (linter, medium)
170
+ // - TSC: "TS6133" (compiler, high)
171
+ // - Semgrep: "unused-variable" (sast, low)
172
+
173
+ // Output: 1 finding (TSC wins: compiler > linter > sast)
174
+ {
175
+ id: 'tsc:/path/file.ts:TS6133:10:5',
176
+ engine: 'tsc',
177
+ severity: 'high',
178
+ message: "'foo' is declared but never used.",
179
+ }
180
+ ```
181
+
182
+ ## Analytics
183
+
184
+ The orchestrator tracks analytics events:
185
+
186
+ - `review:started` - Review started
187
+ - `review:completed` - Review completed successfully
188
+ - `review:failed` - Review failed with error
189
+
190
+ Metrics:
191
+ - Mode used
192
+ - Findings count
193
+ - Duration
194
+ - Engines used
195
+ - Files analyzed
196
+
197
+ ## Configuration
198
+
199
+ Pass config via ReviewRequest:
200
+
201
+ ```typescript
202
+ await runReview({
203
+ mode: 'full',
204
+ scope: 'all',
205
+ cwd: '/path/to/project',
206
+ files: ['src/**/*.ts', 'src/**/*.tsx'],
207
+ presetId: 'typescript-strict',
208
+ config: {
209
+ eslintConfig: '.eslintrc.json',
210
+ // Add more engine configs here
211
+ },
212
+ });
213
+ ```
214
+
215
+ ## Architecture
216
+
217
+ ```
218
+ ReviewOrchestrator
219
+
220
+ ├─ runHeuristicAnalysis()
221
+ │ ├─ runESLint() → analyzeWithESLint()
222
+ │ ├─ runRuff() (TODO)
223
+ │ ├─ runGolangci() (TODO)
224
+ │ └─ deduplicateFindings()
225
+
226
+ ├─ runFullAnalysis()
227
+ │ ├─ runHeuristicAnalysis()
228
+ │ ├─ runLLMAnalyzers() (TODO)
229
+ │ └─ deduplicateFindings()
230
+
231
+ └─ runLLMAnalysis()
232
+ ├─ runLLMAnalyzers() (TODO)
233
+ └─ deduplicateFindings()
234
+ ```
235
+
236
+ ## Error Handling
237
+
238
+ The orchestrator catches and tracks errors:
239
+
240
+ ```typescript
241
+ try {
242
+ const result = await runReview(request);
243
+ console.log('Success:', result.findings.length);
244
+ } catch (error) {
245
+ // Analytics tracked automatically
246
+ console.error('Review failed:', error);
247
+ }
248
+ ```
249
+
250
+ ## Future Enhancements
251
+
252
+ ### Phase 1 (Current)
253
+ - ✅ Heuristic mode with ESLint
254
+ - ✅ Deduplication with engine type priority
255
+ - ✅ Platform integration (useLLM, useCache, useAnalytics)
256
+ - ⏳ CLI commands
257
+
258
+ ### Phase 2
259
+ - [ ] Full mode with LLM analyzers
260
+ - [ ] More heuristic engines (Ruff, golangci, Clippy)
261
+ - [ ] Content-hash based caching
262
+ - [ ] Preset system
263
+
264
+ ### Phase 3
265
+ - [ ] LLM mode for semantic analysis
266
+ - [ ] Agent-powered preset generation
267
+ - [ ] Interactive review UI
268
+ - [ ] Auto-fix support
@@ -0,0 +1,412 @@
1
+ import { ReviewRequest, ReviewResult, PresetDefinition, InputFile, IDiffProvider, BatchDiffRequest, BatchDiffResult, FileDiff, ReviewFinding } from '@kb-labs/review-contracts';
2
+ export { BatchDiffRequest, BatchDiffResult, DiffHunk, FileDiff } from '@kb-labs/review-contracts';
3
+
4
+ /**
5
+ * @module @kb-labs/review-core/orchestrator
6
+ * Main orchestrator for code review operations.
7
+ *
8
+ * Coordinates heuristic engines, LLM analysis, caching, and deduplication.
9
+ */
10
+
11
+ /**
12
+ * Review orchestrator.
13
+ *
14
+ * Main entry point for running code reviews.
15
+ * Thread-safe: all state is passed through parameters, not instance variables.
16
+ */
17
+ declare class ReviewOrchestrator {
18
+ /**
19
+ * Run code review.
20
+ *
21
+ * @param request - Review request
22
+ * @returns Review result with findings
23
+ */
24
+ review(request: ReviewRequest): Promise<ReviewResult>;
25
+ /**
26
+ * Run analysis based on review mode.
27
+ */
28
+ private runAnalysis;
29
+ /**
30
+ * Run heuristic-only analysis (CI mode).
31
+ *
32
+ * Fast, deterministic analysis using linters via CLI.
33
+ * No LLM calls.
34
+ */
35
+ private runHeuristicAnalysis;
36
+ /**
37
+ * Run full analysis (heuristic + LLM-Lite).
38
+ *
39
+ * Runs heuristic analysis (ESLint, etc.) and LLM-Lite in parallel.
40
+ * Combines deterministic linting with intelligent code review.
41
+ * Both use caching for incremental analysis.
42
+ */
43
+ private runFullAnalysis;
44
+ /**
45
+ * Run LLM-Lite analysis (v2) with incremental caching.
46
+ *
47
+ * Uses batch tools, diff-based context, and anti-hallucination verification.
48
+ * Caches findings by file content hash to skip unchanged files.
49
+ */
50
+ private runLLMAnalysis;
51
+ /**
52
+ * Resolve file patterns to absolute paths.
53
+ *
54
+ * Note: Files must be provided in request.files.
55
+ * Pattern resolution should happen at CLI layer using ctx.runtime.fs.glob()
56
+ */
57
+ private resolvePatterns;
58
+ /**
59
+ * Count files to be analyzed.
60
+ */
61
+ private countFiles;
62
+ /**
63
+ * Get list of engines that produced findings.
64
+ */
65
+ private getEnginesUsed;
66
+ /**
67
+ * Group findings by type.
68
+ */
69
+ private groupByType;
70
+ /**
71
+ * Generate cache key for heuristic analysis.
72
+ */
73
+ private generateCacheKey;
74
+ }
75
+ /**
76
+ * Quick helper to run code review.
77
+ */
78
+ declare function runReview(request: ReviewRequest): Promise<ReviewResult>;
79
+
80
+ /**
81
+ * @module @kb-labs/review-core/presets/preset-loader
82
+ * Preset loading and resolution logic
83
+ */
84
+
85
+ /**
86
+ * Preset loader
87
+ * Resolves presets from builtin definitions and kb.config.json
88
+ */
89
+ declare class PresetLoader {
90
+ private presets;
91
+ private configLoaded;
92
+ constructor();
93
+ /**
94
+ * Load builtin presets
95
+ */
96
+ private loadBuiltinPresets;
97
+ /**
98
+ * Resolve preset inheritance (extends) with cycle detection
99
+ */
100
+ private resolveInheritance;
101
+ /**
102
+ * Load custom presets from kb.config.json and preset files
103
+ * Called lazily on first preset access
104
+ */
105
+ private loadConfigPresets;
106
+ /**
107
+ * Auto-scan .kb/ai-review/presets/ directory for preset files
108
+ */
109
+ private scanPresetsDirectory;
110
+ /**
111
+ * Load preset from JSON file
112
+ */
113
+ private loadPresetFromFile;
114
+ /**
115
+ * Load atomic rule from .md file
116
+ */
117
+ private loadAtomicRule;
118
+ /**
119
+ * Compose atomic rules into convention text
120
+ */
121
+ private composeRules;
122
+ /**
123
+ * Apply atomic rules composition to preset
124
+ * Supports dynamic categories - any category name defined in atomicRules
125
+ */
126
+ private applyAtomicRules;
127
+ /**
128
+ * Get preset by ID (with inheritance resolved)
129
+ */
130
+ getPreset(id: string): Promise<PresetDefinition | undefined>;
131
+ /**
132
+ * Get preset by ID or throw error
133
+ */
134
+ getPresetOrThrow(id: string): Promise<PresetDefinition>;
135
+ /**
136
+ * List all available presets
137
+ */
138
+ listPresets(): Promise<PresetDefinition[]>;
139
+ /**
140
+ * Register custom preset
141
+ */
142
+ registerPreset(preset: PresetDefinition): void;
143
+ }
144
+ /**
145
+ * Get global preset loader
146
+ */
147
+ declare function getPresetLoader(): PresetLoader;
148
+ /**
149
+ * Load preset by ID
150
+ */
151
+ declare function loadPreset(id: string): Promise<PresetDefinition>;
152
+
153
+ /**
154
+ * @module @kb-labs/review-core/presets/builtin-presets
155
+ * Built-in preset definitions
156
+ */
157
+
158
+ /**
159
+ * All built-in presets
160
+ */
161
+ declare const builtinPresets: PresetDefinition[];
162
+
163
+ /**
164
+ * @module @kb-labs/review-core/git-scope
165
+ * Git scope resolver for diff-based reviews.
166
+ *
167
+ * Supports nested git repositories (submodules) common in monorepos.
168
+ */
169
+
170
+ interface GitScopeOptions {
171
+ /** Working directory (root of monorepo) */
172
+ cwd: string;
173
+ /** Repository names to include (e.g., ['kb-labs-core', 'kb-labs-cli']) */
174
+ repos: string[];
175
+ /** Include staged files */
176
+ includeStaged?: boolean;
177
+ /** Include unstaged files */
178
+ includeUnstaged?: boolean;
179
+ /** Include untracked files */
180
+ includeUntracked?: boolean;
181
+ }
182
+ interface ScopedFiles {
183
+ /** Files with content ready for review */
184
+ files: InputFile[];
185
+ /** Summary of what was found */
186
+ summary: {
187
+ repos: string[];
188
+ staged: number;
189
+ unstaged: number;
190
+ untracked: number;
191
+ total: number;
192
+ };
193
+ }
194
+ /**
195
+ * Resolve git scope to list of changed files.
196
+ *
197
+ * For each repo in scope:
198
+ * 1. Detect if it's a nested git repo (has .git)
199
+ * 2. Get git status from that repo
200
+ * 3. Read file contents
201
+ * 4. Return as InputFile[] with paths relative to root
202
+ */
203
+ declare function resolveGitScope(options: GitScopeOptions): Promise<ScopedFiles>;
204
+ /**
205
+ * Detect all submodules/nested repos in cwd.
206
+ * Reads paths from .gitmodules — no hardcoded category directories.
207
+ * Returns relative paths as they appear in .gitmodules (e.g. "platform/kb-labs-core").
208
+ */
209
+ declare function discoverRepos(cwd: string): Promise<string[]>;
210
+ /**
211
+ * Get all repos with uncommitted changes
212
+ */
213
+ declare function getReposWithChanges(cwd: string): Promise<string[]>;
214
+
215
+ /**
216
+ * @module @kb-labs/review-core/diff-provider
217
+ * Git diff fetching and parsing for LLM-lite review mode.
218
+ *
219
+ * Provides diff-based context instead of full file content.
220
+ */
221
+
222
+ /**
223
+ * DiffProvider - fetches and parses git diffs
224
+ */
225
+ declare class DiffProvider implements IDiffProvider {
226
+ private git;
227
+ private cwd;
228
+ constructor(cwd: string);
229
+ /**
230
+ * Get diffs for multiple files in one call (batch operation)
231
+ */
232
+ getDiffs(request: BatchDiffRequest): Promise<BatchDiffResult>;
233
+ /**
234
+ * Get diff for a single file
235
+ */
236
+ getFileDiff(file: string, staged?: boolean, unstaged?: boolean, maxLines?: number): Promise<FileDiff | null>;
237
+ /**
238
+ * Parse unified diff into structured format
239
+ */
240
+ private parseDiff;
241
+ /**
242
+ * Parse hunk content to extract line numbers
243
+ */
244
+ private parseHunkContent;
245
+ /**
246
+ * Check if a line number is in the diff (was changed)
247
+ */
248
+ isLineInDiff(fileDiff: FileDiff, lineNumber: number): boolean;
249
+ /**
250
+ * Get context around a specific line (for verification)
251
+ */
252
+ getLineContext(file: string, lineNumber: number, contextLines?: number): Promise<{
253
+ lines: string[];
254
+ startLine: number;
255
+ } | null>;
256
+ }
257
+ /**
258
+ * Create a DiffProvider instance
259
+ */
260
+ declare function createDiffProvider(cwd: string): DiffProvider;
261
+
262
+ /**
263
+ * @module @kb-labs/review-core/findings-cache
264
+ * Cache for LLM review findings based on file content hash.
265
+ *
266
+ * Enables incremental review:
267
+ * - Skip unchanged files (return cached findings)
268
+ * - Track "known issues" vs "new issues" for changed files
269
+ */
270
+
271
+ /**
272
+ * Cached finding with file hash
273
+ */
274
+ interface CachedFinding extends ReviewFinding {
275
+ /** Hash of file content when finding was created */
276
+ contentHash: string;
277
+ /** Timestamp when finding was cached */
278
+ cachedAt: number;
279
+ }
280
+ /**
281
+ * Cache entry for a single file
282
+ */
283
+ interface FileCacheEntry {
284
+ /** File path */
285
+ path: string;
286
+ /** Content hash */
287
+ contentHash: string;
288
+ /** Findings for this file */
289
+ findings: CachedFinding[];
290
+ /** When this entry was created */
291
+ createdAt: number;
292
+ /** When this entry was last accessed */
293
+ lastAccessedAt: number;
294
+ }
295
+ /**
296
+ * Full cache structure
297
+ */
298
+ interface FindingsCacheData {
299
+ /** Version for cache format migrations */
300
+ version: number;
301
+ /** Cache entries by file path */
302
+ entries: Record<string, FileCacheEntry>;
303
+ /** Cache metadata */
304
+ metadata: {
305
+ createdAt: number;
306
+ lastUpdatedAt: number;
307
+ totalFindings: number;
308
+ totalFiles: number;
309
+ };
310
+ }
311
+ /**
312
+ * Result of cache lookup
313
+ */
314
+ interface CacheLookupResult {
315
+ /** Files that have valid cache (unchanged) */
316
+ cached: {
317
+ file: InputFile;
318
+ findings: ReviewFinding[];
319
+ }[];
320
+ /** Files that need fresh analysis (changed or not cached) */
321
+ uncached: InputFile[];
322
+ /** Statistics */
323
+ stats: {
324
+ cachedFiles: number;
325
+ uncachedFiles: number;
326
+ cachedFindings: number;
327
+ };
328
+ }
329
+ /**
330
+ * Result of incremental comparison
331
+ */
332
+ interface IncrementalResult {
333
+ /** New findings (not seen before) */
334
+ newFindings: ReviewFinding[];
335
+ /** Known findings (seen in previous review) */
336
+ knownFindings: ReviewFinding[];
337
+ /** Findings from unchanged (cached) files */
338
+ cachedFindings: ReviewFinding[];
339
+ /** Statistics */
340
+ stats: {
341
+ new: number;
342
+ known: number;
343
+ cached: number;
344
+ total: number;
345
+ };
346
+ }
347
+ /**
348
+ * Generate content hash for a file.
349
+ *
350
+ * Uses SHA-256 for cryptographic strength, truncated to 16 hex chars (64 bits).
351
+ * This provides sufficient collision resistance for cache invalidation while
352
+ * keeping cache keys reasonably short.
353
+ *
354
+ * @param content - File content to hash
355
+ * @returns 16-character hex string (first 64 bits of SHA-256)
356
+ */
357
+ declare function hashFileContent(content: string): string;
358
+ /**
359
+ * Generate finding signature for deduplication
360
+ * Two findings are "same" if they have same file, line range, and category
361
+ */
362
+ declare function findingSignature(finding: ReviewFinding): string;
363
+ /**
364
+ * Findings cache manager
365
+ */
366
+ declare class FindingsCache {
367
+ private cwd;
368
+ private cachePath;
369
+ private data;
370
+ constructor(cwd: string);
371
+ /**
372
+ * Load cache from disk
373
+ */
374
+ load(): Promise<void>;
375
+ /**
376
+ * Save cache to disk
377
+ */
378
+ save(): Promise<void>;
379
+ /**
380
+ * Look up files in cache
381
+ * Returns which files can use cached findings vs need fresh analysis
382
+ */
383
+ lookup(files: InputFile[]): CacheLookupResult;
384
+ /**
385
+ * Update cache with new findings
386
+ */
387
+ update(files: InputFile[], findings: ReviewFinding[]): void;
388
+ /**
389
+ * Compare new findings with cached to find new vs known issues
390
+ */
391
+ compareIncremental(newFindings: ReviewFinding[], cachedFindings: ReviewFinding[]): IncrementalResult;
392
+ /**
393
+ * Clear cache for specific files or all
394
+ */
395
+ clear(files?: string[]): void;
396
+ /**
397
+ * Get cache statistics
398
+ */
399
+ getStats(): {
400
+ totalFiles: number;
401
+ totalFindings: number;
402
+ cacheAge: number;
403
+ };
404
+ private createEmptyCache;
405
+ private stripCacheFields;
406
+ }
407
+ /**
408
+ * Create findings cache instance
409
+ */
410
+ declare function createFindingsCache(cwd: string): FindingsCache;
411
+
412
+ export { type CacheLookupResult, type CachedFinding, DiffProvider, type FileCacheEntry, FindingsCache, type FindingsCacheData, type GitScopeOptions, type IncrementalResult, PresetLoader, ReviewOrchestrator, type ScopedFiles, builtinPresets, createDiffProvider, createFindingsCache, discoverRepos, findingSignature, getPresetLoader, getReposWithChanges, hashFileContent, loadPreset, resolveGitScope, runReview };