Files
oh-my-opencode/src/tools/ast-grep/tools.ts
T
YeonGyu-Kim 2f4b1c3158 feat(ast-grep): rewrite tool descriptions to prevent regex-style misuse
The previous description (41 words) told the LLM to write 'complete AST
nodes' but did not explain that regex syntax is the #1 failure mode. It
also shipped a bug: the Python example 'def $FUNC($$$):' had a trailing
colon that the hint system actively flags as wrong.

Extract descriptions into tool-descriptions.ts and rewrite:
- Open with 'This is NOT regex' so the constraint is unmissable
- List the four regex patterns that do not work (|, .*, \\w, [a-z])
  with the corrective action for each
- Tell the LLM to switch to grep when the pattern is text-shaped
- Fix the Python example (no trailing colon) and add Go and Rust rows
  since the failing reports came from Go codebases
- Shorten the pattern-param description with the same anti-regex list

Also harden the LSP reference for the new test files using the
bun-types triple-slash directive already used elsewhere.
2026-04-22 12:26:24 +09:00

93 lines
3.5 KiB
TypeScript

import type { PluginInput } from "@opencode-ai/plugin"
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 {
AST_GREP_REPLACE_DESCRIPTION,
AST_GREP_SEARCH_DESCRIPTION,
AST_GREP_SEARCH_PATTERN_PARAM,
} from "./tool-descriptions"
import type { CliLanguage } from "./types"
async function showOutputToUser(context: unknown, output: string): Promise<void> {
const ctx = context as {
metadata?: (input: { metadata: { output: string } }) => void | Promise<void>
}
await ctx.metadata?.({ metadata: { output } })
}
export function createAstGrepTools(ctx: PluginInput): Record<string, ToolDefinition> {
const ast_grep_search: ToolDefinition = tool({
description: AST_GREP_SEARCH_DESCRIPTION,
args: {
pattern: tool.schema.string().describe(AST_GREP_SEARCH_PATTERN_PARAM),
lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search (default: ['.'])"),
globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
context: tool.schema.number().optional().describe("Context lines around match"),
},
execute: async (args, context) => {
try {
const result = await runSg({
pattern: args.pattern,
lang: args.lang as CliLanguage,
paths: args.paths ?? [ctx.directory],
globs: args.globs,
context: args.context,
})
let output = formatSearchResult(result)
if (result.matches.length === 0 && !result.error) {
const hint = getPatternHint(args.pattern, args.lang as CliLanguage)
if (hint) {
output += `\n\n${hint}`
}
}
await showOutputToUser(context, output)
return output
} catch (e) {
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
await showOutputToUser(context, output)
return output
}
},
})
const ast_grep_replace: ToolDefinition = tool({
description: AST_GREP_REPLACE_DESCRIPTION,
args: {
pattern: tool.schema.string().describe("AST pattern to match"),
rewrite: tool.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search"),
globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs"),
dryRun: tool.schema.boolean().optional().describe("Preview changes without applying (default: true)"),
},
execute: async (args, context) => {
try {
const result = await runSg({
pattern: args.pattern,
rewrite: args.rewrite,
lang: args.lang as CliLanguage,
paths: args.paths ?? [ctx.directory],
globs: args.globs,
updateAll: args.dryRun === false,
})
const output = formatReplaceResult(result, args.dryRun !== false)
await showOutputToUser(context, output)
return output
} catch (e) {
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
await showOutputToUser(context, output)
return output
}
},
})
return { ast_grep_search, ast_grep_replace }
}