@rigour-labs/core 3.0.6 → 4.0.1

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 (46) hide show
  1. package/dist/deep/fact-extractor.d.ts +80 -0
  2. package/dist/deep/fact-extractor.js +626 -0
  3. package/dist/deep/fact-extractor.test.d.ts +1 -0
  4. package/dist/deep/fact-extractor.test.js +547 -0
  5. package/dist/deep/index.d.ts +14 -0
  6. package/dist/deep/index.js +12 -0
  7. package/dist/deep/prompts.d.ts +22 -0
  8. package/dist/deep/prompts.js +374 -0
  9. package/dist/deep/prompts.test.d.ts +1 -0
  10. package/dist/deep/prompts.test.js +220 -0
  11. package/dist/deep/verifier.d.ts +16 -0
  12. package/dist/deep/verifier.js +388 -0
  13. package/dist/deep/verifier.test.d.ts +1 -0
  14. package/dist/deep/verifier.test.js +514 -0
  15. package/dist/gates/deep-analysis.d.ts +28 -0
  16. package/dist/gates/deep-analysis.js +302 -0
  17. package/dist/gates/runner.d.ts +4 -2
  18. package/dist/gates/runner.js +46 -1
  19. package/dist/index.d.ts +10 -0
  20. package/dist/index.js +12 -2
  21. package/dist/inference/cloud-provider.d.ts +34 -0
  22. package/dist/inference/cloud-provider.js +126 -0
  23. package/dist/inference/index.d.ts +17 -0
  24. package/dist/inference/index.js +23 -0
  25. package/dist/inference/model-manager.d.ts +26 -0
  26. package/dist/inference/model-manager.js +106 -0
  27. package/dist/inference/sidecar-provider.d.ts +15 -0
  28. package/dist/inference/sidecar-provider.js +153 -0
  29. package/dist/inference/types.d.ts +77 -0
  30. package/dist/inference/types.js +19 -0
  31. package/dist/settings.d.ts +104 -0
  32. package/dist/settings.js +186 -0
  33. package/dist/storage/db.d.ts +16 -0
  34. package/dist/storage/db.js +132 -0
  35. package/dist/storage/findings.d.ts +14 -0
  36. package/dist/storage/findings.js +38 -0
  37. package/dist/storage/index.d.ts +9 -0
  38. package/dist/storage/index.js +8 -0
  39. package/dist/storage/patterns.d.ts +35 -0
  40. package/dist/storage/patterns.js +62 -0
  41. package/dist/storage/scans.d.ts +42 -0
  42. package/dist/storage/scans.js +55 -0
  43. package/dist/templates/universal-config.js +19 -0
  44. package/dist/types/index.d.ts +438 -15
  45. package/dist/types/index.js +41 -1
  46. package/package.json +6 -2
