fix: improve omission detection to reduce false positives

- Implement more sophisticated pattern matching that distinguishes between actual code omissions and legitimate documentation
- Add strong omission patterns for clear indicators like "... rest of code unchanged"
- Require multiple weak keywords to trigger detection, reducing false positives
- Add comprehensive tests for various file types and documentation patterns
- Fixes issue #9785 where markdown files with code examples were incorrectly rejected
This commit is contained in:
Roo Code 2025-12-03 17:58:22 +00:00
parent 23605bed93
commit a724d513d1
2 changed files with 222 additions and 64 deletions

View file

@ -15,66 +15,183 @@ describe("detectCodeOmission", () => {
const y = 2;`
}
it("should skip comment checks for files under 100 lines", () => {
const newContent = `// Lines 1-50 remain unchanged
describe("Basic functionality", () => {
it("should skip comment checks for files under 100 lines", () => {
const newContent = `// Lines 1-50 remain unchanged
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not detect regular comments without omission keywords", () => {
const newContent = generateLongContent("// Adding new functionality")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not detect regular comments without omission keywords", () => {
const newContent = generateLongContent("// Adding new functionality")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not detect when comment is part of original content", () => {
const originalWithComment = `// Content remains unchanged
it("should not detect when comment is part of original content", () => {
const originalWithComment = `// Content remains unchanged
${originalContent}`
const newContent = generateLongContent("// Content remains unchanged")
expect(detectCodeOmission(originalWithComment, newContent)).toBe(false)
})
const newContent = generateLongContent("// Content remains unchanged")
expect(detectCodeOmission(originalWithComment, newContent)).toBe(false)
})
it("should not detect code that happens to contain omission keywords", () => {
const newContent = generateLongContent(`const remains = 'some value';
it("should not detect code that happens to contain omission keywords", () => {
const newContent = generateLongContent(`const remains = 'some value';
const unchanged = true;`)
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
})
it("should detect suspicious single-line comment for files with 100+ lines", () => {
const newContent = generateLongContent("// Previous content remains here\nconst x = 1;")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
describe("Strong omission patterns (should detect)", () => {
it("should detect ellipsis patterns", () => {
const newContent = generateLongContent("// ... rest of code unchanged")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect 'rest of code remains' pattern", () => {
const newContent = generateLongContent("// Rest of the code remains unchanged")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect 'previous code remains' pattern", () => {
const newContent = generateLongContent("// Previous code remains here")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect 'code truncated' pattern", () => {
const newContent = generateLongContent("// Code truncated for brevity")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect line range patterns", () => {
const newContent = generateLongContent("// [lines 50-100 remain unchanged]")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect Python-style omission comments", () => {
const newContent = generateLongContent("# Previous content remains here")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect multi-line comment omissions", () => {
const newContent = generateLongContent("/* Previous content remains the same */")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect JSX comment omissions", () => {
const newContent = generateLongContent("{/* Rest of the code remains the same */}")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect HTML comment omissions", () => {
const newContent = generateLongContent("<!-- Existing content unchanged -->")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect square bracket notation", () => {
const newContent = generateLongContent("[Previous content from line 1-305 remains exactly the same]")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
})
it("should detect suspicious Python-style comment for files with 100+ lines", () => {
const newContent = generateLongContent("# Previous content remains here\nconst x = 1;")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
describe("Legitimate documentation patterns (should NOT detect)", () => {
it("should not flag single keyword documentation comments", () => {
const newContent = generateLongContent("// Add to existing configuration")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag 'NEW' annotations in markdown examples", () => {
const newContent = generateLongContent("# NEW: Service configuration")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag makefile comments", () => {
const newContent = generateLongContent("# Add to orchestrator startup sequence")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag step-by-step instructions", () => {
const newContent = generateLongContent("// Step 1: Initialize the service")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag configuration comments", () => {
const newContent = generateLongContent("// Configuration for production environment")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag TODO comments", () => {
const newContent = generateLongContent("// TODO: Add error handling here")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag FIXME comments", () => {
const newContent = generateLongContent("// FIXME: This needs to be refactored")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag NOTE comments", () => {
const newContent = generateLongContent("// NOTE: This is important for performance")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag WARNING comments", () => {
const newContent = generateLongContent("// WARNING: Do not modify this section")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag single 'unchanged' word in different context", () => {
const newContent = generateLongContent("// Keep this value unchanged during migration")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag single 'remains' word in different context", () => {
const newContent = generateLongContent("// The responsibility remains with the caller")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag legitimate YAML comments", () => {
const newContent = generateLongContent("# Service configuration for Docker")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag legitimate JSON5 comments", () => {
const newContent = generateLongContent("// API endpoint configuration")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag legitimate shell script comments", () => {
const newContent = generateLongContent("# Install dependencies first")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it("should not flag legitimate SQL comments", () => {
const newContent = generateLongContent("-- Create indexes for better performance")
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
})
it("should detect suspicious multi-line comment for files with 100+ lines", () => {
const newContent = generateLongContent("/* Previous content remains the same */\nconst x = 1;")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect suspicious JSX comment for files with 100+ lines", () => {
const newContent = generateLongContent("{/* Rest of the code remains the same */}\nconst x = 1;")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect suspicious HTML comment for files with 100+ lines", () => {
const newContent = generateLongContent("<!-- Existing content unchanged -->\nconst x = 1;")
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should detect suspicious square bracket notation for files with 100+ lines", () => {
const newContent = generateLongContent(
"[Previous content from line 1-305 remains exactly the same]\nconst x = 1;",
)
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should not flag legitimate comments in files with 100+ lines when in original", () => {
const originalWithComment = `// This is a legitimate comment that remains here
describe("Edge cases", () => {
it("should not flag legitimate comments when in original", () => {
const originalWithComment = `// This is a legitimate comment that remains here
${originalContent}`
const newContent = generateLongContent("// This is a legitimate comment that remains here")
expect(detectCodeOmission(originalWithComment, newContent)).toBe(false)
const newContent = generateLongContent("// This is a legitimate comment that remains here")
expect(detectCodeOmission(originalWithComment, newContent)).toBe(false)
})
it("should handle mixed legitimate and suspicious patterns correctly", () => {
const legitimateComment = "// Add this to your configuration"
const suspiciousComment = "// ... rest of code remains unchanged"
const newContent = generateLongContent(`${legitimateComment}\n${suspiciousComment}`)
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
})
it("should handle empty files", () => {
expect(detectCodeOmission("", "")).toBe(false)
})
it("should handle files with only comments", () => {
const newContent = generateLongContent("// This is just a comment file")
expect(detectCodeOmission("", newContent)).toBe(false)
})
})
})

View file

@ -1,6 +1,7 @@
/**
* Detects potential AI-generated code omissions in the given file content.
* Looks for comments containing omission keywords that weren't in the original file.
* Uses pattern matching to identify comments that indicate truncated or omitted code,
* while avoiding false positives from legitimate documentation comments.
* @param originalFileContent The original content of the file.
* @param newFileContent The new content of the file to check.
* @returns True if a potential omission is detected, false otherwise.
@ -15,18 +16,26 @@ export function detectCodeOmission(originalFileContent: string, newFileContent:
const originalLines = originalFileContent.split("\n")
const newLines = newFileContent.split("\n")
const omissionKeywords = [
"remain",
"remains",
"unchanged",
"rest",
"previous",
"existing",
"content",
"same",
"...",
// More specific patterns that strongly indicate actual code omissions
// These patterns are much more likely to be AI-generated placeholders
const strongOmissionPatterns = [
/\.\.\.\s*(rest|remainder|remaining|more)\s+(of|code|content|unchanged)/i,
/\.\.\.\s*$/, // Just ellipsis at end of line
/^\s*\/\/\s*\.\.\./, // Comment starting with ellipsis
/^\s*#\s*\.\.\./, // Python comment starting with ellipsis
/(rest|remainder|remaining)\s+of\s+(the\s+)?(code|file|content)\s+(remains?|unchanged|here|omitted)/i,
/(previous|existing|original)\s+code\s+(remains?|unchanged|here|omitted)/i,
/code\s+(truncated|omitted|removed|skipped|abbreviated)/i,
/\[.*\s+(truncated|omitted|removed|skipped)\s*.*\]/i,
/\/\/\s*\[.*lines?\s+\d+.*\]/i, // e.g., // [lines 50-100 remain unchanged]
/#\s*\[.*lines?\s+\d+.*\]/i, // Python version
/<!--\s*\[.*lines?\s+\d+.*\].*-->/i, // HTML version
]
// Weaker patterns that need additional context to be considered omissions
const weakOmissionKeywords = ["remain", "remains", "unchanged", "rest", "previous", "existing", "content", "same"]
const commentPatterns = [
/^\s*\/\//, // Single-line comment for most languages
/^\s*#/, // Single-line comment for Python, Ruby, etc.
@ -36,13 +45,45 @@ export function detectCodeOmission(originalFileContent: string, newFileContent:
/^\s*\[/, // Square bracket notation
]
// Consider comments as suspicious if they weren't in the original file
// and contain omission keywords
// Check for strong omission patterns first
for (const line of newLines) {
// Skip if this line was in the original file
if (originalLines.includes(line)) {
continue
}
// Check for strong patterns that clearly indicate omissions
if (strongOmissionPatterns.some((pattern) => pattern.test(line))) {
return true
}
// For weak patterns, require multiple indicators
if (commentPatterns.some((pattern) => pattern.test(line))) {
const words = line.toLowerCase().split(/\s+/)
if (omissionKeywords.some((keyword) => words.includes(keyword))) {
if (!originalLines.includes(line)) {
const lowerLine = line.toLowerCase()
const words = lowerLine.split(/\s+/)
// Count how many weak keywords are present
let keywordCount = 0
for (const keyword of weakOmissionKeywords) {
if (words.includes(keyword)) {
keywordCount++
}
}
// Only flag if we have multiple weak keywords together
// This reduces false positives from single words like "// Add to config"
if (keywordCount >= 2) {
// Additional check: look for phrases that indicate actual omission
// rather than documentation
const omissionPhrases = [
/remains?\s+(unchanged|the\s+same|here)/i,
/rest\s+of\s+(the\s+)?(code|file|content)/i,
/previous\s+(code|content|implementation)/i,
/existing\s+(code|content|implementation)/i,
/content\s+(remains?|unchanged)/i,
]
if (omissionPhrases.some((phrase) => phrase.test(lowerLine))) {
return true
}
}