feat(ast-grep): detect regex-style pattern misuse before returning empty results
LLMs frequently call ast_grep_search with regex-style patterns like 'func.*build|BuildMode|projectReferences' instead of AST patterns. The search silently returns zero matches with no useful feedback, so the model retries with a different regex-shaped pattern and loops. Extract hint generation into pattern-hints.ts and add detectors for the four dominant misuse modes: - regex escapes (\\w, \\d, \\s, \\b) - character-class ranges ([a-z], [0-9]) - regex wildcards (.* .+) with no meta-vars - pure alternation (foo|bar|baz with no structural syntax) Heuristics are designed to be safe on valid AST patterns: bitwise OR '$A | $B' and Rust closures '|x| x + 1' are not flagged. Language- specific shape hints (trailing-colon Python, body-less JS/TS/Go/Rust functions) are preserved and extended to Go and Rust.
This commit is contained in:
@@ -3,6 +3,7 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import { CLI_LANGUAGES } from "./constants"
|
||||
import { runSg } from "./cli"
|
||||
import { formatSearchResult, formatReplaceResult } from "./result-formatter"
|
||||
import { getPatternHint } from "./pattern-hints"
|
||||
import type { CliLanguage } from "./types"
|
||||
|
||||
async function showOutputToUser(context: unknown, output: string): Promise<void> {
|
||||
@@ -12,29 +13,6 @@ async function showOutputToUser(context: unknown, output: string): Promise<void>
|
||||
await ctx.metadata?.({ metadata: { output } })
|
||||
}
|
||||
|
||||
function getEmptyResultHint(pattern: string, lang: CliLanguage): string | null {
|
||||
const src = pattern.trim()
|
||||
|
||||
if (lang === "python") {
|
||||
if (src.startsWith("class ") && src.endsWith(":")) {
|
||||
const withoutColon = src.slice(0, -1)
|
||||
return `Hint: Remove trailing colon. Try: "${withoutColon}"`
|
||||
}
|
||||
if ((src.startsWith("def ") || src.startsWith("async def ")) && src.endsWith(":")) {
|
||||
const withoutColon = src.slice(0, -1)
|
||||
return `Hint: Remove trailing colon. Try: "${withoutColon}"`
|
||||
}
|
||||
}
|
||||
|
||||
if (["javascript", "typescript", "tsx"].includes(lang)) {
|
||||
if (/^(export\s+)?(async\s+)?function\s+\$[A-Z_]+\s*$/i.test(src)) {
|
||||
return `Hint: Function patterns need params and body. Try "function $NAME($$$) { $$$ }"`
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function createAstGrepTools(ctx: PluginInput): Record<string, ToolDefinition> {
|
||||
const ast_grep_search: ToolDefinition = tool({
|
||||
description:
|
||||
@@ -63,7 +41,7 @@ export function createAstGrepTools(ctx: PluginInput): Record<string, ToolDefinit
|
||||
let output = formatSearchResult(result)
|
||||
|
||||
if (result.matches.length === 0 && !result.error) {
|
||||
const hint = getEmptyResultHint(args.pattern, args.lang as CliLanguage)
|
||||
const hint = getPatternHint(args.pattern, args.lang as CliLanguage)
|
||||
if (hint) {
|
||||
output += `\n\n${hint}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user