From f4cff660aadfb8647ffe8e06ebf3d922dc13b7b1 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Apr 2026 06:05:50 -0500 Subject: [PATCH] ADR-021 Phase D fix: strip flag names + Rust keywords from pattern_keys Iter 9 revealed two quality bugs in the extractor: 1. Kimi wraps the Flag column in backticks (\`DeadCode\`), so the flag name itself was captured as a code token. Result: pattern_keys like "DeadCode:DeadCode" that match nothing and add noise to the index. Fix: filter FLAG_VARIANTS out of token candidates. 2. Complex backtick content like \`Foo::bar(&self) -> u64\` was rejected wholesale by the identifier regex. Fallback now scans for identifier substrings and ranks by ::-qualified paths first, then length. Bonus: filter Rust keywords (self, mut, async, etc) since they're grammar, not bug-shape signal. Dry-run on iter 9 delta.rs output produces semantically meaningful keys: DeadCode:DeltaStats::tombstones_applied NullableConfusion:DeltaError-DeltaStats-apply_delta BoundaryViolation:apply_delta-journald::emit-rows_dropped_by_tombstones PseudoImpl:apply_delta-delta_ops-validate_schema These are stable under reviewer prose variation (canonical sort + top-3 slice) and precise enough to separate different bugs within the same Flag category. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/real-world/scrum_master_pipeline.ts | 53 ++++++++++++++++++----- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/tests/real-world/scrum_master_pipeline.ts b/tests/real-world/scrum_master_pipeline.ts index f5cfff4..d61f9b2 100644 --- a/tests/real-world/scrum_master_pipeline.ts +++ b/tests/real-world/scrum_master_pipeline.ts @@ -795,26 +795,57 @@ Respond with markdown. Be specific, not generic. Cite file-region + PRD-chunk-of } if (!variantOnLine) continue; - // Extract identifier-shaped backtick-quoted tokens. These are - // the names the finding is about — field names, type names, - // function names, path expressions (A::B). + // Extract identifier-shaped tokens from backticks. We try two + // levels: (a) whole-backtick match if it's a clean identifier + // or path, (b) for complex content like function signatures + // (`Foo::bar(&self) -> u64`) pull out the longest identifier + // substrings so we still capture the callable. const codeTokens: string[] = []; + const idRe = /[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)*/g; for (const m of line.matchAll(/`([^`]+)`/g)) { const raw = m[1].trim(); - // Filter to things that look like identifiers or paths. Skip - // punctuation, spaces, SQL keywords, and things that look - // like prose quotes. - if (!/^[A-Za-z_][A-Za-z0-9_:]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(raw)) continue; - if (raw.length < 3) continue; - codeTokens.push(raw); + // Whole-backtick identifier or dotted path? (`row_count`, + // `AccessControl::can_access`, `foo.bar`). + if (/^[A-Za-z_][A-Za-z0-9_:]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(raw)) { + if (raw.length >= 3) codeTokens.push(raw); + continue; + } + // Fallback: scan for identifier substrings, take the longest + // meaningful ones (usually the function or type name comes + // first in a signature like `Foo::bar(&self)`). + const ids = [...raw.matchAll(idRe)] + .map(x => x[0]) + .filter(id => id.length >= 3); + // Prefer ::-qualified paths first (they're more specific), + // then the top-2 longest; keeps the key stable under + // signature variation. + const ranked = ids + .map(id => ({ id, score: (id.includes("::") ? 1000 : 0) + id.length })) + .sort((a, b) => b.score - a.score) + .slice(0, 2) + .map(x => x.id); + codeTokens.push(...ranked); } - if (codeTokens.length === 0) continue; + // Remove the flag variant name itself if it got captured (kimi + // and other reviewers often wrap the flag column in backticks). + // Also drop Rust + common keywords that slip through the + // identifier regex — "self", "mut", "async", "await", "pub" + // aren't bug-shape signal, they're grammar. + const FLAG_SET = new Set(FLAG_VARIANTS); + const KEYWORDS = new Set([ + "self", "Self", "mut", "async", "await", "pub", "fn", "let", + "const", "static", "impl", "trait", "struct", "enum", "use", + "mod", "crate", "super", "match", "return", "Some", "None", + "Ok", "Err", "true", "false", + ]); + const filtered = codeTokens.filter(t => !FLAG_SET.has(t) && !KEYWORDS.has(t)); + if (filtered.length === 0) continue; // Canonicalize: dedupe, sort alphabetically, take top 3. // Alphabetical sort gives stability across "A then B" / "B then A" // variants. Top 3 keeps the key short while retaining enough // signal for different bugs to separate. - const uniqTokens = [...new Set(codeTokens)].sort().slice(0, 3); + const uniqTokens = [...new Set(filtered)].sort().slice(0, 3); const pattern_key = `${variantOnLine}:${uniqTokens.join("-")}`; if (seenKeys.has(pattern_key)) continue; seenKeys.add(pattern_key);