@kolisachint/hoocode-agent 0.4.138 → 0.4.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/extensions/plugins/formats/copilot.d.ts +7 -2
- package/dist/core/extensions/plugins/formats/copilot.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/copilot.js +27 -7
- package/dist/core/extensions/plugins/formats/copilot.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +5 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +3 -1
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +16 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +23 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +128 -85
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/native-search.d.ts +90 -0
- package/dist/core/tools/native-search.d.ts.map +1 -0
- package/dist/core/tools/native-search.js +255 -0
- package/dist/core/tools/native-search.js.map +1 -0
- package/dist/extensions/core/config.d.ts +13 -0
- package/dist/extensions/core/config.d.ts.map +1 -1
- package/dist/extensions/core/config.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +13 -4
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { compressGrepOutput } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Format a context block around a match line. Path is omitted (emitted once per file).\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [` ${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst trimmedStderr = stderr.trim();\n\t\t\t\t\t\t\t\tlet errorMsg = trimmedStderr || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\t// A regex parse error means the pattern is not a valid regex. Point the\n\t\t\t\t\t\t\t\t// caller at the `literal` option so they can search the text verbatim.\n\t\t\t\t\t\t\t\tif (!literal && /regex parse error/i.test(trimmedStderr)) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Group matches by file path, showing filename once per file.\n\t\t\t\t\t\t\tconst fileGroups = new Map<string, typeof matches>();\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tlet group = fileGroups.get(match.filePath);\n\t\t\t\t\t\t\t\tif (!group) {\n\t\t\t\t\t\t\t\t\tgroup = [];\n\t\t\t\t\t\t\t\t\tfileGroups.set(match.filePath, group);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgroup.push(match);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (const [filePath, fileMatches] of fileGroups) {\n\t\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\t\toutputLines.push(relativePath);\n\t\t\t\t\t\t\t\tfor (const match of fileMatches) {\n\t\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\t\toutputLines.push(`${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { compressGrepOutput } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { isNativeSearchForced, nativeGrep } from \"./native-search.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// rg drives the fast path; when it is unavailable (restricted\n\t\t\t\t\t\t// environments) or forced off, a pure-JS scan produces the same\n\t\t\t\t\t\t// match shape so the tool degrades automatically instead of failing.\n\t\t\t\t\t\tconst rgPath = isNativeSearchForced() ? undefined : await ensureTool(\"rg\", true);\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Match collection is shared between the rg and native paths.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\n\t\t\t\t\t\t// Format a context block around a match line. Path is omitted (emitted once per file).\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [` ${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Format the collected matches into the tool result. Shared by both paths.\n\t\t\t\t\t\tconst finalize = async () => {\n\t\t\t\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst outputLines: string[] = [];\n\t\t\t\t\t\t\t// Group matches by file path, showing filename once per file.\n\t\t\t\t\t\t\tconst fileGroups = new Map<string, typeof matches>();\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tlet group = fileGroups.get(match.filePath);\n\t\t\t\t\t\t\t\tif (!group) {\n\t\t\t\t\t\t\t\t\tgroup = [];\n\t\t\t\t\t\t\t\t\tfileGroups.set(match.filePath, group);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgroup.push(match);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (const [filePath, fileMatches] of fileGroups) {\n\t\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\t\toutputLines.push(relativePath);\n\t\t\t\t\t\t\t\tfor (const match of fileMatches) {\n\t\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\t\toutputLines.push(`${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// --- Native fallback: no rg binary available (or forced off) ---\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tlet result: Awaited<ReturnType<typeof nativeGrep>>;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tresult = await nativeGrep(searchPath, {\n\t\t\t\t\t\t\t\t\tpattern,\n\t\t\t\t\t\t\t\t\tisDirectory,\n\t\t\t\t\t\t\t\t\tignoreCase,\n\t\t\t\t\t\t\t\t\tliteral,\n\t\t\t\t\t\t\t\t\tglob,\n\t\t\t\t\t\t\t\t\tlimit: effectiveLimit,\n\t\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t\t\treadFile: ops.readFile,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst err = e as Error & { invalidRegex?: boolean };\n\t\t\t\t\t\t\t\tlet errorMsg = err.message || \"grep failed\";\n\t\t\t\t\t\t\t\tif (err.invalidRegex && !literal) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmatches.push(...result.matches);\n\t\t\t\t\t\t\tmatchLimitReached = result.matchLimitReached;\n\t\t\t\t\t\t\tawait finalize();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// --- Fast path: stream rg --json ---\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst trimmedStderr = stderr.trim();\n\t\t\t\t\t\t\t\tlet errorMsg = trimmedStderr || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\t// A regex parse error means the pattern is not a valid regex. Point the\n\t\t\t\t\t\t\t\t// caller at the `literal` option so they can search the text verbatim.\n\t\t\t\t\t\t\t\tif (!literal && /regex parse error/i.test(trimmedStderr)) {\n\t\t\t\t\t\t\t\t\terrorMsg += `\\n\\nThe pattern is not a valid regex. To search for it as plain text, pass literal: true (or escape the regex metacharacters).`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tawait finalize();\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native (pure-JS) fallbacks for the `find` and `grep` tools.
|
|
3
|
+
*
|
|
4
|
+
* The tools normally shell out to `fd` / `rg`, which are downloaded on demand
|
|
5
|
+
* (see tools-manager.ts). In restricted environments those binaries may be
|
|
6
|
+
* neither on PATH nor downloadable, and `HOOCODE_NATIVE_SEARCH=1` can also force
|
|
7
|
+
* this path. Rather than failing the tool and asking the model to fall back to
|
|
8
|
+
* `bash`, these functions reproduce the essential behaviour in JS so search
|
|
9
|
+
* degrades automatically:
|
|
10
|
+
*
|
|
11
|
+
* - hierarchical `.gitignore` handling (each `.gitignore` is scoped to its own
|
|
12
|
+
* subtree, matching fd's `--no-require-git` behaviour and issue #3303),
|
|
13
|
+
* - hidden files included (like `fd --hidden` / `rg --hidden`),
|
|
14
|
+
* - `.git` always skipped; `node_modules` skipped for `find` (mirrors the
|
|
15
|
+
* tool's built-in excludes) but left to `.gitignore` for `grep` (like rg).
|
|
16
|
+
*
|
|
17
|
+
* These are best-effort approximations, not byte-for-byte fd/rg parity: globs
|
|
18
|
+
* are matched with `minimatch` and patterns with JS `RegExp`, and only
|
|
19
|
+
* `.gitignore` files are honoured (not `.ignore` or global excludes).
|
|
20
|
+
*/
|
|
21
|
+
export type EntryType = "f" | "d" | "l";
|
|
22
|
+
export interface CollectedEntry {
|
|
23
|
+
/** Absolute path. */
|
|
24
|
+
abs: string;
|
|
25
|
+
/** POSIX path relative to the walk root. */
|
|
26
|
+
rel: string;
|
|
27
|
+
type: EntryType;
|
|
28
|
+
}
|
|
29
|
+
export interface WalkOptions {
|
|
30
|
+
/** Max entry depth relative to root; direct children are depth 1. */
|
|
31
|
+
maxDepth?: number;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
/** Directory names to never descend into. Defaults to `.git`. */
|
|
34
|
+
alwaysSkipDirs?: Set<string>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Walk `root` depth-first, returning every entry not excluded by `.gitignore`
|
|
38
|
+
* or an always-skip directory. Symlinks are reported but never followed (avoids
|
|
39
|
+
* cycles). Enumeration stops at {@link MAX_ENTRIES}.
|
|
40
|
+
*/
|
|
41
|
+
export declare function collectEntries(root: string, opts?: WalkOptions): CollectedEntry[];
|
|
42
|
+
export interface NativeFindOptions {
|
|
43
|
+
patterns: string[];
|
|
44
|
+
type: EntryType;
|
|
45
|
+
/** Extra exclusion globs (already includes node_modules/.git for find). */
|
|
46
|
+
excludeGlobs: string[];
|
|
47
|
+
maxDepth?: number;
|
|
48
|
+
alwaysSkipDirs?: Set<string>;
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Native replacement for the fd-backed search. Returns POSIX paths relative to
|
|
53
|
+
* `root`, with a trailing slash on directories (like fd), unsorted/undeduped —
|
|
54
|
+
* the caller applies its own dedupe/sort/limit.
|
|
55
|
+
*/
|
|
56
|
+
export declare function nativeFind(root: string, opts: NativeFindOptions): string[];
|
|
57
|
+
export interface NativeGrepMatch {
|
|
58
|
+
filePath: string;
|
|
59
|
+
lineNumber: number;
|
|
60
|
+
lineText: string;
|
|
61
|
+
}
|
|
62
|
+
export interface NativeGrepOptions {
|
|
63
|
+
pattern: string;
|
|
64
|
+
/** True when `root` is a directory; false when it is a single file. */
|
|
65
|
+
isDirectory: boolean;
|
|
66
|
+
ignoreCase?: boolean;
|
|
67
|
+
literal?: boolean;
|
|
68
|
+
/** Optional glob filter applied to file paths (like rg --glob). */
|
|
69
|
+
glob?: string;
|
|
70
|
+
/** Stop after this many matches. */
|
|
71
|
+
limit: number;
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
readFile: (absolutePath: string) => Promise<string> | string;
|
|
74
|
+
}
|
|
75
|
+
export interface NativeGrepResult {
|
|
76
|
+
matches: NativeGrepMatch[];
|
|
77
|
+
matchLimitReached: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Native replacement for the rg-backed content search. Collects up to `limit`
|
|
81
|
+
* matches as `{ filePath, lineNumber, lineText }`, the same shape the tool's
|
|
82
|
+
* formatter already consumes from rg's JSON output.
|
|
83
|
+
*
|
|
84
|
+
* Throws an Error tagged `invalidRegex` when a non-literal pattern is not a
|
|
85
|
+
* valid JS regex, so the caller can surface the same "pass literal: true" hint.
|
|
86
|
+
*/
|
|
87
|
+
export declare function nativeGrep(root: string, opts: NativeGrepOptions): Promise<NativeGrepResult>;
|
|
88
|
+
/** Whether the native search path is forced regardless of fd/rg availability. */
|
|
89
|
+
export declare function isNativeSearchForced(): boolean;
|
|
90
|
+
//# sourceMappingURL=native-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-search.d.ts","sourceRoot":"","sources":["../../../src/core/tools/native-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAUxC,MAAM,WAAW,cAAc;IAC9B,qBAAqB;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;CAChB;AAiCD,MAAM,WAAW,WAAW;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iEAAiE;IACjE,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,cAAc,EAAE,CA8DrF;AAoBD,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,SAAS,CAAC;IAChB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAe1E;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC7D;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAeD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+DjG;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C","sourcesContent":["/**\n * Native (pure-JS) fallbacks for the `find` and `grep` tools.\n *\n * The tools normally shell out to `fd` / `rg`, which are downloaded on demand\n * (see tools-manager.ts). In restricted environments those binaries may be\n * neither on PATH nor downloadable, and `HOOCODE_NATIVE_SEARCH=1` can also force\n * this path. Rather than failing the tool and asking the model to fall back to\n * `bash`, these functions reproduce the essential behaviour in JS so search\n * degrades automatically:\n *\n * - hierarchical `.gitignore` handling (each `.gitignore` is scoped to its own\n * subtree, matching fd's `--no-require-git` behaviour and issue #3303),\n * - hidden files included (like `fd --hidden` / `rg --hidden`),\n * - `.git` always skipped; `node_modules` skipped for `find` (mirrors the\n * tool's built-in excludes) but left to `.gitignore` for `grep` (like rg).\n *\n * These are best-effort approximations, not byte-for-byte fd/rg parity: globs\n * are matched with `minimatch` and patterns with JS `RegExp`, and only\n * `.gitignore` files are honoured (not `.ignore` or global excludes).\n */\n\nimport { readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore, { type Ignore } from \"ignore\";\nimport { minimatch } from \"minimatch\";\nimport path from \"path\";\nimport { toPosixPath } from \"./fd-utils.js\";\n\nexport type EntryType = \"f\" | \"d\" | \"l\";\n\n/** Hard cap on entries enumerated during a single walk, so a pathological tree\n * can never hang the fallback. Well above any tool's own result limit. */\nconst MAX_ENTRIES = 200_000;\n\n/** Files larger than this are skipped by the grep fallback (rg streams; we read\n * whole files, so we guard against loading huge blobs into memory). */\nconst MAX_GREP_FILE_BYTES = 20 * 1024 * 1024;\n\nexport interface CollectedEntry {\n\t/** Absolute path. */\n\tabs: string;\n\t/** POSIX path relative to the walk root. */\n\trel: string;\n\ttype: EntryType;\n}\n\n/** A `.gitignore` matcher scoped to the subtree rooted at `baseDir`. */\ninterface GitignoreMatcher {\n\tbaseDir: string;\n\tig: Ignore;\n}\n\nfunction loadGitignore(dir: string): Ignore | undefined {\n\tlet content: string;\n\ttry {\n\t\tcontent = readFileSync(path.join(dir, \".gitignore\"), \"utf-8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\treturn ignore().add(content);\n}\n\n/**\n * Whether `absPath` is ignored by any applicable `.gitignore`. Each matcher only\n * applies to paths inside its `baseDir`, and the path is tested relative to that\n * base — so `a/.gitignore` scopes to `a/` and its descendants but never `b/`.\n */\nfunction isGitIgnored(absPath: string, isDir: boolean, matchers: GitignoreMatcher[]): boolean {\n\tfor (const m of matchers) {\n\t\tconst rel = path.relative(m.baseDir, absPath);\n\t\tif (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) continue;\n\t\tconst relPosix = toPosixPath(rel) + (isDir ? \"/\" : \"\");\n\t\tif (m.ig.ignores(relPosix)) return true;\n\t}\n\treturn false;\n}\n\nexport interface WalkOptions {\n\t/** Max entry depth relative to root; direct children are depth 1. */\n\tmaxDepth?: number;\n\tsignal?: AbortSignal;\n\t/** Directory names to never descend into. Defaults to `.git`. */\n\talwaysSkipDirs?: Set<string>;\n}\n\n/**\n * Walk `root` depth-first, returning every entry not excluded by `.gitignore`\n * or an always-skip directory. Symlinks are reported but never followed (avoids\n * cycles). Enumeration stops at {@link MAX_ENTRIES}.\n */\nexport function collectEntries(root: string, opts: WalkOptions = {}): CollectedEntry[] {\n\tconst out: CollectedEntry[] = [];\n\tconst alwaysSkip = opts.alwaysSkipDirs ?? new Set([\".git\"]);\n\n\tconst rootMatchers: GitignoreMatcher[] = [];\n\tconst rootIg = loadGitignore(root);\n\tif (rootIg) rootMatchers.push({ baseDir: root, ig: rootIg });\n\n\t// `depth` is the depth of `dir`; its direct children are at depth + 1.\n\tconst stack: Array<{ dir: string; depth: number; matchers: GitignoreMatcher[] }> = [\n\t\t{ dir: root, depth: 0, matchers: rootMatchers },\n\t];\n\n\twhile (stack.length > 0) {\n\t\tif (out.length >= MAX_ENTRIES || opts.signal?.aborted) break;\n\t\tconst { dir, depth, matchers } = stack.pop()!;\n\n\t\tlet dirents: import(\"fs\").Dirent[];\n\t\ttry {\n\t\t\tdirents = readdirSync(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\tcontinue; // unreadable directory — skip rather than abort the whole walk\n\t\t}\n\n\t\tconst entryDepth = depth + 1;\n\t\tfor (const dirent of dirents) {\n\t\t\tif (out.length >= MAX_ENTRIES) break;\n\n\t\t\tconst name = dirent.name;\n\t\t\tconst abs = path.join(dir, name);\n\n\t\t\tlet type: EntryType;\n\t\t\tlet isDir = false;\n\t\t\tif (dirent.isSymbolicLink()) {\n\t\t\t\ttype = \"l\";\n\t\t\t} else if (dirent.isDirectory()) {\n\t\t\t\ttype = \"d\";\n\t\t\t\tisDir = true;\n\t\t\t} else if (dirent.isFile()) {\n\t\t\t\ttype = \"f\";\n\t\t\t} else {\n\t\t\t\tcontinue; // sockets, fifos, block devices, …\n\t\t\t}\n\n\t\t\tif (isDir && alwaysSkip.has(name)) continue;\n\t\t\tif (isGitIgnored(abs, isDir, matchers)) continue;\n\n\t\t\tif (opts.maxDepth === undefined || entryDepth <= opts.maxDepth) {\n\t\t\t\tout.push({ abs, rel: toPosixPath(path.relative(root, abs)), type });\n\t\t\t}\n\n\t\t\t// Descend only into real directories, and only if their children can\n\t\t\t// still be within the depth budget.\n\t\t\tif (isDir && (opts.maxDepth === undefined || entryDepth < opts.maxDepth)) {\n\t\t\t\tconst childIg = loadGitignore(abs);\n\t\t\t\tconst nextMatchers = childIg ? [...matchers, { baseDir: abs, ig: childIg }] : matchers;\n\t\t\t\tstack.push({ dir: abs, depth: entryDepth, matchers: nextMatchers });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out;\n}\n\n/**\n * Match a `find` glob against a POSIX relative path, mirroring fd's semantics:\n * a slashless pattern matches the basename at any depth; a pattern containing a\n * slash matches the full path and is anchored anywhere in the tree.\n */\nfunction matchesFindPattern(relPosix: string, pattern: string): boolean {\n\tif (pattern.includes(\"/\")) {\n\t\tlet p = pattern;\n\t\tif (p.startsWith(\"/\")) {\n\t\t\tp = p.slice(1); // leading slash anchors to root; rel paths have no leading slash\n\t\t} else if (!p.startsWith(\"**/\") && p !== \"**\") {\n\t\t\tp = `**/${p}`;\n\t\t}\n\t\treturn minimatch(relPosix, p, { dot: true });\n\t}\n\treturn minimatch(relPosix, pattern, { dot: true, matchBase: true });\n}\n\nexport interface NativeFindOptions {\n\tpatterns: string[];\n\ttype: EntryType;\n\t/** Extra exclusion globs (already includes node_modules/.git for find). */\n\texcludeGlobs: string[];\n\tmaxDepth?: number;\n\talwaysSkipDirs?: Set<string>;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Native replacement for the fd-backed search. Returns POSIX paths relative to\n * `root`, with a trailing slash on directories (like fd), unsorted/undeduped —\n * the caller applies its own dedupe/sort/limit.\n */\nexport function nativeFind(root: string, opts: NativeFindOptions): string[] {\n\tconst entries = collectEntries(root, {\n\t\tmaxDepth: opts.maxDepth,\n\t\tsignal: opts.signal,\n\t\talwaysSkipDirs: opts.alwaysSkipDirs,\n\t});\n\n\tconst results: string[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== opts.type) continue;\n\t\tif (opts.excludeGlobs.some((g) => minimatch(entry.rel, g, { dot: true }))) continue;\n\t\tif (!opts.patterns.some((pat) => matchesFindPattern(entry.rel, pat))) continue;\n\t\tresults.push(entry.type === \"d\" ? `${entry.rel}/` : entry.rel);\n\t}\n\treturn results;\n}\n\nexport interface NativeGrepMatch {\n\tfilePath: string;\n\tlineNumber: number;\n\tlineText: string;\n}\n\nexport interface NativeGrepOptions {\n\tpattern: string;\n\t/** True when `root` is a directory; false when it is a single file. */\n\tisDirectory: boolean;\n\tignoreCase?: boolean;\n\tliteral?: boolean;\n\t/** Optional glob filter applied to file paths (like rg --glob). */\n\tglob?: string;\n\t/** Stop after this many matches. */\n\tlimit: number;\n\tsignal?: AbortSignal;\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nexport interface NativeGrepResult {\n\tmatches: NativeGrepMatch[];\n\tmatchLimitReached: boolean;\n}\n\nfunction escapeRegExp(value: string): string {\n\treturn value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** Cheap binary-file heuristic: a NUL byte in the first chunk. rg skips these. */\nfunction looksBinary(content: string): boolean {\n\tconst sampleLength = Math.min(content.length, 8192);\n\tfor (let i = 0; i < sampleLength; i++) {\n\t\tif (content.charCodeAt(i) === 0) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Native replacement for the rg-backed content search. Collects up to `limit`\n * matches as `{ filePath, lineNumber, lineText }`, the same shape the tool's\n * formatter already consumes from rg's JSON output.\n *\n * Throws an Error tagged `invalidRegex` when a non-literal pattern is not a\n * valid JS regex, so the caller can surface the same \"pass literal: true\" hint.\n */\nexport async function nativeGrep(root: string, opts: NativeGrepOptions): Promise<NativeGrepResult> {\n\tlet regex: RegExp;\n\tconst flags = opts.ignoreCase ? \"i\" : \"\";\n\tif (opts.literal) {\n\t\tregex = new RegExp(escapeRegExp(opts.pattern), flags);\n\t} else {\n\t\ttry {\n\t\t\tregex = new RegExp(opts.pattern, flags);\n\t\t} catch (e) {\n\t\t\tconst error = new Error(e instanceof Error ? e.message : String(e)) as Error & { invalidRegex?: boolean };\n\t\t\terror.invalidRegex = true;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tlet files: string[];\n\tif (!opts.isDirectory) {\n\t\tfiles = [root];\n\t} else {\n\t\tconst entries = collectEntries(root, { signal: opts.signal });\n\t\tfiles = entries\n\t\t\t.filter((e) => e.type === \"f\")\n\t\t\t.filter((e) => {\n\t\t\t\tif (!opts.glob) return true;\n\t\t\t\treturn minimatch(e.rel, opts.glob, { dot: true, matchBase: !opts.glob.includes(\"/\") });\n\t\t\t})\n\t\t\t.map((e) => e.abs);\n\t}\n\n\tconst matches: NativeGrepMatch[] = [];\n\tlet matchLimitReached = false;\n\n\tfor (const filePath of files) {\n\t\tif (opts.signal?.aborted || matches.length >= opts.limit) break;\n\n\t\ttry {\n\t\t\tif (statSync(filePath).size > MAX_GREP_FILE_BYTES) continue;\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = await opts.readFile(filePath);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (looksBinary(content)) continue;\n\n\t\tconst lines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t// New RegExp per file with no /g flag → test() is stateless across lines.\n\t\t\tif (regex.test(lines[i])) {\n\t\t\t\tmatches.push({ filePath, lineNumber: i + 1, lineText: lines[i] });\n\t\t\t\tif (matches.length >= opts.limit) {\n\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { matches, matchLimitReached };\n}\n\n/** Whether the native search path is forced regardless of fd/rg availability. */\nexport function isNativeSearchForced(): boolean {\n\treturn process.env.HOOCODE_NATIVE_SEARCH === \"1\";\n}\n"]}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native (pure-JS) fallbacks for the `find` and `grep` tools.
|
|
3
|
+
*
|
|
4
|
+
* The tools normally shell out to `fd` / `rg`, which are downloaded on demand
|
|
5
|
+
* (see tools-manager.ts). In restricted environments those binaries may be
|
|
6
|
+
* neither on PATH nor downloadable, and `HOOCODE_NATIVE_SEARCH=1` can also force
|
|
7
|
+
* this path. Rather than failing the tool and asking the model to fall back to
|
|
8
|
+
* `bash`, these functions reproduce the essential behaviour in JS so search
|
|
9
|
+
* degrades automatically:
|
|
10
|
+
*
|
|
11
|
+
* - hierarchical `.gitignore` handling (each `.gitignore` is scoped to its own
|
|
12
|
+
* subtree, matching fd's `--no-require-git` behaviour and issue #3303),
|
|
13
|
+
* - hidden files included (like `fd --hidden` / `rg --hidden`),
|
|
14
|
+
* - `.git` always skipped; `node_modules` skipped for `find` (mirrors the
|
|
15
|
+
* tool's built-in excludes) but left to `.gitignore` for `grep` (like rg).
|
|
16
|
+
*
|
|
17
|
+
* These are best-effort approximations, not byte-for-byte fd/rg parity: globs
|
|
18
|
+
* are matched with `minimatch` and patterns with JS `RegExp`, and only
|
|
19
|
+
* `.gitignore` files are honoured (not `.ignore` or global excludes).
|
|
20
|
+
*/
|
|
21
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
22
|
+
import ignore from "ignore";
|
|
23
|
+
import { minimatch } from "minimatch";
|
|
24
|
+
import path from "path";
|
|
25
|
+
import { toPosixPath } from "./fd-utils.js";
|
|
26
|
+
/** Hard cap on entries enumerated during a single walk, so a pathological tree
|
|
27
|
+
* can never hang the fallback. Well above any tool's own result limit. */
|
|
28
|
+
const MAX_ENTRIES = 200_000;
|
|
29
|
+
/** Files larger than this are skipped by the grep fallback (rg streams; we read
|
|
30
|
+
* whole files, so we guard against loading huge blobs into memory). */
|
|
31
|
+
const MAX_GREP_FILE_BYTES = 20 * 1024 * 1024;
|
|
32
|
+
function loadGitignore(dir) {
|
|
33
|
+
let content;
|
|
34
|
+
try {
|
|
35
|
+
content = readFileSync(path.join(dir, ".gitignore"), "utf-8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return ignore().add(content);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether `absPath` is ignored by any applicable `.gitignore`. Each matcher only
|
|
44
|
+
* applies to paths inside its `baseDir`, and the path is tested relative to that
|
|
45
|
+
* base — so `a/.gitignore` scopes to `a/` and its descendants but never `b/`.
|
|
46
|
+
*/
|
|
47
|
+
function isGitIgnored(absPath, isDir, matchers) {
|
|
48
|
+
for (const m of matchers) {
|
|
49
|
+
const rel = path.relative(m.baseDir, absPath);
|
|
50
|
+
if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel))
|
|
51
|
+
continue;
|
|
52
|
+
const relPosix = toPosixPath(rel) + (isDir ? "/" : "");
|
|
53
|
+
if (m.ig.ignores(relPosix))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Walk `root` depth-first, returning every entry not excluded by `.gitignore`
|
|
60
|
+
* or an always-skip directory. Symlinks are reported but never followed (avoids
|
|
61
|
+
* cycles). Enumeration stops at {@link MAX_ENTRIES}.
|
|
62
|
+
*/
|
|
63
|
+
export function collectEntries(root, opts = {}) {
|
|
64
|
+
const out = [];
|
|
65
|
+
const alwaysSkip = opts.alwaysSkipDirs ?? new Set([".git"]);
|
|
66
|
+
const rootMatchers = [];
|
|
67
|
+
const rootIg = loadGitignore(root);
|
|
68
|
+
if (rootIg)
|
|
69
|
+
rootMatchers.push({ baseDir: root, ig: rootIg });
|
|
70
|
+
// `depth` is the depth of `dir`; its direct children are at depth + 1.
|
|
71
|
+
const stack = [
|
|
72
|
+
{ dir: root, depth: 0, matchers: rootMatchers },
|
|
73
|
+
];
|
|
74
|
+
while (stack.length > 0) {
|
|
75
|
+
if (out.length >= MAX_ENTRIES || opts.signal?.aborted)
|
|
76
|
+
break;
|
|
77
|
+
const { dir, depth, matchers } = stack.pop();
|
|
78
|
+
let dirents;
|
|
79
|
+
try {
|
|
80
|
+
dirents = readdirSync(dir, { withFileTypes: true });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue; // unreadable directory — skip rather than abort the whole walk
|
|
84
|
+
}
|
|
85
|
+
const entryDepth = depth + 1;
|
|
86
|
+
for (const dirent of dirents) {
|
|
87
|
+
if (out.length >= MAX_ENTRIES)
|
|
88
|
+
break;
|
|
89
|
+
const name = dirent.name;
|
|
90
|
+
const abs = path.join(dir, name);
|
|
91
|
+
let type;
|
|
92
|
+
let isDir = false;
|
|
93
|
+
if (dirent.isSymbolicLink()) {
|
|
94
|
+
type = "l";
|
|
95
|
+
}
|
|
96
|
+
else if (dirent.isDirectory()) {
|
|
97
|
+
type = "d";
|
|
98
|
+
isDir = true;
|
|
99
|
+
}
|
|
100
|
+
else if (dirent.isFile()) {
|
|
101
|
+
type = "f";
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
continue; // sockets, fifos, block devices, …
|
|
105
|
+
}
|
|
106
|
+
if (isDir && alwaysSkip.has(name))
|
|
107
|
+
continue;
|
|
108
|
+
if (isGitIgnored(abs, isDir, matchers))
|
|
109
|
+
continue;
|
|
110
|
+
if (opts.maxDepth === undefined || entryDepth <= opts.maxDepth) {
|
|
111
|
+
out.push({ abs, rel: toPosixPath(path.relative(root, abs)), type });
|
|
112
|
+
}
|
|
113
|
+
// Descend only into real directories, and only if their children can
|
|
114
|
+
// still be within the depth budget.
|
|
115
|
+
if (isDir && (opts.maxDepth === undefined || entryDepth < opts.maxDepth)) {
|
|
116
|
+
const childIg = loadGitignore(abs);
|
|
117
|
+
const nextMatchers = childIg ? [...matchers, { baseDir: abs, ig: childIg }] : matchers;
|
|
118
|
+
stack.push({ dir: abs, depth: entryDepth, matchers: nextMatchers });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Match a `find` glob against a POSIX relative path, mirroring fd's semantics:
|
|
126
|
+
* a slashless pattern matches the basename at any depth; a pattern containing a
|
|
127
|
+
* slash matches the full path and is anchored anywhere in the tree.
|
|
128
|
+
*/
|
|
129
|
+
function matchesFindPattern(relPosix, pattern) {
|
|
130
|
+
if (pattern.includes("/")) {
|
|
131
|
+
let p = pattern;
|
|
132
|
+
if (p.startsWith("/")) {
|
|
133
|
+
p = p.slice(1); // leading slash anchors to root; rel paths have no leading slash
|
|
134
|
+
}
|
|
135
|
+
else if (!p.startsWith("**/") && p !== "**") {
|
|
136
|
+
p = `**/${p}`;
|
|
137
|
+
}
|
|
138
|
+
return minimatch(relPosix, p, { dot: true });
|
|
139
|
+
}
|
|
140
|
+
return minimatch(relPosix, pattern, { dot: true, matchBase: true });
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Native replacement for the fd-backed search. Returns POSIX paths relative to
|
|
144
|
+
* `root`, with a trailing slash on directories (like fd), unsorted/undeduped —
|
|
145
|
+
* the caller applies its own dedupe/sort/limit.
|
|
146
|
+
*/
|
|
147
|
+
export function nativeFind(root, opts) {
|
|
148
|
+
const entries = collectEntries(root, {
|
|
149
|
+
maxDepth: opts.maxDepth,
|
|
150
|
+
signal: opts.signal,
|
|
151
|
+
alwaysSkipDirs: opts.alwaysSkipDirs,
|
|
152
|
+
});
|
|
153
|
+
const results = [];
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
if (entry.type !== opts.type)
|
|
156
|
+
continue;
|
|
157
|
+
if (opts.excludeGlobs.some((g) => minimatch(entry.rel, g, { dot: true })))
|
|
158
|
+
continue;
|
|
159
|
+
if (!opts.patterns.some((pat) => matchesFindPattern(entry.rel, pat)))
|
|
160
|
+
continue;
|
|
161
|
+
results.push(entry.type === "d" ? `${entry.rel}/` : entry.rel);
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
function escapeRegExp(value) {
|
|
166
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
167
|
+
}
|
|
168
|
+
/** Cheap binary-file heuristic: a NUL byte in the first chunk. rg skips these. */
|
|
169
|
+
function looksBinary(content) {
|
|
170
|
+
const sampleLength = Math.min(content.length, 8192);
|
|
171
|
+
for (let i = 0; i < sampleLength; i++) {
|
|
172
|
+
if (content.charCodeAt(i) === 0)
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Native replacement for the rg-backed content search. Collects up to `limit`
|
|
179
|
+
* matches as `{ filePath, lineNumber, lineText }`, the same shape the tool's
|
|
180
|
+
* formatter already consumes from rg's JSON output.
|
|
181
|
+
*
|
|
182
|
+
* Throws an Error tagged `invalidRegex` when a non-literal pattern is not a
|
|
183
|
+
* valid JS regex, so the caller can surface the same "pass literal: true" hint.
|
|
184
|
+
*/
|
|
185
|
+
export async function nativeGrep(root, opts) {
|
|
186
|
+
let regex;
|
|
187
|
+
const flags = opts.ignoreCase ? "i" : "";
|
|
188
|
+
if (opts.literal) {
|
|
189
|
+
regex = new RegExp(escapeRegExp(opts.pattern), flags);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
try {
|
|
193
|
+
regex = new RegExp(opts.pattern, flags);
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
const error = new Error(e instanceof Error ? e.message : String(e));
|
|
197
|
+
error.invalidRegex = true;
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
let files;
|
|
202
|
+
if (!opts.isDirectory) {
|
|
203
|
+
files = [root];
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const entries = collectEntries(root, { signal: opts.signal });
|
|
207
|
+
files = entries
|
|
208
|
+
.filter((e) => e.type === "f")
|
|
209
|
+
.filter((e) => {
|
|
210
|
+
if (!opts.glob)
|
|
211
|
+
return true;
|
|
212
|
+
return minimatch(e.rel, opts.glob, { dot: true, matchBase: !opts.glob.includes("/") });
|
|
213
|
+
})
|
|
214
|
+
.map((e) => e.abs);
|
|
215
|
+
}
|
|
216
|
+
const matches = [];
|
|
217
|
+
let matchLimitReached = false;
|
|
218
|
+
for (const filePath of files) {
|
|
219
|
+
if (opts.signal?.aborted || matches.length >= opts.limit)
|
|
220
|
+
break;
|
|
221
|
+
try {
|
|
222
|
+
if (statSync(filePath).size > MAX_GREP_FILE_BYTES)
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
let content;
|
|
229
|
+
try {
|
|
230
|
+
content = await opts.readFile(filePath);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (looksBinary(content))
|
|
236
|
+
continue;
|
|
237
|
+
const lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
238
|
+
for (let i = 0; i < lines.length; i++) {
|
|
239
|
+
// New RegExp per file with no /g flag → test() is stateless across lines.
|
|
240
|
+
if (regex.test(lines[i])) {
|
|
241
|
+
matches.push({ filePath, lineNumber: i + 1, lineText: lines[i] });
|
|
242
|
+
if (matches.length >= opts.limit) {
|
|
243
|
+
matchLimitReached = true;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { matches, matchLimitReached };
|
|
250
|
+
}
|
|
251
|
+
/** Whether the native search path is forced regardless of fd/rg availability. */
|
|
252
|
+
export function isNativeSearchForced() {
|
|
253
|
+
return process.env.HOOCODE_NATIVE_SEARCH === "1";
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=native-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-search.js","sourceRoot":"","sources":["../../../src/core/tools/native-search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Native (pure-JS) fallbacks for the `find` and `grep` tools.\n *\n * The tools normally shell out to `fd` / `rg`, which are downloaded on demand\n * (see tools-manager.ts). In restricted environments those binaries may be\n * neither on PATH nor downloadable, and `HOOCODE_NATIVE_SEARCH=1` can also force\n * this path. Rather than failing the tool and asking the model to fall back to\n * `bash`, these functions reproduce the essential behaviour in JS so search\n * degrades automatically:\n *\n * - hierarchical `.gitignore` handling (each `.gitignore` is scoped to its own\n * subtree, matching fd's `--no-require-git` behaviour and issue #3303),\n * - hidden files included (like `fd --hidden` / `rg --hidden`),\n * - `.git` always skipped; `node_modules` skipped for `find` (mirrors the\n * tool's built-in excludes) but left to `.gitignore` for `grep` (like rg).\n *\n * These are best-effort approximations, not byte-for-byte fd/rg parity: globs\n * are matched with `minimatch` and patterns with JS `RegExp`, and only\n * `.gitignore` files are honoured (not `.ignore` or global excludes).\n */\n\nimport { readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore, { type Ignore } from \"ignore\";\nimport { minimatch } from \"minimatch\";\nimport path from \"path\";\nimport { toPosixPath } from \"./fd-utils.js\";\n\nexport type EntryType = \"f\" | \"d\" | \"l\";\n\n/** Hard cap on entries enumerated during a single walk, so a pathological tree\n * can never hang the fallback. Well above any tool's own result limit. */\nconst MAX_ENTRIES = 200_000;\n\n/** Files larger than this are skipped by the grep fallback (rg streams; we read\n * whole files, so we guard against loading huge blobs into memory). */\nconst MAX_GREP_FILE_BYTES = 20 * 1024 * 1024;\n\nexport interface CollectedEntry {\n\t/** Absolute path. */\n\tabs: string;\n\t/** POSIX path relative to the walk root. */\n\trel: string;\n\ttype: EntryType;\n}\n\n/** A `.gitignore` matcher scoped to the subtree rooted at `baseDir`. */\ninterface GitignoreMatcher {\n\tbaseDir: string;\n\tig: Ignore;\n}\n\nfunction loadGitignore(dir: string): Ignore | undefined {\n\tlet content: string;\n\ttry {\n\t\tcontent = readFileSync(path.join(dir, \".gitignore\"), \"utf-8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\treturn ignore().add(content);\n}\n\n/**\n * Whether `absPath` is ignored by any applicable `.gitignore`. Each matcher only\n * applies to paths inside its `baseDir`, and the path is tested relative to that\n * base — so `a/.gitignore` scopes to `a/` and its descendants but never `b/`.\n */\nfunction isGitIgnored(absPath: string, isDir: boolean, matchers: GitignoreMatcher[]): boolean {\n\tfor (const m of matchers) {\n\t\tconst rel = path.relative(m.baseDir, absPath);\n\t\tif (rel === \"\" || rel.startsWith(\"..\") || path.isAbsolute(rel)) continue;\n\t\tconst relPosix = toPosixPath(rel) + (isDir ? \"/\" : \"\");\n\t\tif (m.ig.ignores(relPosix)) return true;\n\t}\n\treturn false;\n}\n\nexport interface WalkOptions {\n\t/** Max entry depth relative to root; direct children are depth 1. */\n\tmaxDepth?: number;\n\tsignal?: AbortSignal;\n\t/** Directory names to never descend into. Defaults to `.git`. */\n\talwaysSkipDirs?: Set<string>;\n}\n\n/**\n * Walk `root` depth-first, returning every entry not excluded by `.gitignore`\n * or an always-skip directory. Symlinks are reported but never followed (avoids\n * cycles). Enumeration stops at {@link MAX_ENTRIES}.\n */\nexport function collectEntries(root: string, opts: WalkOptions = {}): CollectedEntry[] {\n\tconst out: CollectedEntry[] = [];\n\tconst alwaysSkip = opts.alwaysSkipDirs ?? new Set([\".git\"]);\n\n\tconst rootMatchers: GitignoreMatcher[] = [];\n\tconst rootIg = loadGitignore(root);\n\tif (rootIg) rootMatchers.push({ baseDir: root, ig: rootIg });\n\n\t// `depth` is the depth of `dir`; its direct children are at depth + 1.\n\tconst stack: Array<{ dir: string; depth: number; matchers: GitignoreMatcher[] }> = [\n\t\t{ dir: root, depth: 0, matchers: rootMatchers },\n\t];\n\n\twhile (stack.length > 0) {\n\t\tif (out.length >= MAX_ENTRIES || opts.signal?.aborted) break;\n\t\tconst { dir, depth, matchers } = stack.pop()!;\n\n\t\tlet dirents: import(\"fs\").Dirent[];\n\t\ttry {\n\t\t\tdirents = readdirSync(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\tcontinue; // unreadable directory — skip rather than abort the whole walk\n\t\t}\n\n\t\tconst entryDepth = depth + 1;\n\t\tfor (const dirent of dirents) {\n\t\t\tif (out.length >= MAX_ENTRIES) break;\n\n\t\t\tconst name = dirent.name;\n\t\t\tconst abs = path.join(dir, name);\n\n\t\t\tlet type: EntryType;\n\t\t\tlet isDir = false;\n\t\t\tif (dirent.isSymbolicLink()) {\n\t\t\t\ttype = \"l\";\n\t\t\t} else if (dirent.isDirectory()) {\n\t\t\t\ttype = \"d\";\n\t\t\t\tisDir = true;\n\t\t\t} else if (dirent.isFile()) {\n\t\t\t\ttype = \"f\";\n\t\t\t} else {\n\t\t\t\tcontinue; // sockets, fifos, block devices, …\n\t\t\t}\n\n\t\t\tif (isDir && alwaysSkip.has(name)) continue;\n\t\t\tif (isGitIgnored(abs, isDir, matchers)) continue;\n\n\t\t\tif (opts.maxDepth === undefined || entryDepth <= opts.maxDepth) {\n\t\t\t\tout.push({ abs, rel: toPosixPath(path.relative(root, abs)), type });\n\t\t\t}\n\n\t\t\t// Descend only into real directories, and only if their children can\n\t\t\t// still be within the depth budget.\n\t\t\tif (isDir && (opts.maxDepth === undefined || entryDepth < opts.maxDepth)) {\n\t\t\t\tconst childIg = loadGitignore(abs);\n\t\t\t\tconst nextMatchers = childIg ? [...matchers, { baseDir: abs, ig: childIg }] : matchers;\n\t\t\t\tstack.push({ dir: abs, depth: entryDepth, matchers: nextMatchers });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out;\n}\n\n/**\n * Match a `find` glob against a POSIX relative path, mirroring fd's semantics:\n * a slashless pattern matches the basename at any depth; a pattern containing a\n * slash matches the full path and is anchored anywhere in the tree.\n */\nfunction matchesFindPattern(relPosix: string, pattern: string): boolean {\n\tif (pattern.includes(\"/\")) {\n\t\tlet p = pattern;\n\t\tif (p.startsWith(\"/\")) {\n\t\t\tp = p.slice(1); // leading slash anchors to root; rel paths have no leading slash\n\t\t} else if (!p.startsWith(\"**/\") && p !== \"**\") {\n\t\t\tp = `**/${p}`;\n\t\t}\n\t\treturn minimatch(relPosix, p, { dot: true });\n\t}\n\treturn minimatch(relPosix, pattern, { dot: true, matchBase: true });\n}\n\nexport interface NativeFindOptions {\n\tpatterns: string[];\n\ttype: EntryType;\n\t/** Extra exclusion globs (already includes node_modules/.git for find). */\n\texcludeGlobs: string[];\n\tmaxDepth?: number;\n\talwaysSkipDirs?: Set<string>;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Native replacement for the fd-backed search. Returns POSIX paths relative to\n * `root`, with a trailing slash on directories (like fd), unsorted/undeduped —\n * the caller applies its own dedupe/sort/limit.\n */\nexport function nativeFind(root: string, opts: NativeFindOptions): string[] {\n\tconst entries = collectEntries(root, {\n\t\tmaxDepth: opts.maxDepth,\n\t\tsignal: opts.signal,\n\t\talwaysSkipDirs: opts.alwaysSkipDirs,\n\t});\n\n\tconst results: string[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== opts.type) continue;\n\t\tif (opts.excludeGlobs.some((g) => minimatch(entry.rel, g, { dot: true }))) continue;\n\t\tif (!opts.patterns.some((pat) => matchesFindPattern(entry.rel, pat))) continue;\n\t\tresults.push(entry.type === \"d\" ? `${entry.rel}/` : entry.rel);\n\t}\n\treturn results;\n}\n\nexport interface NativeGrepMatch {\n\tfilePath: string;\n\tlineNumber: number;\n\tlineText: string;\n}\n\nexport interface NativeGrepOptions {\n\tpattern: string;\n\t/** True when `root` is a directory; false when it is a single file. */\n\tisDirectory: boolean;\n\tignoreCase?: boolean;\n\tliteral?: boolean;\n\t/** Optional glob filter applied to file paths (like rg --glob). */\n\tglob?: string;\n\t/** Stop after this many matches. */\n\tlimit: number;\n\tsignal?: AbortSignal;\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nexport interface NativeGrepResult {\n\tmatches: NativeGrepMatch[];\n\tmatchLimitReached: boolean;\n}\n\nfunction escapeRegExp(value: string): string {\n\treturn value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** Cheap binary-file heuristic: a NUL byte in the first chunk. rg skips these. */\nfunction looksBinary(content: string): boolean {\n\tconst sampleLength = Math.min(content.length, 8192);\n\tfor (let i = 0; i < sampleLength; i++) {\n\t\tif (content.charCodeAt(i) === 0) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Native replacement for the rg-backed content search. Collects up to `limit`\n * matches as `{ filePath, lineNumber, lineText }`, the same shape the tool's\n * formatter already consumes from rg's JSON output.\n *\n * Throws an Error tagged `invalidRegex` when a non-literal pattern is not a\n * valid JS regex, so the caller can surface the same \"pass literal: true\" hint.\n */\nexport async function nativeGrep(root: string, opts: NativeGrepOptions): Promise<NativeGrepResult> {\n\tlet regex: RegExp;\n\tconst flags = opts.ignoreCase ? \"i\" : \"\";\n\tif (opts.literal) {\n\t\tregex = new RegExp(escapeRegExp(opts.pattern), flags);\n\t} else {\n\t\ttry {\n\t\t\tregex = new RegExp(opts.pattern, flags);\n\t\t} catch (e) {\n\t\t\tconst error = new Error(e instanceof Error ? e.message : String(e)) as Error & { invalidRegex?: boolean };\n\t\t\terror.invalidRegex = true;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tlet files: string[];\n\tif (!opts.isDirectory) {\n\t\tfiles = [root];\n\t} else {\n\t\tconst entries = collectEntries(root, { signal: opts.signal });\n\t\tfiles = entries\n\t\t\t.filter((e) => e.type === \"f\")\n\t\t\t.filter((e) => {\n\t\t\t\tif (!opts.glob) return true;\n\t\t\t\treturn minimatch(e.rel, opts.glob, { dot: true, matchBase: !opts.glob.includes(\"/\") });\n\t\t\t})\n\t\t\t.map((e) => e.abs);\n\t}\n\n\tconst matches: NativeGrepMatch[] = [];\n\tlet matchLimitReached = false;\n\n\tfor (const filePath of files) {\n\t\tif (opts.signal?.aborted || matches.length >= opts.limit) break;\n\n\t\ttry {\n\t\t\tif (statSync(filePath).size > MAX_GREP_FILE_BYTES) continue;\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = await opts.readFile(filePath);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (looksBinary(content)) continue;\n\n\t\tconst lines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t// New RegExp per file with no /g flag → test() is stateless across lines.\n\t\t\tif (regex.test(lines[i])) {\n\t\t\t\tmatches.push({ filePath, lineNumber: i + 1, lineText: lines[i] });\n\t\t\t\tif (matches.length >= opts.limit) {\n\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { matches, matchLimitReached };\n}\n\n/** Whether the native search path is forced regardless of fd/rg availability. */\nexport function isNativeSearchForced(): boolean {\n\treturn process.env.HOOCODE_NATIVE_SEARCH === \"1\";\n}\n"]}
|
|
@@ -46,7 +46,20 @@ export interface ThinkingEscalationConfig {
|
|
|
46
46
|
/** Number of subsequent turns to stay escalated after an error. Default: 1. */
|
|
47
47
|
cooldown_turns?: number;
|
|
48
48
|
}
|
|
49
|
+
/** LLM defaults seeded in hoo-config.json and honoured during model selection. */
|
|
50
|
+
export interface HooLlmConfig {
|
|
51
|
+
/** Preferred provider when the pi-layer settings.json has no saved default. */
|
|
52
|
+
default_provider?: string;
|
|
53
|
+
/** Preferred model id for `default_provider` (otherwise the provider's built-in default). */
|
|
54
|
+
default_model?: string;
|
|
55
|
+
/** Informational map of provider → env var holding its API key. */
|
|
56
|
+
providers?: Record<string, {
|
|
57
|
+
api_key_env?: string;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
49
60
|
export interface HooConfig {
|
|
61
|
+
/** LLM default provider/model preferences. */
|
|
62
|
+
llm?: HooLlmConfig;
|
|
50
63
|
/** Manually-pinned active mode (overrides default "build") */
|
|
51
64
|
active_mode?: string;
|
|
52
65
|
/** Per-mode configuration keyed by mode name */
|