@@ -0,0 +1,547 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // We test the internal extractFileFacts by importing extractFacts and factsToPromptString
3
+ // But we need to test extraction logic directly, so we also test via the public API
4
+ const mockGlobby = vi.hoisted(() => vi.fn());
5
+ const mockReadFile = vi.hoisted(() => vi.fn());
6
+ vi.mock('globby', () => ({
7
+ globby: mockGlobby,
8
+ }));
9
+ vi.mock('fs-extra', () => ({
10
+ default: {
11
+ readFile: mockReadFile,
12
+ },
13
+ }));
14
+ import { extractFacts, factsToPromptString } from './fact-extractor.js';
15
+ describe('Fact Extractor', () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+ // ── TypeScript extraction ──
20
+ describe('TypeScript/JavaScript extraction', () => {
21
+ it('should extract classes with methods and dependencies', async () => {
22
+ mockGlobby.mockResolvedValue(['src/service.ts']);
23
+ mockReadFile.mockResolvedValue(`
24
+ import { Logger } from './logger';
25
+
26
+ export class UserService {
27
+ constructor(
28
+ private db: Database,
29
+ private logger: Logger
30
+ ) {}
31
+
32
+ async findById(id: string): Promise<User> {
33
+ return this.db.find(id);
34
+ }
35
+
36
+ async create(data: CreateUserDto): Promise<User> {
37
+ this.logger.info('Creating user');
38
+ return this.db.create(data);
39
+ }
40
+
41
+ async update(id: string, data: UpdateUserDto): Promise<User> {
42
+ return this.db.update(id, data);
43
+ }
44
+
45
+ async delete(id: string): Promise<void> {
46
+ return this.db.delete(id);
47
+ }
48
+ }
49
+ `);
50
+ const facts = await extractFacts('/project');
51
+ expect(facts).toHaveLength(1);
52
+ expect(facts[0].language).toBe('typescript');
53
+ expect(facts[0].classes).toHaveLength(1);
54
+ expect(facts[0].classes[0].name).toBe('UserService');
55
+ expect(facts[0].classes[0].methodCount).toBeGreaterThanOrEqual(4);
56
+ expect(facts[0].classes[0].dependencies).toContain('Database');
57
+ expect(facts[0].classes[0].dependencies).toContain('Logger');
58
+ });
59
+ it('should extract functions with params and async detection', async () => {
60
+ mockGlobby.mockResolvedValue(['src/utils.ts']);
61
+ mockReadFile.mockResolvedValue(`
62
+ export async function processData(input: string, config: Config, options: Options): Promise<Result> {
63
+ if (input.length > 0) {
64
+ if (config.validate) {
65
+ if (options.strict) {
66
+ return validate(input);
67
+ }
68
+ }
69
+ }
70
+ return { data: input };
71
+ }
72
+
73
+ export function simpleHelper(x: number): number {
74
+ return x * 2;
75
+ }
76
+ `);
77
+ const facts = await extractFacts('/project');
78
+ const funcs = facts[0].functions;
79
+ expect(funcs.length).toBeGreaterThanOrEqual(1);
80
+ const processData = funcs.find(f => f.name === 'processData');
81
+ expect(processData).toBeDefined();
82
+ expect(processData.isAsync).toBe(true);
83
+ expect(processData.isExported).toBe(true);
84
+ expect(processData.paramCount).toBe(3);
85
+ expect(processData.maxNesting).toBeGreaterThanOrEqual(3);
86
+ });
87
+ it('should extract imports from ES modules and require', async () => {
88
+ mockGlobby.mockResolvedValue(['src/index.ts']);
89
+ mockReadFile.mockResolvedValue(`
90
+ import { Router } from 'express';
91
+ import path from 'path';
92
+ const fs = require('fs-extra');
93
+ import type { Config } from './types';
94
+
95
+ export function init() {
96
+ const router = Router();
97
+ return router;
98
+ }
99
+ `);
100
+ const facts = await extractFacts('/project');
101
+ expect(facts[0].imports).toContain('express');
102
+ expect(facts[0].imports).toContain('path');
103
+ expect(facts[0].imports).toContain('fs-extra');
104
+ expect(facts[0].imports).toContain('./types');
105
+ });
106
+ it('should extract error handling patterns', async () => {
107
+ mockGlobby.mockResolvedValue(['src/api.ts']);
108
+ mockReadFile.mockResolvedValue(`
109
+ async function fetchData() {
110
+ try {
111
+ const res = await fetch('/api');
112
+ return res.json();
113
+ } catch (err) {
114
+ console.error(err);
115
+ throw err;
116
+ }
117
+ }
118
+
119
+ async function riskyOp() {
120
+ try {
121
+ await doSomething();
122
+ } catch (err) {
123
+ }
124
+ }
125
+
126
+ fetchData().then(data => {
127
+ process(data);
128
+ }).catch(() => {});
129
+ `);
130
+ const facts = await extractFacts('/project');
131
+ expect(facts[0].errorHandling.length).toBeGreaterThanOrEqual(2);
132
+ const tryCatches = facts[0].errorHandling.filter(e => e.type === 'try-catch');
133
+ expect(tryCatches.length).toBeGreaterThanOrEqual(2);
134
+ // Strategy detection finds console.error first → 'log'
135
+ const strategies = tryCatches.map(e => e.strategy);
136
+ expect(strategies.length).toBeGreaterThanOrEqual(2);
137
+ });
138
+ it('should detect test files and count assertions', async () => {
139
+ mockGlobby.mockResolvedValue(['src/utils.test.ts']);
140
+ mockReadFile.mockResolvedValue(`
141
+ import { describe, it, expect } from 'vitest';
142
+
143
+ describe('Utils', () => {
144
+ it('should add numbers', () => {
145
+ expect(add(1, 2)).toBe(3);
146
+ expect(add(0, 0)).toBe(0);
147
+ });
148
+
149
+ it('should handle negatives', () => {
150
+ expect(add(-1, 1)).toBe(0);
151
+ });
152
+ });
153
+ `);
154
+ const facts = await extractFacts('/project');
155
+ expect(facts[0].hasTests).toBe(true);
156
+ expect(facts[0].testAssertions).toBeGreaterThanOrEqual(3);
157
+ });
158
+ it('should extract exports', async () => {
159
+ mockGlobby.mockResolvedValue(['src/types.ts']);
160
+ mockReadFile.mockResolvedValue(`
161
+ export interface User {
162
+ id: string;
163
+ name: string;
164
+ }
165
+
166
+ export const DEFAULT_CONFIG = {};
167
+
168
+ export function createUser(): User {
169
+ return { id: '1', name: 'test' };
170
+ }
171
+
172
+ export type Severity = 'high' | 'low';
173
+ `);
174
+ const facts = await extractFacts('/project');
175
+ expect(facts[0].exports).toContain('User');
176
+ expect(facts[0].exports).toContain('DEFAULT_CONFIG');
177
+ expect(facts[0].exports).toContain('createUser');
178
+ expect(facts[0].exports).toContain('Severity');
179
+ });
180
+ });
181
+ // ── Python extraction ──
182
+ describe('Python extraction', () => {
183
+ it('should extract Python classes', async () => {
184
+ mockGlobby.mockResolvedValue(['app/service.py']);
185
+ mockReadFile.mockResolvedValue(`
186
+ class UserService:
187
+ def __init__(self, db):
188
+ self.db = db
189
+
190
+ def find_by_id(self, user_id):
191
+ return self.db.find(user_id)
192
+
193
+ def create(self, data):
194
+ return self.db.create(data)
195
+
196
+ def _validate(self, data):
197
+ pass
198
+ `);
199
+ const facts = await extractFacts('/project');
200
+ expect(facts[0].language).toBe('python');
201
+ expect(facts[0].classes).toHaveLength(1);
202
+ expect(facts[0].classes[0].name).toBe('UserService');
203
+ expect(facts[0].classes[0].methodCount).toBeGreaterThanOrEqual(3);
204
+ });
205
+ it('should extract Python imports', async () => {
206
+ mockGlobby.mockResolvedValue(['app/main.py']);
207
+ mockReadFile.mockResolvedValue(`
208
+ import os
209
+ from pathlib import Path
210
+ import json
211
+ from typing import Optional
212
+ from app.service import UserService
213
+
214
+ def main():
215
+ svc = UserService()
216
+ return svc
217
+ `);
218
+ const facts = await extractFacts('/project');
219
+ expect(facts[0].imports).toContain('os');
220
+ expect(facts[0].imports).toContain('pathlib');
221
+ expect(facts[0].imports).toContain('app.service');
222
+ });
223
+ });
224
+ // ── Go extraction ──
225
+ describe('Go extraction', () => {
226
+ it('should extract Go structs with fields and methods', async () => {
227
+ mockGlobby.mockResolvedValue(['pkg/server.go']);
228
+ mockReadFile.mockResolvedValue(`package server
229
+
230
+ import (
231
+ "net/http"
232
+ "sync"
233
+ )
234
+
235
+ type Server struct {
236
+ addr string
237
+ port int
238
+ handler http.Handler
239
+ mu sync.Mutex
240
+ *Base
241
+ }
242
+
243
+ func NewServer(addr string, port int) *Server {
244
+ return &Server{addr: addr, port: port}
245
+ }
246
+
247
+ func (s *Server) Start() error {
248
+ s.mu.Lock()
249
+ defer s.mu.Unlock()
250
+ return http.ListenAndServe(s.addr, s.handler)
251
+ }
252
+
253
+ func (s *Server) Stop() error {
254
+ return nil
255
+ }
256
+
257
+ func (s *Server) Handler() http.Handler {
258
+ return s.handler
259
+ }
260
+ `);
261
+ const facts = await extractFacts('/project');
262
+ expect(facts[0].language).toBe('go');
263
+ expect(facts[0].structs).toBeDefined();
264
+ expect(facts[0].structs.length).toBeGreaterThanOrEqual(1);
265
+ const server = facts[0].structs[0];
266
+ expect(server.name).toBe('Server');
267
+ expect(server.fieldCount).toBeGreaterThanOrEqual(4); // addr, port, handler, mu, Base
268
+ expect(server.methodCount).toBeGreaterThanOrEqual(3); // Start, Stop, Handler
269
+ expect(server.methods).toContain('Start');
270
+ expect(server.methods).toContain('Stop');
271
+ expect(server.embeds).toContain('Base');
272
+ });
273
+ it('should extract Go interfaces', async () => {
274
+ mockGlobby.mockResolvedValue(['pkg/store.go']);
275
+ mockReadFile.mockResolvedValue(`package store
276
+
277
+ type Store interface {
278
+ Get(key string) (string, error)
279
+ Set(key string, value string) error
280
+ Delete(key string) error
281
+ List(prefix string) ([]string, error)
282
+ Close() error
283
+ Watch(key string) <-chan Event
284
+ }
285
+
286
+ type SimpleStore struct {
287
+ data map[string]string
288
+ }
289
+ `);
290
+ const facts = await extractFacts('/project');
291
+ expect(facts[0].interfaces).toBeDefined();
292
+ expect(facts[0].interfaces.length).toBeGreaterThanOrEqual(1);
293
+ const store = facts[0].interfaces[0];
294
+ expect(store.name).toBe('Store');
295
+ expect(store.methodCount).toBe(6);
296
+ expect(store.methods).toContain('Get');
297
+ expect(store.methods).toContain('Close');
298
+ });
299
+ it('should extract Go functions with receiver methods', async () => {
300
+ mockGlobby.mockResolvedValue(['pkg/handler.go']);
301
+ mockReadFile.mockResolvedValue(`package handler
302
+
303
+ type Handler struct {
304
+ service Service
305
+ }
306
+
307
+ func NewHandler(svc Service) *Handler {
308
+ return &Handler{service: svc}
309
+ }
310
+
311
+ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
312
+ if r.Method == "GET" {
313
+ h.handleGet(w, r)
314
+ } else {
315
+ h.handlePost(w, r)
316
+ }
317
+ }
318
+
319
+ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
320
+ data, err := h.service.Find(r.URL.Query().Get("id"))
321
+ if err != nil {
322
+ http.Error(w, err.Error(), 500)
323
+ return
324
+ }
325
+ json.NewEncoder(w).Encode(data)
326
+ }
327
+ `);
328
+ const facts = await extractFacts('/project');
329
+ const funcs = facts[0].functions;
330
+ // Should have receiver methods named as Receiver.Method
331
+ const serveHTTP = funcs.find(f => f.name === 'Handler.ServeHTTP');
332
+ expect(serveHTTP).toBeDefined();
333
+ const handleGet = funcs.find(f => f.name === 'Handler.handleGet');
334
+ expect(handleGet).toBeDefined();
335
+ // NewHandler should be a standalone func (no receiver)
336
+ const newHandler = funcs.find(f => f.name === 'NewHandler');
337
+ expect(newHandler).toBeDefined();
338
+ expect(newHandler.isExported).toBe(true);
339
+ });
340
+ it('should count concurrency metrics', async () => {
341
+ mockGlobby.mockResolvedValue(['pkg/worker.go']);
342
+ mockReadFile.mockResolvedValue(`package worker
343
+
344
+ import "sync"
345
+
346
+ func StartWorkers(n int) {
347
+ var mu sync.Mutex
348
+ var wg sync.WaitGroup
349
+ ch := make(chan int, 10)
350
+
351
+ for i := 0; i < n; i++ {
352
+ wg.Add(1)
353
+ go processItem(ch, &mu, &wg)
354
+ }
355
+
356
+ go monitorWorkers(ch)
357
+
358
+ defer close(ch)
359
+ }
360
+
361
+ func processItem(ch chan int, mu *sync.Mutex, wg *sync.WaitGroup) {
362
+ defer wg.Done()
363
+ for item := range ch {
364
+ mu.Lock()
365
+ process(item)
366
+ mu.Unlock()
367
+ }
368
+ }
369
+ `);
370
+ const facts = await extractFacts('/project');
371
+ expect(facts[0].goroutines).toBeGreaterThanOrEqual(2);
372
+ expect(facts[0].channels).toBeGreaterThanOrEqual(1);
373
+ expect(facts[0].defers).toBeGreaterThanOrEqual(2);
374
+ expect(facts[0].mutexes).toBeGreaterThanOrEqual(2);
375
+ });
376
+ });
377
+ // ── Quality metrics ──
378
+ describe('Quality metrics', () => {
379
+ it('should detect magic numbers', async () => {
380
+ mockGlobby.mockResolvedValue(['src/calc.ts']);
381
+ mockReadFile.mockResolvedValue(`
382
+ export function calculate(input: number): number {
383
+ const base = input * 42;
384
+ const tax = base * 17;
385
+ const fee = 325 + 1250;
386
+ const limit = 9999;
387
+ return base + tax + fee + limit;
388
+ }
389
+ `);
390
+ const facts = await extractFacts('/project');
391
+ expect(facts[0].magicNumbers).toBeGreaterThanOrEqual(3);
392
+ });
393
+ it('should count TODOs', async () => {
394
+ mockGlobby.mockResolvedValue(['src/app.ts']);
395
+ mockReadFile.mockResolvedValue(`
396
+ // TODO: implement caching
397
+ export function getData() {
398
+ // FIXME: this is slow
399
+ const data = fetch('/api');
400
+ // HACK: workaround for bug
401
+ return data;
402
+ }
403
+ `);
404
+ const facts = await extractFacts('/project');
405
+ expect(facts[0].todoCount).toBe(4); // TODO, FIXME, HACK, WORKAROUND
406
+ });
407
+ it('should calculate comment ratio', async () => {
408
+ mockGlobby.mockResolvedValue(['src/app.ts']);
409
+ mockReadFile.mockResolvedValue(`
410
+ // This is a comment
411
+ // Another comment
412
+ export function foo() {
413
+ return 1;
414
+ }
415
+
416
+ function bar() {
417
+ return 2;
418
+ }
419
+ `);
420
+ const facts = await extractFacts('/project');
421
+ expect(facts[0].commentRatio).toBeGreaterThan(0);
422
+ });
423
+ it('should skip trivial files (< 3 lines)', async () => {
424
+ mockGlobby.mockResolvedValue(['src/empty.ts']);
425
+ mockReadFile.mockResolvedValue(`// empty\n`);
426
+ const facts = await extractFacts('/project');
427
+ expect(facts).toHaveLength(0);
428
+ });
429
+ });
430
+ // ── factsToPromptString ──
431
+ describe('factsToPromptString', () => {
432
+ it('should serialize file facts to a prompt string', () => {
433
+ const facts = [
434
+ {
435
+ path: 'src/service.ts',
436
+ language: 'typescript',
437
+ lineCount: 150,
438
+ classes: [{
439
+ name: 'UserService',
440
+ lineStart: 5,
441
+ lineEnd: 140,
442
+ methodCount: 8,
443
+ methods: ['find', 'create', 'update', 'delete', 'validate', 'transform', 'cache', 'notify'],
444
+ publicMethods: ['find', 'create', 'update', 'delete'],
445
+ lineCount: 135,
446
+ dependencies: ['Database', 'Logger'],
447
+ }],
448
+ functions: [],
449
+ imports: ['express', './types', 'lodash'],
450
+ exports: ['UserService'],
451
+ errorHandling: [
452
+ { type: 'try-catch', lineStart: 10, isEmpty: false, strategy: 'throw' },
453
+ { type: 'try-catch', lineStart: 30, isEmpty: true, strategy: 'ignore' },
454
+ ],
455
+ testAssertions: 0,
456
+ hasTests: false,
457
+ },
458
+ ];
459
+ const result = factsToPromptString(facts);
460
+ expect(result).toContain('FILE: src/service.ts');
461
+ expect(result).toContain('CLASS UserService');
462
+ expect(result).toContain('135 lines');
463
+ expect(result).toContain('8 methods');
464
+ expect(result).toContain('deps: Database, Logger');
465
+ expect(result).toContain('ERROR_HANDLING');
466
+ expect(result).toContain('1 empty catches');
467
+ expect(result).toContain('IMPORTS: 3');
468
+ });
469
+ it('should include Go structs and concurrency info', () => {
470
+ const facts = [
471
+ {
472
+ path: 'pkg/worker.go',
473
+ language: 'go',
474
+ lineCount: 200,
475
+ classes: [],
476
+ functions: [{
477
+ name: 'StartWorkers',
478
+ lineStart: 10,
479
+ lineEnd: 60,
480
+ lineCount: 50,
481
+ paramCount: 2,
482
+ params: ['n int', 'config Config'],
483
+ maxNesting: 3,
484
+ hasReturn: true,
485
+ isAsync: true,
486
+ isExported: true,
487
+ }],
488
+ imports: ['sync', 'context'],
489
+ exports: [],
490
+ errorHandling: [],
491
+ testAssertions: 0,
492
+ hasTests: false,
493
+ structs: [{
494
+ name: 'Worker',
495
+ lineStart: 5,
496
+ lineEnd: 15,
497
+ fieldCount: 4,
498
+ methodCount: 3,
499
+ methods: ['Start', 'Stop', 'Process'],
500
+ lineCount: 10,
501
+ embeds: [],
502
+ }],
503
+ goroutines: 5,
504
+ channels: 2,
505
+ defers: 3,
506
+ mutexes: 1,
507
+ },
508
+ ];
509
+ const result = factsToPromptString(facts);
510
+ expect(result).toContain('STRUCT Worker');
511
+ expect(result).toContain('4 fields');
512
+ expect(result).toContain('3 methods');
513
+ expect(result).toContain('CONCURRENCY');
514
+ expect(result).toContain('goroutines:5');
515
+ expect(result).toContain('channels:2');
516
+ expect(result).toContain('defers:3');
517
+ expect(result).toContain('mutexes:1');
518
+ });
519
+ it('should respect maxChars budget', () => {
520
+ const manyFacts = Array.from({ length: 100 }, (_, i) => ({
521
+ path: `src/file${i}.ts`,
522
+ language: 'typescript',
523
+ lineCount: 100,
524
+ classes: [],
525
+ functions: [{
526
+ name: `func${i}`,
527
+ lineStart: 1,
528
+ lineEnd: 50,
529
+ lineCount: 50,
530
+ paramCount: 2,
531
+ params: ['a', 'b'],
532
+ maxNesting: 2,
533
+ hasReturn: true,
534
+ isAsync: false,
535
+ isExported: true,
536
+ }],
537
+ imports: ['express', 'lodash', 'react'],
538
+ exports: [`func${i}`],
539
+ errorHandling: [],
540
+ testAssertions: 0,
541
+ hasTests: false,
542
+ }));
543
+ const result = factsToPromptString(manyFacts, 500);
544
+ expect(result.length).toBeLessThanOrEqual(600); // Allow some slack
545
+ });
546
+ });
547
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Deep Analysis Pipeline — AST → LLM → Verify
3
+ *
4
+ * Step 1: AST extracts structured facts from code
5
+ * Step 2: LLM interprets facts and identifies quality issues
6
+ * Step 3: AST verifies LLM isn't hallucinating
7
+ *
8
+ * Neither AST nor LLM works alone. Together they're accurate.
9
+ */
10
+ export { extractFacts, factsToPromptString } from './fact-extractor.js';
11
+ export type { FileFacts, ClassFact, FunctionFact, ErrorHandlingFact, StructFact, InterfaceFact } from './fact-extractor.js';
12
+ export { buildAnalysisPrompt, buildCrossFilePrompt, chunkFacts, DEEP_SYSTEM_PROMPT } from './prompts.js';
13
+ export { verifyFindings } from './verifier.js';
14
+ export type { VerifiedFinding } from './verifier.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Deep Analysis Pipeline — AST → LLM → Verify
3
+ *
4
+ * Step 1: AST extracts structured facts from code
5
+ * Step 2: LLM interprets facts and identifies quality issues
6
+ * Step 3: AST verifies LLM isn't hallucinating
7
+ *
8
+ * Neither AST nor LLM works alone. Together they're accurate.
9
+ */
10
+ export { extractFacts, factsToPromptString } from './fact-extractor.js';
11
+ export { buildAnalysisPrompt, buildCrossFilePrompt, chunkFacts, DEEP_SYSTEM_PROMPT } from './prompts.js';
12
+ export { verifyFindings } from './verifier.js';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Prompt Engineering — Step 2 of the three-step pipeline.
3
+ * Constructs structured prompts that ask the LLM to interpret AST-extracted facts.
4
+ */
5
+ import type { FileFacts } from './fact-extractor.js';
6
+ /**
7
+ * System prompt that defines the LLM's role and output format.
8
+ */
9
+ export declare const DEEP_SYSTEM_PROMPT = "You are an expert code reviewer and software architect performing deep quality analysis. You receive AST-extracted facts about a codebase and must identify quality issues, anti-patterns, and best practice violations.\n\nIMPORTANT RULES:\n1. ONLY report issues you can verify from the provided facts. Do NOT hallucinate files, classes, or functions.\n2. Every finding MUST reference a real file and entity from the facts.\n3. Be specific: include file paths, struct/class names, function names, line counts.\n4. Assign confidence scores honestly: 0.9+ only for certain issues, 0.5-0.7 for probable issues.\n5. Respond ONLY with valid JSON matching the schema below. No explanation text outside JSON.\n6. AIM for 5-15 findings per batch. Be thorough \u2014 report ALL issues you can identify, not just the most obvious ones.\n7. For Go code: treat structs as classes, receiver methods as class methods. Check Go idioms specifically.\n\nOUTPUT SCHEMA:\n{\n \"findings\": [\n {\n \"category\": \"string (see CATEGORIES below)\",\n \"severity\": \"string (critical|high|medium|low|info)\",\n \"file\": \"string (exact file path from facts)\",\n \"line\": \"number or null\",\n \"description\": \"string (what the issue is, referencing specific entities)\",\n \"suggestion\": \"string (actionable fix recommendation)\",\n \"confidence\": \"number 0.0-1.0\"\n }\n ]\n}\n\nCATEGORIES:\n SOLID Principles:\n srp_violation - Single file/struct/class handles multiple unrelated responsibilities\n ocp_violation - Code requires modification (not extension) for new behavior\n lsp_violation - Subtypes break substitutability contracts\n isp_violation - Interface has too many methods forcing unnecessary implementations\n dip_violation - High-level modules depend directly on low-level implementations\n\n Design Patterns & Anti-patterns:\n god_class - Class/struct with too many fields, methods, or responsibilities (>8 methods or >300 lines)\n god_function - Function exceeding 50 lines or doing too many things\n feature_envy - Function/method uses another module's data more than its own\n shotgun_surgery - A single change requires modifying many files\n long_params - Function with 4+ parameters (use struct/options pattern)\n data_clump - Same group of fields/params repeated across multiple structs/functions\n inappropriate_intimacy - Two modules too tightly coupled, accessing each other's internals\n primitive_obsession - Using primitives instead of domain types (string for email, int for ID)\n lazy_class - Struct/class that does too little to justify its existence\n speculative_generality - Over-engineered abstractions not justified by current usage\n refused_bequest - Subtype/implementation ignores inherited behavior\n\n DRY & Duplication:\n dry_violation - Duplicated logic across files that should be extracted\n copy_paste_code - Nearly identical functions/methods in different files\n\n Error Handling:\n error_inconsistency - Mixed error handling strategies in same package/module\n empty_catch - Empty catch/except blocks that silently swallow errors\n error_swallowing - Errors logged but not propagated when they should be\n missing_error_check - Return values (especially errors) not checked\n panic_in_library - Library code using panic/os.Exit instead of returning errors\n\n Concurrency (Go/Rust/async languages):\n race_condition - Shared mutable state accessed without synchronization\n goroutine_leak - Goroutines spawned without cancellation/context mechanism\n missing_context - Functions that should accept context.Context but don't\n channel_misuse - Unbuffered channels that could deadlock, or missing close()\n mutex_scope - Mutex held too long or across I/O operations\n\n Testing:\n test_quality - Insufficient assertions, no edge cases, weak coverage\n test_coupling - Tests tightly coupled to implementation details\n missing_test - Complex public function/method with no corresponding test\n test_duplication - Multiple tests verifying the same behavior redundantly\n\n Architecture:\n architecture - Layer violations, wrong dependency direction\n circular_dependency - Modules that import each other\n package_cohesion - Package/directory contains unrelated concerns\n api_design - Exported API is confusing, inconsistent, or poorly structured\n missing_abstraction - Direct usage where an interface/abstraction would improve design\n\n Language Idioms:\n language_idiom - Language-specific anti-patterns\n naming_convention - Names don't follow language conventions (Go: MixedCaps, Python: snake_case)\n dead_code - Unreferenced exports, unused functions\n magic_number - Numeric literals without named constants\n\n Performance & Security:\n performance - Obvious performance anti-patterns (N+1 queries, unbounded allocations)\n resource_leak - Opened resources (files, connections, readers) not properly closed\n hardcoded_config - Configuration values hardcoded instead of externalized\n\n Code Smells:\n code_smell - General smell with refactoring suggestion\n complex_conditional - Deeply nested or overly complex conditional logic\n long_file - File exceeds reasonable length for its responsibility";
10
+ /**
11
+ * Build the analysis prompt for a batch of file facts.
12
+ */
13
+ export declare function buildAnalysisPrompt(factsStr: string, checks?: Record<string, boolean>): string;
14
+ /**
15
+ * Build a cross-file analysis prompt that looks at patterns across the whole codebase.
16
+ */
17
+ export declare function buildCrossFilePrompt(allFacts: FileFacts[]): string;
18
+ /**
19
+ * Chunk file facts into batches that fit within token limits.
20
+ * Groups related files (same directory) together.
21
+ */
22
+ export declare function chunkFacts(facts: FileFacts[], maxCharsPerChunk?: number): FileFacts[][];