feat(extraction): content-based generated-file detection (CG-5, #1500)
`isGeneratedFile` was path-only, but Go's own convention is a CONTENT marker (`// Code generated by <tool>. DO NOT EDIT.`), not a filename one. A Go monorepo with generated CRUD in ordinarily-named files sitting beside hand-written use-cases was therefore invisible to every generated-file down-rank in the codebase — that is #1500. Measured on kubernetes/client-go (2,453 Go files): the canonical banner appears in 2,001 of them, the path check flags 0, the new content check flags exactly those 2,001 — no false positives, no misses. Design: decide at INDEX time (content is already in memory for parsing), persist on `files.generated`, read from the DB. Explore never reads file headers per request. - `hasGeneratedHeader(content)` recognizes the standard banners — Go's, protoc's, `@generated`, `<auto-generated>`, Thrift, OpenAPI Generator, FlatBuffers, bindgen, ANTLR. Precision-first and fenced three ways: an 8KB/60-line header window, a comment-line requirement (leader or open block comment), and markers tight enough that prose can't trip them. A generator's own source, holding the banner as a string constant in its body, is not flagged; neither is this module itself (pinned by test). - `isGeneratedFile(path)` is unchanged — cheap, sync, still the fallback. - Schema v9 adds `files.generated` + a PARTIAL index. DDL only, no backfill: the flag derives from content the migration cannot see, so rows stay 0 until a re-index and every reader unions the flag with the path check — an un-migrated index keeps pre-#1500 behavior rather than regressing. Re-index required; noted in the CHANGELOG. - `generatedPredicateFor(paths)` gives ranking a bounded probe + O(1) lookups. Bounded, not cached: no invalidation, so a ranking call can never serve a verdict the last sync already replaced. Wired into explore ranking, findSymbolMatches, findAllSymbols, search (MCP + CLI), the context formatter, and the dominant-file/route-file hygiene filters. Cost (acceptance bar was no measurable index-time regression): a single unanchored `/generat/i` test over the header rejects ~every hand-written file before any line splitting. 4.6 µs/file on client-go (worst case — 82% generated). End-to-end `codegraph init` on client-go, n=3 alternating arms: 5.73s median with detection vs 5.76s path-only baseline; the arms cross over between runs, so the difference is inside run-to-run noise. Scope note: generated status remains a stable TIEBREAK at equal score, exactly where it was. Making it a strong negative signal is CG-10, which this unblocks by making the signal correct and available. Two pre-existing tests hard-coded schema version 8; both now track CURRENT_SCHEMA_VERSION (or the migration table) so future migrations don't require editing them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b37f191f5a
commit
16e17495f4
@@ -15,7 +15,15 @@ import { isGeneratedFile } from '../extraction/generated-detection';
|
||||
* - Entry points with locations
|
||||
* - Code blocks only for key symbols
|
||||
*/
|
||||
export function formatContextAsMarkdown(context: TaskContext): string {
|
||||
export function formatContextAsMarkdown(
|
||||
context: TaskContext,
|
||||
/**
|
||||
* Generated-file test. Defaults to the filename convention alone; the
|
||||
* ContextBuilder passes a DB-backed predicate so files flagged by their
|
||||
* HEADER at index time (#1500) demote here too.
|
||||
*/
|
||||
isGenerated: (filePath: string) => boolean = isGeneratedFile
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
// Header with query
|
||||
@@ -26,8 +34,8 @@ export function formatContextAsMarkdown(context: TaskContext): string {
|
||||
// .pulsar.go, mocks, …) rank LAST — a flow query should lead with the
|
||||
// hand-written implementation, not protobuf scaffolding.
|
||||
const orderedEntries = [...context.entryPoints].sort((a, b) => {
|
||||
const aGen = isGeneratedFile(a.filePath) ? 1 : 0;
|
||||
const bGen = isGeneratedFile(b.filePath) ? 1 : 0;
|
||||
const aGen = isGenerated(a.filePath) ? 1 : 0;
|
||||
const bGen = isGenerated(b.filePath) ? 1 : 0;
|
||||
return aGen - bGen;
|
||||
});
|
||||
if (orderedEntries.length > 0) {
|
||||
@@ -49,7 +57,7 @@ export function formatContextAsMarkdown(context: TaskContext): string {
|
||||
// Related Symbols, pure noise that displaced real-flow entries).
|
||||
const otherSymbols = Array.from(context.subgraph.nodes.values())
|
||||
.filter(n => !context.entryPoints.some(e => e.id === n.id))
|
||||
.filter(n => !isGeneratedFile(n.filePath))
|
||||
.filter(n => !isGenerated(n.filePath))
|
||||
.slice(0, 10); // Limit to 10 related symbols
|
||||
|
||||
if (otherSymbols.length > 0) {
|
||||
@@ -72,8 +80,8 @@ export function formatContextAsMarkdown(context: TaskContext): string {
|
||||
// show first (consistent with Entry Points reordering above).
|
||||
if (context.codeBlocks.length > 0) {
|
||||
const orderedBlocks = [...context.codeBlocks].sort((a, b) => {
|
||||
const aGen = isGeneratedFile(a.filePath) ? 1 : 0;
|
||||
const bGen = isGeneratedFile(b.filePath) ? 1 : 0;
|
||||
const aGen = isGenerated(a.filePath) ? 1 : 0;
|
||||
const bGen = isGenerated(b.filePath) ? 1 : 0;
|
||||
return aGen - bGen;
|
||||
});
|
||||
lines.push('### Code\n');
|
||||
|
||||
@@ -265,7 +265,14 @@ export class ContextBuilder {
|
||||
|
||||
// Return formatted output or raw context
|
||||
if (opts.format === 'markdown') {
|
||||
return formatContextAsMarkdown(context)
|
||||
// Bounded candidate set (entry points + subgraph + code blocks), so the
|
||||
// DB-backed generated check is one probe, not a per-comparison query.
|
||||
const isGenerated = this.queries.generatedPredicateFor([
|
||||
...entryPoints.map((n) => n.filePath),
|
||||
...Array.from(subgraph.nodes.values(), (n) => n.filePath),
|
||||
...codeBlocks.map((b) => b.filePath),
|
||||
]);
|
||||
return formatContextAsMarkdown(context, isGenerated)
|
||||
+ this.buildCallPathsSection(subgraph)
|
||||
+ (subgraph.confidence === 'low' ? this.buildLowConfidenceNote(entryPoints) : '');
|
||||
} else if (opts.format === 'json') {
|
||||
|
||||
Reference in New Issue
Block a user