Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ded97701b8 | ||
|
|
316cdc1a62 | ||
|
|
f19cd8fc71 | ||
|
|
181194ae3c | ||
|
|
b8f5599e61 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode",
|
||||
"version": "0.1.18",
|
||||
"version": "0.1.21",
|
||||
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -22,16 +22,39 @@ function getPlatformPackageName(): string | null {
|
||||
return platformMap[`${platform}-${arch}`] ?? null
|
||||
}
|
||||
|
||||
function isValidBinary(filePath: string): boolean {
|
||||
try {
|
||||
const stats = require("fs").statSync(filePath)
|
||||
return stats.size > 10000
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function findSgCliPathSync(): string | null {
|
||||
const binaryName = process.platform === "win32" ? "sg.exe" : "sg"
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"]
|
||||
for (const path of homebrewPaths) {
|
||||
if (existsSync(path) && isValidBinary(path)) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cachedPath = getCachedBinaryPath()
|
||||
if (cachedPath && isValidBinary(cachedPath)) {
|
||||
return cachedPath
|
||||
}
|
||||
|
||||
try {
|
||||
const require = createRequire(import.meta.url)
|
||||
const cliPkgPath = require.resolve("@ast-grep/cli/package.json")
|
||||
const cliDir = dirname(cliPkgPath)
|
||||
const sgPath = join(cliDir, binaryName)
|
||||
|
||||
if (existsSync(sgPath)) {
|
||||
if (existsSync(sgPath) && isValidBinary(sgPath)) {
|
||||
return sgPath
|
||||
}
|
||||
} catch {
|
||||
@@ -47,7 +70,7 @@ export function findSgCliPathSync(): string | null {
|
||||
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep"
|
||||
const binaryPath = join(pkgDir, astGrepName)
|
||||
|
||||
if (existsSync(binaryPath)) {
|
||||
if (existsSync(binaryPath) && isValidBinary(binaryPath)) {
|
||||
return binaryPath
|
||||
}
|
||||
} catch {
|
||||
@@ -55,20 +78,6 @@ export function findSgCliPathSync(): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"]
|
||||
for (const path of homebrewPaths) {
|
||||
if (existsSync(path)) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cachedPath = getCachedBinaryPath()
|
||||
if (cachedPath) {
|
||||
return cachedPath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -10,44 +10,27 @@ function showOutputToUser(context: unknown, output: string): void {
|
||||
ctx.metadata?.({ metadata: { output } })
|
||||
}
|
||||
|
||||
/**
|
||||
* JS/TS languages that require complete function declaration patterns
|
||||
*/
|
||||
const JS_TS_LANGUAGES = ["javascript", "typescript", "tsx"] as const
|
||||
|
||||
/**
|
||||
* Validates AST pattern for common incomplete patterns that will fail silently.
|
||||
* Only validates JS/TS languages where function declarations require body.
|
||||
*
|
||||
* @throws Error with helpful message if pattern is incomplete
|
||||
*/
|
||||
function validatePatternForCli(pattern: string, lang: CliLanguage): void {
|
||||
if (!JS_TS_LANGUAGES.includes(lang as (typeof JS_TS_LANGUAGES)[number])) {
|
||||
return
|
||||
}
|
||||
|
||||
function getEmptyResultHint(pattern: string, lang: CliLanguage): string | null {
|
||||
const src = pattern.trim()
|
||||
|
||||
// Detect incomplete function declarations:
|
||||
// - "function $NAME" (no params/body)
|
||||
// - "export function $NAME" (no params/body)
|
||||
// - "export async function $NAME" (no params/body)
|
||||
// - "export default function $NAME" (no params/body)
|
||||
// Pattern: ends with $METAVAR (uppercase, underscore, digits) without ( or {
|
||||
const incompleteFunctionDecl =
|
||||
/^(export\s+)?(default\s+)?(async\s+)?function\s+\$[A-Z_][A-Z0-9_]*\s*$/i.test(src)
|
||||
|
||||
if (incompleteFunctionDecl) {
|
||||
throw new Error(
|
||||
`Incomplete AST pattern for ${lang}: "${pattern}"\n\n` +
|
||||
`ast-grep requires complete AST nodes. Function declarations must include parameters and body.\n\n` +
|
||||
`Examples of correct patterns:\n` +
|
||||
` - "export async function $NAME($$$) { $$$ }" (matches export async functions)\n` +
|
||||
` - "function $NAME($$$) { $$$ }" (matches all function declarations)\n` +
|
||||
` - "async function $NAME($$$) { $$$ }" (matches async functions)\n\n` +
|
||||
`Your pattern "${pattern}" is missing the parameter list and body.`
|
||||
)
|
||||
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 const ast_grep_search = tool({
|
||||
@@ -66,8 +49,6 @@ export const ast_grep_search = tool({
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
validatePatternForCli(args.pattern, args.lang as CliLanguage)
|
||||
|
||||
const matches = await runSg({
|
||||
pattern: args.pattern,
|
||||
lang: args.lang as CliLanguage,
|
||||
@@ -75,7 +56,16 @@ export const ast_grep_search = tool({
|
||||
globs: args.globs,
|
||||
context: args.context,
|
||||
})
|
||||
const output = formatSearchResult(matches)
|
||||
|
||||
let output = formatSearchResult(matches)
|
||||
|
||||
if (matches.length === 0) {
|
||||
const hint = getEmptyResultHint(args.pattern, args.lang as CliLanguage)
|
||||
if (hint) {
|
||||
output += `\n\n${hint}`
|
||||
}
|
||||
}
|
||||
|
||||
showOutputToUser(context, output)
|
||||
return output
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user