2026-02-07 19:04:31 +09:00
|
|
|
import type { PluginInput } from "@opencode-ai/plugin"
|
2025-12-27 17:56:40 +08:00
|
|
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
2025-12-09 11:49:00 +09:00
|
|
|
import { CLI_LANGUAGES } from "./constants"
|
2025-12-04 22:40:25 +09:00
|
|
|
import { runSg } from "./cli"
|
2026-02-08 13:57:26 +09:00
|
|
|
import { formatSearchResult, formatReplaceResult } from "./result-formatter"
|
2026-04-22 12:23:51 +09:00
|
|
|
import { getPatternHint } from "./pattern-hints"
|
2026-04-22 12:26:24 +09:00
|
|
|
import {
|
|
|
|
|
AST_GREP_REPLACE_DESCRIPTION,
|
|
|
|
|
AST_GREP_SEARCH_DESCRIPTION,
|
|
|
|
|
AST_GREP_SEARCH_PATTERN_PARAM,
|
|
|
|
|
} from "./tool-descriptions"
|
2025-12-09 11:49:00 +09:00
|
|
|
import type { CliLanguage } from "./types"
|
2025-12-04 22:40:25 +09:00
|
|
|
|
2026-02-06 16:01:54 +09:00
|
|
|
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 } })
|
2025-12-04 22:40:25 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 19:04:31 +09:00
|
|
|
export function createAstGrepTools(ctx: PluginInput): Record<string, ToolDefinition> {
|
|
|
|
|
const ast_grep_search: ToolDefinition = tool({
|
2026-04-22 12:26:24 +09:00
|
|
|
description: AST_GREP_SEARCH_DESCRIPTION,
|
2026-02-07 19:04:31 +09:00
|
|
|
args: {
|
2026-04-22 12:26:24 +09:00
|
|
|
pattern: tool.schema.string().describe(AST_GREP_SEARCH_PATTERN_PARAM),
|
2026-02-07 19:04:31 +09:00
|
|
|
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,
|
|
|
|
|
})
|
2025-12-05 20:59:05 +09:00
|
|
|
|
2026-02-07 19:04:31 +09:00
|
|
|
let output = formatSearchResult(result)
|
2025-12-05 20:59:05 +09:00
|
|
|
|
2026-02-07 19:04:31 +09:00
|
|
|
if (result.matches.length === 0 && !result.error) {
|
2026-04-22 12:23:51 +09:00
|
|
|
const hint = getPatternHint(args.pattern, args.lang as CliLanguage)
|
2026-02-07 19:04:31 +09:00
|
|
|
if (hint) {
|
|
|
|
|
output += `\n\n${hint}`
|
|
|
|
|
}
|
2025-12-05 20:59:05 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 19:04:31 +09:00
|
|
|
await showOutputToUser(context, output)
|
|
|
|
|
return output
|
|
|
|
|
} catch (e) {
|
|
|
|
|
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
|
|
|
|
await showOutputToUser(context, output)
|
|
|
|
|
return output
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
2025-12-04 22:40:25 +09:00
|
|
|
|
2026-02-07 19:04:31 +09:00
|
|
|
const ast_grep_replace: ToolDefinition = tool({
|
2026-04-22 12:26:24 +09:00
|
|
|
description: AST_GREP_REPLACE_DESCRIPTION,
|
2026-02-07 19:04:31 +09:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
2025-12-04 22:40:25 +09:00
|
|
|
|
2026-02-07 19:04:31 +09:00
|
|
|
return { ast_grep_search, ast_grep_replace }
|
|
|
|
|
}
|