Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions docs/HALLUCINATION_FILTER_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,37 @@ Findings reference files not in the diff, lines outside diff hunks, or fabricate
**Cost**: $0 (pure code, no model calls)
**Blocks**: Path 3

Programmatic validation of every evidence document:
Programmatic validation of every evidence document. The filter has grown from 3 checks in the original design to **7 checks** as of 2026-04-20:

1. **File existence**: `doc.filePath` must be in `extractFileListFromDiff(diffContent)`. If not → remove.
2. **Line range**: `doc.lineRange` must overlap with at least one diff hunk for that file. If not → remove.
3. **Code quote verification**: If `doc.problem` contains inline code quotes, check if they exist in the diff. If fabricated → confidence × 0.5.
| # | Check | Effect |
|---|-------|--------|
| 1 | File existence | `doc.filePath` must be in diff file list → else hard-remove |
| 2 | Line range | `doc.lineRange` must overlap a diff hunk ±10 lines → else hard-remove |
| 3 | Code quote verification | Backtick-quoted code must appear in diff → fabricated > 50% → confidence × 0.5 |
| 4 | Self-contradiction | Claim of "added" vs actual removals (or vice versa) → confidence × 0.5 |
| 5 | Speculative language | Hedge markers ("may not exist", "potentially broken") → confidence × 0.7 |
| 6 | Evidence quality (#468) | 0–1 score from evidence list length + problem length + specificity markers → multiplier 0.7 + 0.3 × score |
| 7 | Finding-class prior (#468 follow-up) | Empirically FP-heavy categories (ReDoS, may-throw, missing-validation, missing-null-guard, zero-width) → per-class multiplier 0.5–0.85 |

Checks 1–2 are hard removes. Checks 3–7 apply multiplicative penalties that compound — a finding that trips speculation (×0.7) + evidence (×0.7) + class prior (×0.7) ends at 0.34× its raw confidence.

```typescript
function filterHallucinations(docs, diffContent): { filtered, removed }
function filterHallucinations(docs, diffContent): { filtered, removed, uncertain }
```

**Expected impact**: ~3-4 findings removed per review (files not in diff, fabricated code).
**Expected impact**: with all seven checks active, the 2026-04-20 baseline showed the `fp-moderator-regex` fixture's phantom CRITICAL findings moved from must-fix (100% confidence) to verify/ignore (43% or below) across multiple runs.

#### Finding-class priors table

`packages/core/src/pipeline/finding-class-scorer.ts` exports `FINDING_CLASS_PRIORS` — the one place to tune per-class multipliers. Ordered so specific classes match before `generic-potential`. Each entry maps multiple regex patterns to a single id + multiplier. To add a new class when an FP pattern emerges empirically, append a new entry with patterns matched against `issueTitle + problem`.

#### Witness-based corroboration echo dampener (#5)

`computeL1Confidence` applies an additional ×0.75 dampener when:
- 3+ co-located findings exist with non-empty evidence
- Majority share the same fingerprint (first 80 normalised chars of joined `evidence[]`)

This catches correlated-failure cascades where multiple reviewers latch onto the same superficial cue instead of corroborating independently.

### Layer 2: Corroboration Scoring

Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/pipeline/chunker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export const BUILT_IN_ARTIFACT_PATTERNS = [
'**/*.ico',
'**/*.bmp',
'**/*.tiff',
'**/*.avif',
'**/*.woff',
'**/*.woff2',
'**/*.ttf',
Expand All @@ -339,12 +340,27 @@ export const BUILT_IN_ARTIFACT_PATTERNS = [
'**/*.tar',
'**/*.gz',
'**/*.tgz',
'**/*.bz2',
'**/*.7z',
'**/*.rar',
'**/*.wasm',

// Test snapshots
'**/*.snap',
'**/__snapshots__/**',

// Additional ecosystem artifacts (Python / Go / Rust / Java / Ruby)
'**/*.pyc',
'**/__pycache__/**',
'**/*.whl',
'**/*.egg-info/**',
'vendor/**',
'target/debug/**',
'target/release/**',
'**/*.class',
'**/*.jar',
'**/*.war',
'.bundle/**',
];

// ============================================================================
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/pipeline/finding-class-scorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ export const FINDING_CLASS_PRIORS: FindingClassPrior[] = [
/\bunvalidated\s+(?:input|parameter|argument|user\s+input)\b/i,
],
},
{
id: 'missing-null-guard',
label: 'missing null / undefined guard',
multiplier: 0.7,
patterns: [
/\bmissing\s+null(?:[/\-\s]?(?:undefined))?\s+(?:guard|check)\b/i,
/\b(?:no|without|lacks)\s+null(?:[/\-\s]?(?:undefined))?\s+(?:guard|check)\b/i,
/\bnull\s*\/\s*undefined\s+check\b/i,
/\bundefined\s+reference\b/i,
],
},
{
id: 'generic-potential',
label: 'generic "potential" security concern',
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/tests/finding-class-scorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,37 @@ describe('matchFindingClass — positive matches', () => {
expect(match.id).toBe('zero-width');
});

it('catches "missing null guard" (PR #499 self-review FP)', () => {
const match = matchFindingClass(
doc({
issueTitle: 'Missing null guard for activeReviewers in computeL1Confidence',
problem: 'The function does not check whether activeReviewers is null before use.',
}),
)!;
expect(match.id).toBe('missing-null-guard');
expect(match.multiplier).toBe(0.7);
});

it('catches "no null check" phrasing', () => {
const match = matchFindingClass(
doc({
issueTitle: 'Null dereference risk',
problem: 'The code has no null check on the response object.',
}),
)!;
expect(match.id).toBe('missing-null-guard');
});

it('catches "null/undefined check" phrasing', () => {
const match = matchFindingClass(
doc({
issueTitle: 'Potential undefined reference in handler',
problem: 'A null/undefined check is missing before property access.',
}),
)!;
expect(match.id).toBe('missing-null-guard');
});

it('catches generic "potential security concern" phrasing (run 3 FP)', () => {
const match = matchFindingClass(
doc({
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/tests/pipeline-chunker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ describe('BUILT_IN_ARTIFACT_PATTERNS', () => {
'src/__snapshots__/foo.test.ts.snap',
'components/__snapshots__/Button.test.tsx.snap',
'src/foo.snap',
// Additional ecosystems
'assets/hero.avif',
'releases/archive.bz2',
'pkg/runtime.wasm',
'src/foo.pyc',
'src/__pycache__/compiled.cpython-312.pyc',
'dist/my_pkg-1.0-py3-none-any.whl',
'dist/my_pkg.egg-info/PKG-INFO',
'vendor/github.com/pkg/errors/errors.go',
'target/debug/build/foo.o',
'target/release/build/foo.o',
'classes/com/example/Foo.class',
'libs/common-1.0.jar',
'deploy/webapp-1.0.war',
'.bundle/config',
].filter((p) => !p.endsWith('.ignored-ext-so-skip'));

// Files that MUST survive the built-in filter — real source code that
Expand Down
Loading